From 2168624c2d504b3a045b859b6b00988dfac04a43 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Thu, 1 Jul 2021 00:00:25 +0200 Subject: [PATCH 01/36] Add support for Kelon ACs --- src/IRac.cpp | 65 ++++++ src/IRac.h | 7 + src/IRrecv.cpp | 4 + src/IRrecv.h | 4 + src/IRremoteESP8266.h | 13 +- src/IRsend.cpp | 5 + src/IRsend.h | 4 + src/IRtext.cpp | 2 + src/IRtext.h | 1 + src/IRutils.cpp | 1 + src/ir_Kelon.cpp | 451 ++++++++++++++++++++++++++++++++++++++++++ src/ir_Kelon.h | 158 +++++++++++++++ src/locale/defaults.h | 6 + 13 files changed, 720 insertions(+), 1 deletion(-) create mode 100644 src/ir_Kelon.cpp create mode 100644 src/ir_Kelon.h diff --git a/src/IRac.cpp b/src/IRac.cpp index 97fd695e3..d1d212fd1 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -28,6 +28,7 @@ #include "ir_Fujitsu.h" #include "ir_Haier.h" #include "ir_Hitachi.h" +#include "ir_Kelon.h" #include "ir_Kelvinator.h" #include "ir_LG.h" #include "ir_Midea.h" @@ -217,6 +218,9 @@ bool IRac::isProtocolSupported(const decode_type_t protocol) { #if SEND_HITACHI_AC424 case decode_type_t::HITACHI_AC424: #endif +#if SEND_KELON + case decode_type_t::KELON: +#endif #if SEND_KELVINATOR case decode_type_t::KELVINATOR: #endif @@ -1250,6 +1254,36 @@ void IRac::hitachi424(IRHitachiAc424 *ac, } #endif // SEND_HITACHI_AC424 +#if SEND_KELON +/// Send a Kelon A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRKelonAC object to use. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] sleep Run the device in sleep/quiet mode. +/// @param[in] superCool Run the device in Super cooling mode. +/// @param[in] dryGrade The dehumidification intensity grade +/// @param[in] togglePower Whether to toggle the unit's power +/// @param[in] toggleSwing Whether to toggle the swing setting +void IRac::kelon(IRKelonAC *ac, + const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan, const bool sleep, + const bool superCool, const int8_t dryGrade, + const bool togglePower = false, const bool toggleSwing = false) { + ac->begin(); + ac->setMode(IRKelonAC::convertMode(mode)); + ac->setFan(IRKelonAC::convertFan(fan)); + ac->setTemp(static_cast(degrees)); + ac->setSleep(sleep); + ac->setSupercool(superCool); + + ac->setTogglePower(togglePower); + ac->setToggleSwingVertical(toggleSwing); + + ac->send(); +} +#endif // SEND_KELON + #if SEND_KELVINATOR /// Send a Kelvinator A/C message with the supplied settings. /// @param[in, out] ac A Ptr to an IRKelvinatorAC object to use. @@ -2262,6 +2296,14 @@ stdAc::state_t IRac::handleToggles(const stdAc::state_t desired, if (desired.model == panasonic_ac_remote_model_t::kPanasonicCkp) result.power = desired.power ^ prev->power; break; + case decode_type_t::KELON: + result.power = desired.power ^ prev->power; + if ((desired.swingv == stdAc::swingv_t::kOff) ^ + (prev->swingv == stdAc::swingv_t::kOff)) // It changed, so toggle. + result.swingv = stdAc::swingv_t::kAuto; + else + result.swingv = stdAc::swingv_t::kOff; // No change, so no toggle. + break; default: {}; } @@ -2572,6 +2614,14 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { break; } #endif // SEND_HITACHI_AC424 +#if SEND_KELON + case KELON: { + IRKelonAC ac(_pin, _inverted, _modulation); + kelon(&ac, send.mode, send.degrees, send.fanspeed, send.sleep, + send.turbo, 0, send.power, send.swingv != stdAc::swingv_t::kOff); + break; + } +#endif #if SEND_KELVINATOR case KELVINATOR: { @@ -3281,6 +3331,13 @@ namespace IRAcUtils { return ac.toString(); } #endif // DECODE_FUJITSU_AC +#if DECODE_KELON + case decode_type_t::KELON: { + IRKelonAC ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_KELON #if DECODE_KELVINATOR case decode_type_t::KELVINATOR: { IRKelvinatorAC ac(kGpioUnused); @@ -3766,6 +3823,14 @@ namespace IRAcUtils { break; } #endif // DECODE_HITACHI_AC424 +#if DECODE_KELON + case decode_type_t::KELON: { + IRKelonAC ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif #if DECODE_KELVINATOR case decode_type_t::KELVINATOR: { IRKelvinatorAC ac(kGpioUnused); diff --git a/src/IRac.h b/src/IRac.h index 9c62f9198..415b744fd 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -22,6 +22,7 @@ #include "ir_Gree.h" #include "ir_Haier.h" #include "ir_Hitachi.h" +#include "ir_Kelon.h" #include "ir_Kelvinator.h" #include "ir_LG.h" #include "ir_Midea.h" @@ -289,6 +290,12 @@ void electra(IRElectraAc *ac, const float degrees, const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv); #endif // SEND_HITACHI_AC424 +#if SEND_KELON + void kelon(IRKelonAC *ac, + const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan, const bool sleep, const bool superCool, + const int8_t dryGrade, const bool togglePower, const bool toggleSwing); +#endif // SEND_KELON #if SEND_KELVINATOR void kelvinator(IRKelvinatorAC *ac, const bool on, const stdAc::opmode_t mode, diff --git a/src/IRrecv.cpp b/src/IRrecv.cpp index d58fce295..6f0eb61c1 100644 --- a/src/IRrecv.cpp +++ b/src/IRrecv.cpp @@ -1018,6 +1018,10 @@ bool IRrecv::decode(decode_results *results, irparams_t *save, DPRINTLN("Attempting Teknopoint decode"); if (decodeTeknopoint(results, offset)) return true; #endif // DECODE_TEKNOPOINT +#if DECODE_KELON + DPRINTLN("Attempting Kelon decode"); + if (decodeKelon(results, offset, kKelonBits)) return true; +#endif // DECODE_KELON // Typically new protocols are added above this line. } #if DECODE_HASH diff --git a/src/IRrecv.h b/src/IRrecv.h index de9794939..ed204cbb0 100644 --- a/src/IRrecv.h +++ b/src/IRrecv.h @@ -744,6 +744,10 @@ class IRrecv { const uint16_t nbits = kTeknopointBits, const bool strict = true); #endif // DECODE_TEKNOPOINT +#if DECODE_KELON + bool decodeKelon(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kKelonBits, const bool strict = true); +#endif // DECODE_KELON }; #endif // IRRECV_H_ diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h index 8661f8b3f..fdc992c5b 100644 --- a/src/IRremoteESP8266.h +++ b/src/IRremoteESP8266.h @@ -37,6 +37,7 @@ * Vestel AC code by Erdem U. Altınyurt * Teco AC code by Fabien Valthier (hcoohb) * Mitsubishi 112 AC Code by kuchel77 + * Kelon AC code by Davide Depau (Depau) * * GPL license, all text above must be included in any redistribution ****************************************************/ @@ -761,6 +762,13 @@ #define SEND_TEKNOPOINT _IR_ENABLE_DEFAULT_ #endif // SEND_TEKNOPOINT +#ifndef DECODE_KELON +#define DECODE_KELON _IR_ENABLE_DEFAULT_ +#endif // DECODE_KELON +#ifndef SEND_KELON +#define SEND_KELON _IR_ENABLE_DEFAULT_ +#endif // SEND_KELON + #if (DECODE_ARGO || DECODE_DAIKIN || DECODE_FUJITSU_AC || DECODE_GREE || \ DECODE_KELVINATOR || DECODE_MITSUBISHI_AC || DECODE_TOSHIBA_AC || \ DECODE_TROTEC || DECODE_HAIER_AC || DECODE_HITACHI_AC || \ @@ -774,7 +782,7 @@ DECODE_MITSUBISHI112 || DECODE_HITACHI_AC424 || DECODE_HITACHI_AC3 || \ DECODE_HITACHI_AC344 || DECODE_CORONA_AC || DECODE_SANYO_AC || \ DECODE_VOLTAS || DECODE_MIRAGE || DECODE_HAIER_AC176 || \ - DECODE_TEKNOPOINT || \ + DECODE_TEKNOPOINT || DECODE_KELON || \ false) // Add any DECODE to the above if it uses result->state (see kStateSizeMax) // you might also want to add the protocol to hasACState function @@ -916,6 +924,7 @@ enum decode_type_t { XMP, TRUMA, // 100 HAIER_AC176, + KELON, TEKNOPOINT, // Add new entries before this one, and update it to point to the last entry. kLastDecodeType = TEKNOPOINT, @@ -1031,6 +1040,8 @@ const uint16_t kHitachiAc424Bits = kHitachiAc424StateLength * 8; const uint16_t kInaxBits = 24; const uint16_t kInaxMinRepeat = kSingleRepeat; const uint16_t kJvcBits = 16; +const uint16_t kKelonStateLength = 6; +const uint16_t kKelonBits = kKelonStateLength * 8; const uint16_t kKelvinatorStateLength = 16; const uint16_t kKelvinatorBits = kKelvinatorStateLength * 8; const uint16_t kKelvinatorDefaultRepeat = kNoRepeat; diff --git a/src/IRsend.cpp b/src/IRsend.cpp index c7ba7daa7..febddeb65 100644 --- a/src/IRsend.cpp +++ b/src/IRsend.cpp @@ -1182,6 +1182,11 @@ bool IRsend::send(const decode_type_t type, const uint8_t *state, sendHitachiAc424(state, nbytes); break; #endif // SEND_HITACHI_AC424 +#if SEND_KELON + case KELON: + sendKelon(state, nbytes); + break; +#endif // SEND_KELON #if SEND_KELVINATOR case KELVINATOR: sendKelvinator(state, nbytes); diff --git a/src/IRsend.h b/src/IRsend.h index 2799924fd..53b6058e0 100644 --- a/src/IRsend.h +++ b/src/IRsend.h @@ -717,6 +717,10 @@ class IRsend { const uint16_t nbytes = kTeknopointStateLength, const uint16_t repeat = kNoRepeat); #endif // SEND_TEKNOPOINT +#if SEND_KELON + void sendKelon(const unsigned char data[], const uint16_t nbytes = kKelonStateLength, + const uint16_t repeat = kNoRepeat); +#endif // SEND_KELON protected: #ifdef UNIT_TEST diff --git a/src/IRtext.cpp b/src/IRtext.cpp index cbd03c434..48f288fe9 100644 --- a/src/IRtext.cpp +++ b/src/IRtext.cpp @@ -99,6 +99,7 @@ const PROGMEM char* k6thSenseStr = D_STR_6THSENSE; ///< "6th Sense" const PROGMEM char* kTypeStr = D_STR_TYPE; ///< "Type" const PROGMEM char* kSpecialStr = D_STR_SPECIAL; ///< "Special" const PROGMEM char* kIdStr = D_STR_ID; ///< "Id" / Device Identifier +const PROGMEM char* kDryGradeStr = D_STR_DRY_GRADE; ///< "Dry grade" const PROGMEM char* kAutoStr = D_STR_AUTO; ///< "Auto" const PROGMEM char* kAutomaticStr = D_STR_AUTOMATIC; ///< "Automatic" @@ -287,5 +288,6 @@ const PROGMEM char *kAllProtocolNamesStr = D_STR_TRUMA "\x0" D_STR_HAIER_AC176 "\x0" D_STR_TEKNOPOINT "\x0" + D_STR_KELON "\x0" ///< New protocol strings should be added just above this line. "\x0"; ///< This string requires double null termination. diff --git a/src/IRtext.h b/src/IRtext.h index d991276cd..05fa563ae 100644 --- a/src/IRtext.h +++ b/src/IRtext.h @@ -46,6 +46,7 @@ extern const char* kDayStr; extern const char* kDisplayTempStr; extern const char* kDownStr; extern const char* kDryStr; +extern const char* kDryGradeStr; extern const char* kEconoStr; extern const char* kEconoToggleStr; extern const char* kEyeAutoStr; diff --git a/src/IRutils.cpp b/src/IRutils.cpp index bf35d6cbf..10cb1e177 100644 --- a/src/IRutils.cpp +++ b/src/IRutils.cpp @@ -152,6 +152,7 @@ bool hasACState(const decode_type_t protocol) { case HITACHI_AC3: case HITACHI_AC344: case HITACHI_AC424: + case KELON: case KELVINATOR: case MIRAGE: case MITSUBISHI136: diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp new file mode 100644 index 000000000..e63a79eac --- /dev/null +++ b/src/ir_Kelon.cpp @@ -0,0 +1,451 @@ +// Copyright 2021 Davide Depau + +/// @file +/// @brief Support for Kelan AC protocol. +/// Both sending and decoding should be functional for models of series KELON ON/OFF 9000-12000. +/// All features of the standard remote are implemented. +/// +/// @note Unsupported: +/// - Explicit on/off due to AC unit limitations +/// - Explicit swing position due to AC unit limitations +/// - Fahrenheit. + +#include + +#include "ir_Kelon.h" + +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" +#include "IRtext.h" + + +using irutils::addBoolToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addFanToString; +using irutils::addTempToString; + +// Constants +const uint16_t kKelonHdrMark = 9000; +const uint16_t kKelonHdrSpace = 4600; +const uint16_t kKelonBitMark = 560; +const uint16_t kKelonOneSpace = 1680; +const uint16_t kKelonZeroSpace = 600; +const uint32_t kKelonGap = kDefaultMessageGap; +const uint16_t kKelonFreq = 38000; + +#if SEND_KELON + +/// Send a Kelon message. +/// Status: Beta / Should be working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendKelon(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + sendGeneric(kKelonHdrMark, kKelonHdrSpace, + kKelonBitMark, kKelonOneSpace, + kKelonBitMark, kKelonZeroSpace, + kKelonBitMark, kKelonGap, + data, nbytes, kKelonFreq, false, // LSB First. + repeat, 50); +} + +#endif // SEND_KELON + +#if DECODE_KELON +/// Decode the supplied Kelon message. +/// Status: Beta / Should be working. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. + +bool IRrecv::decodeKelon(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kKelonBits) { + return false; + } + uint16_t used; + used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kKelonHdrMark, kKelonHdrSpace, + kKelonBitMark, kKelonOneSpace, + kKelonBitMark, kKelonZeroSpace, + kKelonBitMark, kKelonBitMark, false, + _tolerance, 0, false); + + // Data bits + 2 bits header + 1 bit footer = 99 bits + if (strict && used != nbits * 2 + 3) { + return false; + } + + results->decode_type = decode_type_t::KELON; + results->bits = nbits; + return true; +} + +#endif // DECODE_KELON + +/// 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? +IRKelonAC::IRKelonAC(const uint16_t pin, const bool inverted, const bool use_modulation) + : _irsend{pin, inverted, use_modulation}, _{} { stateReset(); } + +/// Reset the internals of the object to a known good state. +void IRKelonAC::stateReset() { + for (unsigned char &i : _.raw) i = 0x0; + _.preamble[0] = 0b10000011; + _.preamble[1] = 0b00000110; +} + +#if SEND_KELON + +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRKelonAC::send(const uint16_t repeat) { + _irsend.sendKelon(getRaw(), kKelonStateLength, repeat); + + // Reset toggle flags + _.PowerToggle = false; + _.SwingVToggle = false; + + // Remove the timer time setting + _.TimerHours = 0; + _.TimerHalfHour = 0; +} + +#endif // SEND_KELON + +/// Set up hardware to be able to send a message. +void IRKelonAC::begin() { + _irsend.begin(); +} + +/// Request toggling power - will be reset to false after sending +/// @param[in] toggle Whether to toggle the power state +void IRKelonAC::setTogglePower(const bool toggle) { + _.PowerToggle = toggle; +} + +/// Get whether toggling power will be requested +/// @return The power toggle state +bool IRKelonAC::getTogglePower() const { + return _.PowerToggle; +} + +/// Set the temperature setting. +/// @param[in] degrees The temperature in degrees celsius. +void IRKelonAC::setTemp(const uint8_t degrees) { + uint8_t temp = std::max(kKelonMinTemp, degrees); + temp = std::min(kKelonMaxTemp, temp); + _previousTemp = _.Temperature; + _.Temperature = temp - kKelonMinTemp; +} + +/// Get the current temperature setting. +/// @return Get current setting for temp. in degrees celsius. +uint8_t IRKelonAC::getTemp() const { + return _.Temperature + kKelonMinTemp; +} + +/// Set the speed of the fan. +/// @param[in] speed 0 is auto, 1-5 is the speed +void IRKelonAC::setFan(const uint8_t speed) { + uint8_t fan = std::min(speed, kKelonFanMax); + + // Note: Kelon fan speeds are backwards! This code maps the range 0,1:3 to 0,3:1 to save the API's user's sanity. + _.Fan = ((static_cast(fan) - 4) * -1) % 4; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRKelonAC::getFan() const { + return ((static_cast(_.Fan) - 4) * -1) % 4;; +} + +/// Set the dehumidification intensity. +/// @param[in] grade has to be in the range [-2 : +2] +void IRKelonAC::setDryGrade(const int8_t grade) { + int8_t drygrade = std::max(kKelonDryGradeMin, grade); + drygrade = std::min(kKelonDryGradeMax, drygrade); + + // Two's complement is clearly too bleeding edge for this manufacturer + uint8_t outval; + if (drygrade < 0) { + outval = 0b100 | (-drygrade & 0b011); + } else { + outval = drygrade & 0b011; + } + _.DehumidifierGrade = outval; +} + +/// Get the current dehumidification intensity setting. In smart mode, this controls the temperature adjustment. +/// @return The current dehumidification intensity. +int8_t IRKelonAC::getDryGrade() const { + auto outval = static_cast(_.DehumidifierGrade & 0b011); + if ((_.DehumidifierGrade & 0b100) == 0b100) { + outval *= -1; + } + return outval; +} + +/// Set the desired operation mode. +/// @param[in] mode The desired operation mode. +void IRKelonAC::setMode(const uint8_t mode) { + if (_previousMode == kKelonModeSmart || _previousMode == kKelonModeFan || _.SuperCoolEnabled1) { + _.Temperature = _previousTemp; + } + _.SuperCoolEnabled1 = false; + _.SuperCoolEnabled2 = false; + _previousMode = _.Mode; + + switch (mode) { + case kKelonModeSmart: + _.Temperature = 26 - kKelonMinTemp; // Do not save _previousTemp + _.SmartModeEnabled = true; + _.Mode = mode; + break; + case kKelonModeDry: + case kKelonModeFan: + _.Temperature = 25 - kKelonMinTemp; // Do not save _previousTemp + _.Mode = mode; + //fallthrough + case kKelonModeCool: + case kKelonModeHeat: + _.Mode = mode; + // fallthrough + default: + _.SmartModeEnabled = false; + } +} + +/// Get the current operation mode setting. +/// @return The current operation mode. +uint8_t IRKelonAC::getMode() const { + return _.Mode; +} + +/// Request toggling the vertical swing - will be reset to false after sending +/// @param[in] toggle If true, the swing mode will be toggled when sent. +void IRKelonAC::setToggleSwingVertical(const bool toggle) { + _.SwingVToggle = toggle; +} + +/// Get whether the swing mode is set to be toggled +/// @return Whether the toggle bit is set +bool IRKelonAC::getToggleSwingVertical() const { + return _.SwingVToggle; +} + +/// Control the current sleep (quiet) setting. +/// @param[in] on The desired setting. +void IRKelonAC::setSleep(const bool on) { + _.SleepEnabled = on; +} + +/// Is the sleep setting on? +/// @return The current value. +bool IRKelonAC::getSleep() const { + return _.SleepEnabled; +} + +/// Control the current super cool mode setting. +/// @param[in] on The desired setting. +void IRKelonAC::setSupercool(const bool on) { + _.SuperCoolEnabled1 = on; + _.SuperCoolEnabled2 = on; + if (on) { + setTemp(kKelonMinTemp); + setMode(kKelonModeCool); + } else { + _.Temperature = _previousTemp; + setMode(_previousMode); + } +} + +/// Is the super cool mode setting on? +/// @return The current value. +bool IRKelonAC::getSupercool() const { + return _.SuperCoolEnabled1; +} + +/// Set the timer time and enable it. Timer is an off timer if the unit is on, it is an on timer if the unit is off. +/// @param[in] mins Timer minutes (only multiples of 30m are supported for < 10h, then only multiples of 60m) +void IRKelonAC::setTimer(uint8_t mins) { + const uint8_t minutes = std::min(static_cast(mins), 24 * 60); + + if (minutes / 60 >= 10) { + uint8_t hours = minutes / 60 + 10; + _.TimerHalfHour = hours & 1; + _.TimerHours = hours >> 1; + } else { + _.TimerHalfHour = (minutes % 60) >= 30 ? 1 : 0; + _.TimerHours = minutes / 60; + } + + setTimerEnabled(true); +} + +/// Get the set timer. Timer set time is deleted once the command is sent, so calling this after send() will return 0. +/// The AC unit will continue keeping track of the remaining time unless it is later disabled. +/// @return The timer set minutes +uint8_t IRKelonAC::getTimer() const { + if (_.TimerHours >= 10) { + return (((_.TimerHours << 1) | _.TimerHalfHour) - 10) * 60; + } + return _.TimerHours * 60 + _.TimerHalfHour ? 30 : 0; +} + +/// Enable or disable the timer. Note that in order to enable the timer the minutes must be set with setTimer(). +/// @param[in] on Whether to enable or disable the timer +void IRKelonAC::setTimerEnabled(bool on) { + _.TimerEnabled = on; +} + +/// Get the current timer status +/// @return Whether the timer is enabled. +bool IRKelonAC::getTimerEnabled() const { + return _.TimerEnabled; +} + + +/// Get the raw state of the object, suitable to be sent with the appropriate +/// IRsend object method. +/// @return A PTR to the internal state. +uint8_t *IRKelonAC::getRaw() { + return _.raw; +} + +/// Set the raw state of the object. +/// @param[in] new_code The raw state from the native IR message. +void IRKelonAC::setRaw(const uint8_t *new_code) { + std::memcpy(_.raw, new_code, kKelonStateLength); +} + +/// Convert a standard A/C mode (stdAc::opmode_t) into it a native mode. +/// @param[in] mode A stdAc::opmode_t operation mode. +/// @return The native mode equivalent. +uint8_t IRKelonAC::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: + return kKelonModeCool; + case stdAc::opmode_t::kHeat: + return kKelonModeHeat; + case stdAc::opmode_t::kDry: + return kKelonModeDry; + case stdAc::opmode_t::kFan: + return kKelonModeFan; + case stdAc::opmode_t::kAuto: + default: + return kKelonModeSmart; + } +} + +/// Convert a standard A/C fan speed (stdAc::fanspeed_t) into it a native speed. +/// @param[in] mode A stdAc::fanspeed_t fan speed +/// @return The native speed equivalent. +uint8_t IRKelonAC::convertFan(stdAc::fanspeed_t fan) { + switch (fan) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: + return kKelonFanMin; + case stdAc::fanspeed_t::kMedium: + return kKelonFanMedium; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: + return kKelonFanMax; + case stdAc::fanspeed_t::kAuto: + default: + return kKelonFanAuto; + } +} + +/// Convert a native mode to it's stdAc::opmode_t equivalent. +/// @param[in] mode A native operating mode value. +/// @return The stdAc::opmode_t equivalent. +stdAc::opmode_t IRKelonAC::toCommonMode(const uint8_t mode) { + switch (mode) { + case kKelonModeCool: + return stdAc::opmode_t::kCool; + case kKelonModeHeat: + return stdAc::opmode_t::kHeat; + case kKelonModeDry: + return stdAc::opmode_t::kDry; + case kKelonModeFan: + return stdAc::opmode_t::kFan; + case kKelonModeSmart: + default: + return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed to it's stdAc::fanspeed_t equivalent. +/// @param[in] speed A native fan speed value. +/// @return The stdAc::fanspeed_t equivalent. +stdAc::fanspeed_t IRKelonAC::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kKelonFanMin: + return stdAc::fanspeed_t::kLow; + case kKelonFanMedium: + return stdAc::fanspeed_t::kMedium; + case kKelonFanMax: + return stdAc::fanspeed_t::kHigh; + case kKelonFanAuto: + default: + return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the internal A/C object state to it's stdAc::state_t equivalent. +/// @return A stdAc::state_t containing the current settings. +stdAc::state_t IRKelonAC::toCommon() const { + stdAc::state_t result{}; + result.protocol = decode_type_t::KELON; + result.model = -1; // Unused. + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.turbo = getSupercool(); + result.sleep = _.SleepEnabled ? 0 : -1; + // Not supported. + result.power = true; // N/A, AC only supports toggling it + result.swingv = stdAc::swingv_t::kAuto; // N/A, AC only supports toggling it + result.swingh = stdAc::swingh_t::kOff; // N/A, horizontal air direction can only be set by manually adjusting the flaps + result.light = true; + result.beep = true; + result.quiet = false; + result.filter = false; + result.clean = false; + result.econo = false; + result.clock = -1; + return result; +} + +/// Convert the internal settings into a human readable string. +/// @return A String. +String IRKelonAC::toString() const { + String result = ""; + result.reserve(160); // Reserve some heap for the string to reduce fragging. + result += addTempToString(getTemp(), true, false); + result += addModeToString(_.Mode, kKelonModeSmart, kKelonModeCool, kKelonModeHeat, kKelonModeDry, kKelonModeFan); + result += addFanToString(_.Fan, kKelonFanMax, kKelonFanMin, kKelonFanAuto, -1, kKelonFanMedium, kKelonFanMax); + result += addBoolToString(_.SleepEnabled, kSleepStr); + result += addIntToString(getDryGrade(), kDryGradeStr); + result += addBoolToString(getTimerEnabled(), kTimerStr); + result += addBoolToString(getSupercool(), kTurboStr); + if (getTogglePower()) { + result += addBoolToString(true, kPowerToggleStr); + } + if (getToggleSwingVertical()) { + result += addBoolToString(true, kSwingVToggleStr); + } + return result; +} diff --git a/src/ir_Kelon.h b/src/ir_Kelon.h new file mode 100644 index 000000000..454eac2f3 --- /dev/null +++ b/src/ir_Kelon.h @@ -0,0 +1,158 @@ +// Copyright 2021 Davide Depau + +/// @file +/// @brief Support for Kelan AC protocol. +/// Both sending and decoding should be functional for models of series KELON ON/OFF 9000-12000. +/// All features of the standard remote are implemented. +/// +/// @note Unsupported: +/// - Explicit on/off due to AC unit limitations +/// - Explicit swing position due to AC unit limitations +/// - Fahrenheit. + + +// Supports: +// - Brand: Kelon, Model: ON/OFF 9000-12000 + +#ifndef IR_KELON_H_ +#define IR_KELON_H_ + +#include "IRremoteESP8266.h" +#include "IRsend.h" +#include "IRutils.h" + +union KelonProtocol { + uint8_t raw[kKelonStateLength]; + + struct { + uint8_t preamble[2]; + uint8_t Fan: 2; + uint8_t PowerToggle: 1; + uint8_t SleepEnabled: 1; + uint8_t DehumidifierGrade: 3; + uint8_t SwingVToggle: 1; + uint8_t Mode: 3; + uint8_t TimerEnabled: 1; + uint8_t Temperature: 4; + uint8_t TimerHalfHour: 1; + uint8_t TimerHours: 6; + uint8_t SmartModeEnabled: 1; + uint8_t pad1: 4; + uint8_t SuperCoolEnabled1: 1; + uint8_t pad2: 2; + uint8_t SuperCoolEnabled2: 1; + }; +}; + +// Constants +const uint8_t kKelonModeHeat{0}; +const uint8_t kKelonModeSmart{1}; // (temp = 26C, but not shown) +const uint8_t kKelonModeCool{2}; +const uint8_t kKelonModeDry{3}; // (temp = 25C, but not shown) +const uint8_t kKelonModeFan{4}; // (temp = 25C, but not shown) +const uint8_t kKelonFanAuto{0}; +// Note! Kelon fan speeds are actually 0:AUTO, 1:MAX, 2:MED, 3:MIN +// Since this is insane, I decided to invert them in the public API, they are converted back in setFan/getFan +const uint8_t kKelonFanMin{1}; +const uint8_t kKelonFanMedium{2}; +const uint8_t kKelonFanMax{3}; + +const int8_t kKelonDryGradeMax{2}; +const int8_t kKelonDryGradeMin{-2}; +const uint8_t kKelonMinTemp{18}; +const uint8_t kKelonMaxTemp{32}; + + +class IRKelonAC { +public: + explicit IRKelonAC(uint16_t pin, bool inverted = false, bool use_modulation = true); + + void stateReset(); + + #if SEND_KELON + + void send(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() { return _irsend.calibrate(); } + + #endif + + + void begin(); + + void setTogglePower(bool toggle); + + bool getTogglePower() const; + + void setTemp(uint8_t degrees); + + uint8_t getTemp() const; + + void setFan(uint8_t speed); + + uint8_t getFan() const; + + void setDryGrade(int8_t grade); + + int8_t getDryGrade() const; + + void setMode(uint8_t mode); + + uint8_t getMode() const; + + void setToggleSwingVertical(bool toggle); + + bool getToggleSwingVertical() const; + + void setSleep(bool on); + + bool getSleep() const; + + void setSupercool(bool on); + + bool getSupercool() const; + + void setTimer(uint8_t mins); + + uint8_t getTimer() const; + + void setTimerEnabled(bool on); + + bool getTimerEnabled() const; + + uint8_t *getRaw(); + + void setRaw(const uint8_t new_code[]); + + static uint8_t convertMode(stdAc::opmode_t mode); + + static uint8_t convertFan(stdAc::fanspeed_t fan); + + static stdAc::opmode_t toCommonMode(uint8_t mode); + + static stdAc::fanspeed_t toCommonFanSpeed(uint8_t speed); + + stdAc::state_t toCommon() const; + + String toString() const; + +private: +#ifndef UNIT_TEST + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + KelonProtocol _; + + // Used when exiting supercool mode + uint8_t _previousMode{0}; + uint8_t _previousTemp{kKelonMinTemp}; +}; + +#endif // IR_KELON_H_ diff --git a/src/locale/defaults.h b/src/locale/defaults.h index 582e2cc93..e33ba8afa 100644 --- a/src/locale/defaults.h +++ b/src/locale/defaults.h @@ -279,6 +279,9 @@ #ifndef D_STR_ID #define D_STR_ID "Id" #endif // D_STR_ID +#ifndef D_STR_DRY_GRADE +#define D_STR_DRY_GRADE "Dry grade" +#endif // D_STR_DRY_GRADE #ifndef D_STR_AUTO #define D_STR_AUTO "Auto" @@ -802,6 +805,9 @@ #ifndef D_STR_ZEPEAL #define D_STR_ZEPEAL "ZEPEAL" #endif // D_STR_ZEPEAL +#ifndef D_STR_KELON +#define D_STR_KELON "KELON" +#endif // D_STR_KELON // IRrecvDumpV2+ #ifndef D_STR_TIMESTAMP From 7252f46d942f682704314471d07f70761c263360 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Sun, 6 Jun 2021 01:07:24 +0200 Subject: [PATCH 02/36] Allow converting signed ints to string --- src/IRutils.cpp | 25 +++++++++++++++++++++++++ src/IRutils.h | 3 +++ 2 files changed, 28 insertions(+) diff --git a/src/IRutils.cpp b/src/IRutils.cpp index 10cb1e177..fb20b6662 100644 --- a/src/IRutils.cpp +++ b/src/IRutils.cpp @@ -67,6 +67,18 @@ String uint64ToString(uint64_t input, uint8_t base) { return result; } +/// Convert a int64_t (signed long long) to a string. +/// Arduino String/toInt/Serial.print() can't handle printing 64 bit values. +/// @param[in] input The value to print +/// @param[in] base The output base. +/// @returns A String representation of the integer. +String int64ToString(int64_t input, uint8_t base) { + if (input < 0) { + return "-" + uint64ToString(-input, base); + } + return uint64ToString(input, base); +} + #ifdef ARDUINO /// Print a uint64_t/unsigned long long to the Serial port /// Serial.print() can't handle printing long longs. (uint64_t) @@ -497,6 +509,19 @@ namespace irutils { return addLabeledString(uint64ToString(value), label, precomma); } + /// Create a String with a colon separated labeled Integer suitable for + /// Humans. + /// e.g. "Foo: 23" + /// @param[in] value The value to come after the label. + /// @param[in] label The label to precede the value. + /// @param[in] precomma Should the output string start with ", " or not? + /// @return The resulting String. + String addSignedIntToString(const int16_t value, const String label, + const bool precomma) { + return addLabeledString(int64ToString(value), label, precomma); + } + + /// Generate the model string for a given Protocol/Model pair. /// @param[in] protocol The IR protocol. /// @param[in] model The model number for that protocol. diff --git a/src/IRutils.h b/src/IRutils.h index 8ce04b814..738cf787d 100644 --- a/src/IRutils.h +++ b/src/IRutils.h @@ -20,6 +20,7 @@ const uint8_t kHighNibble = 4; const uint8_t kModeBitsSize = 3; uint64_t reverseBits(uint64_t input, uint16_t nbits); String uint64ToString(uint64_t input, uint8_t base = 10); +String int64ToString(int64_t input, uint8_t base = 10); String typeToString(const decode_type_t protocol, const bool isRepeat = false); void serialPrintUint64(uint64_t input, uint8_t base = 10); @@ -49,6 +50,8 @@ namespace irutils { const bool precomma = true); String addIntToString(const uint16_t value, const String label, const bool precomma = true); + String addSignedIntToString(const int16_t value, const String label, + const bool precomma = true); String modelToStr(const decode_type_t protocol, const int16_t model); String addModelToString(const decode_type_t protocol, const int16_t model, const bool precomma = true); From 3810875accf89f6a05f8e949cc6857f939d6132c Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Sun, 6 Jun 2021 01:08:28 +0200 Subject: [PATCH 03/36] Kelon: print dry grade as signed integer --- src/ir_Kelon.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index e63a79eac..ba1f2bdac 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -22,7 +22,7 @@ using irutils::addBoolToString; using irutils::addIntToString; -using irutils::addLabeledString; +using irutils::addSignedIntToString; using irutils::addModeToString; using irutils::addFanToString; using irutils::addTempToString; @@ -438,7 +438,7 @@ String IRKelonAC::toString() const { result += addModeToString(_.Mode, kKelonModeSmart, kKelonModeCool, kKelonModeHeat, kKelonModeDry, kKelonModeFan); result += addFanToString(_.Fan, kKelonFanMax, kKelonFanMin, kKelonFanAuto, -1, kKelonFanMedium, kKelonFanMax); result += addBoolToString(_.SleepEnabled, kSleepStr); - result += addIntToString(getDryGrade(), kDryGradeStr); + result += addSignedIntToString(getDryGrade(), kDryGradeStr); result += addBoolToString(getTimerEnabled(), kTimerStr); result += addBoolToString(getSupercool(), kTurboStr); if (getTogglePower()) { From 2aa7841a133dd18852a9be3b792dc2aa24008b9b Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Sun, 6 Jun 2021 01:43:20 +0200 Subject: [PATCH 04/36] Order strings alphabetically --- src/locale/defaults.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/locale/defaults.h b/src/locale/defaults.h index e33ba8afa..004825a16 100644 --- a/src/locale/defaults.h +++ b/src/locale/defaults.h @@ -616,6 +616,9 @@ #ifndef D_STR_JVC #define D_STR_JVC "JVC" #endif // D_STR_JVC +#ifndef D_STR_KELON +#define D_STR_KELON "KELON" +#endif // D_STR_KELON #ifndef D_STR_KELVINATOR #define D_STR_KELVINATOR "KELVINATOR" #endif // D_STR_KELVINATOR @@ -805,9 +808,6 @@ #ifndef D_STR_ZEPEAL #define D_STR_ZEPEAL "ZEPEAL" #endif // D_STR_ZEPEAL -#ifndef D_STR_KELON -#define D_STR_KELON "KELON" -#endif // D_STR_KELON // IRrecvDumpV2+ #ifndef D_STR_TIMESTAMP From f8392eba158b9a5ca0a7896d05c80f59cdb6e0e8 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Sun, 6 Jun 2021 01:58:08 +0200 Subject: [PATCH 05/36] Kelon: Use uint64 to store raw data --- src/IRac.cpp | 4 ++-- src/IRremoteESP8266.h | 3 +-- src/IRsend.cpp | 10 +++++----- src/IRsend.h | 4 ++-- src/IRutils.cpp | 1 - src/ir_Kelon.cpp | 19 +++++++++---------- src/ir_Kelon.h | 6 +++--- 7 files changed, 22 insertions(+), 25 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index d1d212fd1..e6f4ea6fa 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -3334,7 +3334,7 @@ namespace IRAcUtils { #if DECODE_KELON case decode_type_t::KELON: { IRKelonAC ac(kGpioUnused); - ac.setRaw(result->state); + ac.setRaw(result->value); return ac.toString(); } #endif // DECODE_KELON @@ -3826,7 +3826,7 @@ namespace IRAcUtils { #if DECODE_KELON case decode_type_t::KELON: { IRKelonAC ac(kGpioUnused); - ac.setRaw(decode->state); + ac.setRaw(decode->value); *result = ac.toCommon(); break; } diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h index fdc992c5b..08e11767a 100644 --- a/src/IRremoteESP8266.h +++ b/src/IRremoteESP8266.h @@ -1040,8 +1040,7 @@ const uint16_t kHitachiAc424Bits = kHitachiAc424StateLength * 8; const uint16_t kInaxBits = 24; const uint16_t kInaxMinRepeat = kSingleRepeat; const uint16_t kJvcBits = 16; -const uint16_t kKelonStateLength = 6; -const uint16_t kKelonBits = kKelonStateLength * 8; +const uint16_t kKelonBits = 48; const uint16_t kKelvinatorStateLength = 16; const uint16_t kKelvinatorBits = kKelvinatorStateLength * 8; const uint16_t kKelvinatorDefaultRepeat = kNoRepeat; diff --git a/src/IRsend.cpp b/src/IRsend.cpp index febddeb65..b14979da0 100644 --- a/src/IRsend.cpp +++ b/src/IRsend.cpp @@ -869,6 +869,11 @@ bool IRsend::send(const decode_type_t type, const uint64_t data, sendJVC(data, nbits, min_repeat); break; #endif +#if SEND_KELON + case KELON: + sendKelon(data, nbits, min_repeat); + break; +#endif // SEND_KELON #if SEND_LASERTAG case LASERTAG: sendLasertag(data, nbits, min_repeat); @@ -1182,11 +1187,6 @@ bool IRsend::send(const decode_type_t type, const uint8_t *state, sendHitachiAc424(state, nbytes); break; #endif // SEND_HITACHI_AC424 -#if SEND_KELON - case KELON: - sendKelon(state, nbytes); - break; -#endif // SEND_KELON #if SEND_KELVINATOR case KELVINATOR: sendKelvinator(state, nbytes); diff --git a/src/IRsend.h b/src/IRsend.h index 53b6058e0..a936da0f7 100644 --- a/src/IRsend.h +++ b/src/IRsend.h @@ -718,8 +718,8 @@ class IRsend { const uint16_t repeat = kNoRepeat); #endif // SEND_TEKNOPOINT #if SEND_KELON - void sendKelon(const unsigned char data[], const uint16_t nbytes = kKelonStateLength, - const uint16_t repeat = kNoRepeat); + void sendKelon(const uint64_t data, const uint16_t nbits = kKelonBits, + const uint16_t repeat = kNoRepeat); #endif // SEND_KELON protected: diff --git a/src/IRutils.cpp b/src/IRutils.cpp index fb20b6662..b894b67e9 100644 --- a/src/IRutils.cpp +++ b/src/IRutils.cpp @@ -164,7 +164,6 @@ bool hasACState(const decode_type_t protocol) { case HITACHI_AC3: case HITACHI_AC344: case HITACHI_AC424: - case KELON: case KELVINATOR: case MIRAGE: case MITSUBISHI136: diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index ba1f2bdac..19afa9a29 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -40,16 +40,15 @@ const uint16_t kKelonFreq = 38000; /// Send a Kelon message. /// Status: Beta / Should be working. -/// @param[in] data The message to be sent. -/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] data The data to be transmitted. +/// @param[in] nbits Nr. of bits of data to be sent. /// @param[in] repeat The number of times the command is to be repeated. -void IRsend::sendKelon(const unsigned char data[], const uint16_t nbytes, - const uint16_t repeat) { +void IRsend::sendKelon(const uint64_t data, const uint16_t nbits, const uint16_t repeat) { sendGeneric(kKelonHdrMark, kKelonHdrSpace, kKelonBitMark, kKelonOneSpace, kKelonBitMark, kKelonZeroSpace, kKelonBitMark, kKelonGap, - data, nbytes, kKelonFreq, false, // LSB First. + data, nbits, kKelonFreq, false, // LSB First. repeat, 50); } @@ -100,7 +99,7 @@ IRKelonAC::IRKelonAC(const uint16_t pin, const bool inverted, const bool use_mod /// Reset the internals of the object to a known good state. void IRKelonAC::stateReset() { - for (unsigned char &i : _.raw) i = 0x0; + _.raw = 0L; _.preamble[0] = 0b10000011; _.preamble[1] = 0b00000110; } @@ -110,7 +109,7 @@ void IRKelonAC::stateReset() { /// Send the current internal state as an IR message. /// @param[in] repeat Nr. of times the message will be repeated. void IRKelonAC::send(const uint16_t repeat) { - _irsend.sendKelon(getRaw(), kKelonStateLength, repeat); + _irsend.sendKelon(getRaw(), kKelonBits, repeat); // Reset toggle flags _.PowerToggle = false; @@ -319,14 +318,14 @@ bool IRKelonAC::getTimerEnabled() const { /// Get the raw state of the object, suitable to be sent with the appropriate /// IRsend object method. /// @return A PTR to the internal state. -uint8_t *IRKelonAC::getRaw() { +uint64_t IRKelonAC::getRaw() const { return _.raw; } /// Set the raw state of the object. /// @param[in] new_code The raw state from the native IR message. -void IRKelonAC::setRaw(const uint8_t *new_code) { - std::memcpy(_.raw, new_code, kKelonStateLength); +void IRKelonAC::setRaw(const uint64_t new_code) { + _.raw = new_code; } /// Convert a standard A/C mode (stdAc::opmode_t) into it a native mode. diff --git a/src/ir_Kelon.h b/src/ir_Kelon.h index 454eac2f3..411887322 100644 --- a/src/ir_Kelon.h +++ b/src/ir_Kelon.h @@ -22,7 +22,7 @@ #include "IRutils.h" union KelonProtocol { - uint8_t raw[kKelonStateLength]; + uint64_t raw; struct { uint8_t preamble[2]; @@ -124,9 +124,9 @@ class IRKelonAC { bool getTimerEnabled() const; - uint8_t *getRaw(); + uint64_t getRaw() const; - void setRaw(const uint8_t new_code[]); + void setRaw(const uint64_t new_code); static uint8_t convertMode(stdAc::opmode_t mode); From 9e2b1585246f4268c7d7d8717840b22ce064b77b Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Sun, 6 Jun 2021 02:09:13 +0200 Subject: [PATCH 06/36] Kelon: Fix issues reported by CI --- src/IRac.cpp | 1 + src/ir_Kelon.h | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index e6f4ea6fa..097536dc6 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -1276,6 +1276,7 @@ void IRac::kelon(IRKelonAC *ac, ac->setTemp(static_cast(degrees)); ac->setSleep(sleep); ac->setSupercool(superCool); + ac->setDryGrade(dryGrade); ac->setTogglePower(togglePower); ac->setToggleSwingVertical(toggleSwing); diff --git a/src/ir_Kelon.h b/src/ir_Kelon.h index 411887322..6c73e42a7 100644 --- a/src/ir_Kelon.h +++ b/src/ir_Kelon.h @@ -9,14 +9,16 @@ /// - Explicit on/off due to AC unit limitations /// - Explicit swing position due to AC unit limitations /// - Fahrenheit. - - // Supports: -// - Brand: Kelon, Model: ON/OFF 9000-12000 +// Brand: Kelon, Model: ON/OFF 9000-12000 #ifndef IR_KELON_H_ #define IR_KELON_H_ +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + #include "IRremoteESP8266.h" #include "IRsend.h" #include "IRutils.h" From 6b87f7bed4854152f1ff0f02f675f88937d0818d Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Sun, 6 Jun 2021 02:38:45 +0200 Subject: [PATCH 07/36] Kelon: Add defaultBits --- src/IRsend.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/IRsend.cpp b/src/IRsend.cpp index b14979da0..f552000aa 100644 --- a/src/IRsend.cpp +++ b/src/IRsend.cpp @@ -658,6 +658,7 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) { case SANYO_LC7461: return kSanyoLC7461Bits; // 42 case GOODWEATHER: + case KELON: case MIDEA: case PANASONIC: return 48; From 217e3158cecd4a0eaab87ef5d992215ef7048325 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Tue, 8 Jun 2021 23:02:57 +0200 Subject: [PATCH 08/36] Kelon: Fix incorrect timer operator order --- src/ir_Kelon.cpp | 10 +++++----- src/ir_Kelon.h | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index 19afa9a29..b8b830c5a 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -277,8 +277,8 @@ bool IRKelonAC::getSupercool() const { /// Set the timer time and enable it. Timer is an off timer if the unit is on, it is an on timer if the unit is off. /// @param[in] mins Timer minutes (only multiples of 30m are supported for < 10h, then only multiples of 60m) -void IRKelonAC::setTimer(uint8_t mins) { - const uint8_t minutes = std::min(static_cast(mins), 24 * 60); +void IRKelonAC::setTimer(uint16_t mins) { + const uint16_t minutes = std::min(static_cast(mins), 24 * 60); if (minutes / 60 >= 10) { uint8_t hours = minutes / 60 + 10; @@ -295,11 +295,11 @@ void IRKelonAC::setTimer(uint8_t mins) { /// Get the set timer. Timer set time is deleted once the command is sent, so calling this after send() will return 0. /// The AC unit will continue keeping track of the remaining time unless it is later disabled. /// @return The timer set minutes -uint8_t IRKelonAC::getTimer() const { +uint16_t IRKelonAC::getTimer() const { if (_.TimerHours >= 10) { - return (((_.TimerHours << 1) | _.TimerHalfHour) - 10) * 60; + return ((uint16_t) ((_.TimerHours << 1) | _.TimerHalfHour) - 10) * 60; } - return _.TimerHours * 60 + _.TimerHalfHour ? 30 : 0; + return (((uint16_t) _.TimerHours) * 60) + (_.TimerHalfHour ? 30 : 0); } /// Enable or disable the timer. Note that in order to enable the timer the minutes must be set with setTimer(). diff --git a/src/ir_Kelon.h b/src/ir_Kelon.h index 6c73e42a7..b19b7b180 100644 --- a/src/ir_Kelon.h +++ b/src/ir_Kelon.h @@ -118,9 +118,9 @@ class IRKelonAC { bool getSupercool() const; - void setTimer(uint8_t mins); + void setTimer(uint16_t mins); - uint8_t getTimer() const; + uint16_t getTimer() const; void setTimerEnabled(bool on); From 67fae81c9c0797792a686ad99b1119d0c9033deb Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Tue, 8 Jun 2021 23:03:18 +0200 Subject: [PATCH 09/36] Kelon: Add timer time to toString() --- src/ir_Kelon.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index b8b830c5a..4ec65f48a 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -26,6 +26,8 @@ using irutils::addSignedIntToString; using irutils::addModeToString; using irutils::addFanToString; using irutils::addTempToString; +using irutils::addLabeledString; +using irutils::minsToString; // Constants const uint16_t kKelonHdrMark = 9000; @@ -438,7 +440,9 @@ String IRKelonAC::toString() const { result += addFanToString(_.Fan, kKelonFanMax, kKelonFanMin, kKelonFanAuto, -1, kKelonFanMedium, kKelonFanMax); result += addBoolToString(_.SleepEnabled, kSleepStr); result += addSignedIntToString(getDryGrade(), kDryGradeStr); - result += addBoolToString(getTimerEnabled(), kTimerStr); + result += addLabeledString(getTimerEnabled() + ? (getTimer() > 0 ? minsToString(getTimer()) : kOnStr) + : kOffStr,kTimerStr); result += addBoolToString(getSupercool(), kTurboStr); if (getTogglePower()) { result += addBoolToString(true, kPowerToggleStr); From 583f43b399ab1bc239ed8989eda7e6994f703c48 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Fri, 11 Jun 2021 23:14:09 +0200 Subject: [PATCH 10/36] Kelon: Do not expect footerspace --- src/ir_Kelon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index 4ec65f48a..92d1036f7 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -77,7 +77,7 @@ bool IRrecv::decodeKelon(decode_results *results, uint16_t offset, kKelonHdrMark, kKelonHdrSpace, kKelonBitMark, kKelonOneSpace, kKelonBitMark, kKelonZeroSpace, - kKelonBitMark, kKelonBitMark, false, + kKelonBitMark, 0, false, _tolerance, 0, false); // Data bits + 2 bits header + 1 bit footer = 99 bits From 07e8a966bcf2d9010021c8715364a9796e24bbd8 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Sat, 12 Jun 2021 00:04:46 +0200 Subject: [PATCH 11/36] Kelon: Fix incorrect previous temp/mode revert --- src/ir_Kelon.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index 92d1036f7..d0ddf79d3 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -200,7 +200,7 @@ int8_t IRKelonAC::getDryGrade() const { /// Set the desired operation mode. /// @param[in] mode The desired operation mode. void IRKelonAC::setMode(const uint8_t mode) { - if (_previousMode == kKelonModeSmart || _previousMode == kKelonModeFan || _.SuperCoolEnabled1) { + if (_.Mode == kKelonModeSmart || _.Mode == kKelonModeFan || _.Mode == kKelonModeDry || _.SuperCoolEnabled1) { _.Temperature = _previousTemp; } _.SuperCoolEnabled1 = false; @@ -209,13 +209,13 @@ void IRKelonAC::setMode(const uint8_t mode) { switch (mode) { case kKelonModeSmart: - _.Temperature = 26 - kKelonMinTemp; // Do not save _previousTemp + setTemp(26); _.SmartModeEnabled = true; _.Mode = mode; break; case kKelonModeDry: case kKelonModeFan: - _.Temperature = 25 - kKelonMinTemp; // Do not save _previousTemp + setTemp(25); _.Mode = mode; //fallthrough case kKelonModeCool: @@ -260,8 +260,6 @@ bool IRKelonAC::getSleep() const { /// Control the current super cool mode setting. /// @param[in] on The desired setting. void IRKelonAC::setSupercool(const bool on) { - _.SuperCoolEnabled1 = on; - _.SuperCoolEnabled2 = on; if (on) { setTemp(kKelonMinTemp); setMode(kKelonModeCool); @@ -269,6 +267,8 @@ void IRKelonAC::setSupercool(const bool on) { _.Temperature = _previousTemp; setMode(_previousMode); } + _.SuperCoolEnabled1 = on; + _.SuperCoolEnabled2 = on; } /// Is the super cool mode setting on? From 31f17935eaad48177649ae36fedf6d9c473185be Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Sat, 12 Jun 2021 00:08:30 +0200 Subject: [PATCH 12/36] Kelon: Supercool should set/revert fan speed max --- src/ir_Kelon.cpp | 15 +++++++++++---- src/ir_Kelon.h | 1 + 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index d0ddf79d3..974325446 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -161,6 +161,7 @@ uint8_t IRKelonAC::getTemp() const { void IRKelonAC::setFan(const uint8_t speed) { uint8_t fan = std::min(speed, kKelonFanMax); + _previousFan = _.Fan; // Note: Kelon fan speeds are backwards! This code maps the range 0,1:3 to 0,3:1 to save the API's user's sanity. _.Fan = ((static_cast(fan) - 4) * -1) % 4; } @@ -200,11 +201,16 @@ int8_t IRKelonAC::getDryGrade() const { /// Set the desired operation mode. /// @param[in] mode The desired operation mode. void IRKelonAC::setMode(const uint8_t mode) { - if (_.Mode == kKelonModeSmart || _.Mode == kKelonModeFan || _.Mode == kKelonModeDry || _.SuperCoolEnabled1) { + if (_.Mode == kKelonModeSmart || _.Mode == kKelonModeFan || _.Mode == kKelonModeDry) { _.Temperature = _previousTemp; } - _.SuperCoolEnabled1 = false; - _.SuperCoolEnabled2 = false; + if (_.SuperCoolEnabled1) { + // Cancel supercool + _.SuperCoolEnabled1 = false; + _.SuperCoolEnabled2 = false; + _.Temperature = _previousTemp; + _.Fan = _previousFan; + } _previousMode = _.Mode; switch (mode) { @@ -263,8 +269,9 @@ void IRKelonAC::setSupercool(const bool on) { if (on) { setTemp(kKelonMinTemp); setMode(kKelonModeCool); + setFan(kKelonFanMax); } else { - _.Temperature = _previousTemp; + // All reverts to previous are handled by setMode as needed setMode(_previousMode); } _.SuperCoolEnabled1 = on; diff --git a/src/ir_Kelon.h b/src/ir_Kelon.h index b19b7b180..26be2e6c2 100644 --- a/src/ir_Kelon.h +++ b/src/ir_Kelon.h @@ -155,6 +155,7 @@ class IRKelonAC { // Used when exiting supercool mode uint8_t _previousMode{0}; uint8_t _previousTemp{kKelonMinTemp}; + uint8_t _previousFan{kKelonFanAuto}; }; #endif // IR_KELON_H_ From 7a289adfde24cc7aa31b09f45cd6dcdd3b1e43dd Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Sat, 12 Jun 2021 00:26:32 +0200 Subject: [PATCH 13/36] Kelon: toCommon(): do not access _ directly --- src/ir_Kelon.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index 974325446..1e6fd9b2c 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -417,12 +417,12 @@ stdAc::state_t IRKelonAC::toCommon() const { stdAc::state_t result{}; result.protocol = decode_type_t::KELON; result.model = -1; // Unused. - result.mode = toCommonMode(_.Mode); + result.mode = toCommonMode(getMode()); result.celsius = true; result.degrees = getTemp(); - result.fanspeed = toCommonFanSpeed(_.Fan); + result.fanspeed = toCommonFanSpeed(getFan()); result.turbo = getSupercool(); - result.sleep = _.SleepEnabled ? 0 : -1; + result.sleep = getSleep() ? 0 : -1; // Not supported. result.power = true; // N/A, AC only supports toggling it result.swingv = stdAc::swingv_t::kAuto; // N/A, AC only supports toggling it From df75050d6c9e5d57bf90146dcd27c9d9b42ff59b Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Sat, 12 Jun 2021 00:31:09 +0200 Subject: [PATCH 14/36] Kelon: Add unit tests --- test/ir_Kelon_test.cpp | 401 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 401 insertions(+) create mode 100644 test/ir_Kelon_test.cpp diff --git a/test/ir_Kelon_test.cpp b/test/ir_Kelon_test.cpp new file mode 100644 index 000000000..858a631ab --- /dev/null +++ b/test/ir_Kelon_test.cpp @@ -0,0 +1,401 @@ +// Copyright 2021 Davide Depau + +#include "IRac.h" +#include "IRrecv.h" +#include "IRrecv_test.h" +#include "IRsend.h" +#include "IRsend_test.h" +#include "gtest/gtest.h" + +// Tests for sendKelon(). + +// Test sending typical data only. +TEST(TestSendKelon, SendDataOnly) { + IRsendTest irsend(4); + irsend.begin(); + + // Temp: 26C, Mode: 2 (Cool), Fan: 0 (Auto), Sleep: Off, Dry grade: 0, Timer: Off, Turbo: Off + irsend.reset(); + irsend.sendKelon(0x82000683); + EXPECT_EQ( + "f38000d50" + "m9000s4600m560s1680m560s1680m560s600m560s600m560s600m560s600" + "m560s600m560s1680m560s600m560s1680m560s1680m560s600m560s600m560s600" + "m560s600m560s600m560s600m560s600m560s600m560s600m560s600m560s600" + "m560s600m560s600m560s600m560s1680m560s600m560s600m560s600m560s600" + "m560s600m560s1680m560s600m560s600m560s600m560s600m560s600m560s600" + "m560s600m560s600m560s600m560s600m560s600m560s600m560s600m560s600" + "m560s600m560s600m560s100000", + irsend.outputStr() + ); + + // Temp: 18C, Mode: 2 (Cool), Fan: 1 (Low), Sleep: Off, Dry grade: 0, Timer: Off, Turbo: On + irsend.reset(); + irsend.sendKelon(0x900002010683); + EXPECT_EQ( + "f38000d50" + "m9000s4600m560s1680m560s1680m560s600m560s600m560s600m560s600m560s600" + "m560s1680m560s600m560s1680m560s1680m560s600m560s600m560s600" + "m560s600m560s600m560s1680m560s600m560s600m560s600m560s600" + "m560s600m560s600m560s600m560s600m560s1680m560s600m560s600m560s600" + "m560s600m560s600m560s600m560s600m560s600m560s600m560s600" + "m560s600m560s600m560s600m560s600m560s600m560s600m560s600" + "m560s600m560s1680m560s600m560s600m560s1680m560s100000", + irsend.outputStr() + ); + + // Temp: 23C, Mode: 0 (Heat), Fan: 0 (Auto), Sleep: Off, Dry grade: 0, Timer: Off, Turbo: Off, Power Toggle: On + irsend.reset(); + irsend.sendKelon(0x50040683); + EXPECT_EQ( + "f38000d50" + "m9000s4600m560s1680m560s1680m560s600m560s600m560s600m560s600" + "m560s600m560s1680m560s600m560s1680m560s1680m560s600m560s600" + "m560s600m560s600m560s600m560s600m560s600m560s1680m560s600m560s600" + "m560s600m560s600m560s600m560s600m560s600m560s600m560s600m560s1680" + "m560s600m560s1680m560s600m560s600m560s600m560s600m560s600m560s600" + "m560s600m560s600m560s600m560s600m560s600m560s600m560s600m560s600" + "m560s600m560s600m560s600m560s100000", + irsend.outputStr() + ); + + // Temp: 26C, Mode: 2 (Cool), Fan: 0 (Auto), Sleep: Off, Dry grade: 0, Timer: On (9.5h), Turbo: + irsend.reset(); + irsend.sendKelon(0x138A000683); + EXPECT_EQ( + "f38000d50" + "m9000s4600m560s1680m560s1680m560s600m560s600m560s600" + "m560s600m560s600m560s1680m560s600m560s1680m560s1680m560s600" + "m560s600m560s600m560s600m560s600m560s600m560s600m560s600" + "m560s600m560s600m560s600m560s600m560s600m560s600m560s1680" + "m560s600m560s1680m560s600m560s600m560s600m560s1680m560s1680" + "m560s1680m560s600m560s600m560s1680m560s600m560s600m560s600" + "m560s600m560s600m560s600m560s600m560s600m560s600m560s600" + "m560s600m560s100000", + irsend.outputStr() + ); + + // Temp: 26C, Mode: 2 (Cool), Fan: 0 (Auto), Sleep: Off, Dry grade: 0, Timer: On (15h), Turbo: Off: + irsend.reset(); + irsend.sendKelon(0x198A000683); + EXPECT_EQ( + "f38000d50" + "m9000s4600m560s1680m560s1680m560s600m560s600m560s600" + "m560s600m560s600m560s1680m560s600m560s1680m560s1680" + "m560s600m560s600m560s600m560s600m560s600m560s600m560s600" + "m560s600m560s600m560s600m560s600m560s600m560s600m560s600" + "m560s1680m560s600m560s1680m560s600m560s600m560s600m560s1680" + "m560s1680m560s600m560s600m560s1680m560s1680m560s600" + "m560s600m560s600m560s600m560s600m560s600m560s600m560s600" + "m560s600m560s600m560s600m560s100000", + irsend.outputStr() + ); +} + +// Tests for decodeKelon(). +// Decode normal Kelon messages. +TEST(TestDecodeKelon, Timer12HSmartMode) { + IRsendTest irsend(0); + IRrecv irrecv(0); + irsend.begin(); + + irsend.reset(); + irsend.sendKelon(0x1679030683); + irsend.makeDecodeResult(); + EXPECT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(KELON, irsend.capture.decode_type); + EXPECT_EQ(kKelonBits, irsend.capture.bits); + EXPECT_EQ( + "Temp: 25C, Mode: 1 (Auto), Fan: 3 (High), Sleep: Off, Dry grade: 0, Timer: 12:00, Turbo: Off", + IRAcUtils::resultAcToString(&irsend.capture) + ); + stdAc::state_t result, prev; + ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); +} + +TEST(TestDecodeKelon, Timer5_5hSuperCoolMode) { + IRsendTest irsend(0); + IRrecv irrecv(0); + irsend.begin(); + + irsend.reset(); + irsend.sendKelon(0x100B0A010683); + irsend.makeDecodeResult(); + EXPECT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(KELON, irsend.capture.decode_type); + EXPECT_EQ(kKelonBits, irsend.capture.bits); + EXPECT_EQ( + "Temp: 18C, Mode: 2 (Cool), Fan: 1 (Low), Sleep: Off, Dry grade: 0, Timer: 05:30, Turbo: On", + IRAcUtils::resultAcToString(&irsend.capture) + ); + stdAc::state_t result, prev; + ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); +} + +TEST(TestDecodeKelon, ChangeSettingsWithTimerSetHeatMode) { + IRsendTest irsend(0); + IRrecv irrecv(0); + irsend.begin(); + + irsend.reset(); + irsend.sendKelon(0x58000683); + irsend.makeDecodeResult(); + EXPECT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(KELON, irsend.capture.decode_type); + EXPECT_EQ(kKelonBits, irsend.capture.bits); + EXPECT_EQ( + "Temp: 23C, Mode: 0 (Heat), Fan: 0 (Auto), Sleep: Off, Dry grade: 0, Timer: On, Turbo: Off", + IRAcUtils::resultAcToString(&irsend.capture) + ); + stdAc::state_t result, prev; + ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); +} + +TEST(TestDecodeKelon, TestPowerToggleDryMode) { + IRsendTest irsend(0); + IRrecv irrecv(0); + irsend.begin(); + + irsend.reset(); + irsend.sendKelon(0x83040683); + irsend.makeDecodeResult(); + EXPECT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(KELON, irsend.capture.decode_type); + EXPECT_EQ(kKelonBits, irsend.capture.bits); + EXPECT_EQ( + "Temp: 26C, Mode: 3 (Dry), Fan: 0 (Auto), Sleep: Off, Dry grade: 0, Timer: Off, Turbo: Off, Power Toggle: On", + IRAcUtils::resultAcToString(&irsend.capture) + ); + stdAc::state_t result, prev; + ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); +} + +TEST(TestDecodeKelon, TestSwingToggleDryMode) { + IRsendTest irsend(0); + IRrecv irrecv(0); + irsend.begin(); + + irsend.reset(); + irsend.sendKelon(0x83800683); + irsend.makeDecodeResult(); + EXPECT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(KELON, irsend.capture.decode_type); + EXPECT_EQ(kKelonBits, irsend.capture.bits); + EXPECT_EQ( + "Temp: 26C, Mode: 3 (Dry), Fan: 0 (Auto), Sleep: Off, Dry grade: 0, Timer: Off, Turbo: Off, Swing(V) Toggle: On", + IRAcUtils::resultAcToString(&irsend.capture) + ); + stdAc::state_t result, prev; + ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); +} + +TEST(TestDecodeKelon, TestDryGradeNegativeValue) { + IRsendTest irsend(0); + IRrecv irrecv(0); + irsend.begin(); + + irsend.reset(); + irsend.sendKelon(0x83600683); + irsend.makeDecodeResult(); + EXPECT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(KELON, irsend.capture.decode_type); + EXPECT_EQ(kKelonBits, irsend.capture.bits); + EXPECT_EQ( + "Temp: 26C, Mode: 3 (Dry), Fan: 0 (Auto), Sleep: Off, Dry grade: -2, Timer: Off, Turbo: Off", + IRAcUtils::resultAcToString(&irsend.capture) + ); + stdAc::state_t result, prev; + ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); +} + +TEST(TestIRKelonClass, SetAndGetRaw) { + uint64_t rawData = 0x100B0A010683; + IRKelonAC ac(kGpioUnused); + ac.setRaw(rawData); + EXPECT_EQ(rawData, ac.getRaw()); +} + +TEST(TestIRKelonClass, SetAndGetTemp) { + IRKelonAC ac(kGpioUnused); + + for (uint8_t temp = 18; temp <= 32; temp++) { + ac.setTemp(temp); + EXPECT_EQ(temp, ac.getTemp()); + } + + // Check bounds + ac.setTemp(kKelonMinTemp - 1); + EXPECT_EQ(kKelonMinTemp, ac.getTemp()); + ac.setTemp(kKelonMaxTemp + 1); + EXPECT_EQ(kKelonMaxTemp, ac.getTemp()); +} + +TEST(TestIRKelonClass, SetAndGetTimer) { + IRKelonAC ac(kGpioUnused); + + // 0.5h to 10h timers have a granularity of 30 minutes + for (uint16_t minutes = 0; minutes <= 60 * 10; minutes += 30) { + ac.setTimer(minutes); + EXPECT_EQ(minutes, ac.getTimer()); + } + // 10h to 24h timers have a granularity of 1h + for (uint16_t minutes = 600; minutes <= 60 * 24; minutes += 60) { + ac.setTimer(minutes); + EXPECT_EQ(minutes, ac.getTimer()); + } + // For 10h to 24h timers, we expect the minutes to be floored down to the hour + for (uint16_t minutes = 600 + 30; minutes <= 60 * 24; minutes += 60) { + ac.setTimer(minutes); + EXPECT_EQ(minutes - 30, ac.getTimer()); + } +} + +TEST(TestIRKelonClass, CheckToggles) { + IRKelonAC ac(kGpioUnused); + + ac.setTogglePower(true); + EXPECT_TRUE(ac.getTogglePower()); + ac.send(); + EXPECT_FALSE(ac.getTogglePower()); + + ac.setToggleSwingVertical(true); + EXPECT_TRUE(ac.getToggleSwingVertical()); + ac.send(); + EXPECT_FALSE(ac.getToggleSwingVertical()); + + // Known state with a power toggle + ac.setRaw(0x62040683); + EXPECT_TRUE(ac.getTogglePower()); + + // Known state with a swing toggle + ac.setRaw(0x62800683); + EXPECT_TRUE(ac.getToggleSwingVertical()); +} + +TEST(TestIRKelonClass, SetAndGetMode) { + IRKelonAC ac(kGpioUnused); + + uint8_t initial_temp = 20; + ac.setMode(kKelonModeHeat); + ac.setTemp(initial_temp); + + ac.setMode(kKelonModeCool); + EXPECT_EQ(kKelonModeCool, ac.getMode()); + EXPECT_EQ(initial_temp, ac.getTemp()); + + ac.setMode(kKelonModeHeat); + EXPECT_EQ(kKelonModeHeat, ac.getMode()); + EXPECT_EQ(initial_temp, ac.getTemp()); + + ac.setMode(kKelonModeDry); + EXPECT_EQ(kKelonModeDry, ac.getMode()); + EXPECT_EQ(25, ac.getTemp()); + + // Should revert back to previous temp + ac.setMode(kKelonModeHeat); + EXPECT_EQ(kKelonModeHeat, ac.getMode()); + EXPECT_EQ(initial_temp, ac.getTemp()); + + ac.setMode(kKelonModeFan); + EXPECT_EQ(kKelonModeFan, ac.getMode()); + EXPECT_EQ(25, ac.getTemp()); + + // Should revert back to previous temp + ac.setMode(kKelonModeHeat); + EXPECT_EQ(kKelonModeHeat, ac.getMode()); + EXPECT_EQ(initial_temp, ac.getTemp()); + + ac.setMode(kKelonModeSmart); + EXPECT_EQ(kKelonModeSmart, ac.getMode()); + EXPECT_EQ(26, ac.getTemp()); + + // Should revert back to previous temp + ac.setMode(kKelonModeHeat); + EXPECT_EQ(kKelonModeHeat, ac.getMode()); + EXPECT_EQ(initial_temp, ac.getTemp()); +} + +TEST(TestIRKelonClass, CheckSuperCoolMode) { + IRKelonAC ac(kGpioUnused); + + uint8_t initial_temp = 20; + uint8_t initial_fan = kKelonFanMin; + ac.setMode(kKelonModeHeat); + ac.setTemp(initial_temp); + ac.setFan(initial_fan); + + ac.setSupercool(true); + EXPECT_TRUE(ac.getSupercool()); + EXPECT_EQ(kKelonModeCool, ac.getMode()); + EXPECT_EQ(kKelonMinTemp, ac.getTemp()); + EXPECT_EQ(kKelonFanMax, ac.getFan()); + + // Should revert back to previous temp and mode + ac.setSupercool(false); + EXPECT_EQ(kKelonModeHeat, ac.getMode()); + EXPECT_EQ(initial_temp, ac.getTemp()); + EXPECT_EQ(initial_fan, ac.getFan()); + + ac.setSupercool(true); + EXPECT_TRUE(ac.getSupercool()); + EXPECT_EQ(kKelonModeCool, ac.getMode()); + EXPECT_EQ(kKelonMinTemp, ac.getTemp()); + EXPECT_EQ(kKelonFanMax, ac.getFan()); + + // Setting any mode should cancel supercool + ac.setMode(kKelonModeHeat); + EXPECT_EQ(kKelonModeHeat, ac.getMode()); + EXPECT_EQ(initial_temp, ac.getTemp()); + EXPECT_EQ(initial_fan, ac.getFan()); +} + +TEST(TestIRKelonClass, SetAndGetDryGrade) { + IRKelonAC ac(kGpioUnused); + + for (int i = -2; i <= 2; i++) { + ac.setDryGrade(i); + EXPECT_EQ(i, ac.getDryGrade()); + } + // Check bounds + ac.setDryGrade(3); + EXPECT_EQ(2, ac.getDryGrade()); + ac.setDryGrade(-3); + EXPECT_EQ(-2, ac.getDryGrade()); +} + +TEST(TestIRKelonClass, toCommon) { + IRKelonAC ac(kGpioUnused); + + ac.setSleep(false); + ac.setTemp(23); + ac.setMode(kKelonModeHeat); + ac.setFan(kAmcorFanMed); + auto common = ac.toCommon(); + + EXPECT_EQ(decode_type_t::KELON, common.protocol); + EXPECT_EQ(23, common.degrees); + EXPECT_TRUE(common.celsius); + EXPECT_EQ(stdAc::fanspeed_t::kMedium, common.fanspeed); + EXPECT_FALSE(common.turbo); + EXPECT_EQ(-1, common.sleep); + + ac.setSleep(true); + ac.setSupercool(true); + + common = ac.toCommon(); + EXPECT_EQ(decode_type_t::KELON, common.protocol); + EXPECT_EQ(kKelonMinTemp, common.degrees); + EXPECT_TRUE(common.celsius); + EXPECT_EQ(stdAc::fanspeed_t::kHigh, common.fanspeed); + EXPECT_TRUE(common.turbo); + EXPECT_EQ(0, common.sleep); +} + +TEST(TestUtils, Housekeeping) { + ASSERT_EQ("KELON", typeToString(decode_type_t::KELON)); + ASSERT_EQ(decode_type_t::KELON, strToDecodeType("KELON")); + ASSERT_FALSE(hasACState(decode_type_t::KELON)); + ASSERT_TRUE(IRac::isProtocolSupported(decode_type_t::KELON)); + ASSERT_EQ(kKelonBits, IRsend::defaultBits(decode_type_t::KELON)); + ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::KELON)); +} \ No newline at end of file From f45f938959563bfd63fdb17ab75e53a20f304e2b Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Sat, 12 Jun 2021 00:37:43 +0200 Subject: [PATCH 15/36] Kelon: Add utility method to ensure power on/off --- src/ir_Kelon.cpp | 25 +++++++++++++++++++++++++ src/ir_Kelon.h | 6 ++++++ 2 files changed, 31 insertions(+) diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index 1e6fd9b2c..cd88aa048 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -122,6 +122,31 @@ void IRKelonAC::send(const uint16_t repeat) { _.TimerHalfHour = 0; } +/// Ensures the AC is on or off by exploiting the fact that setting +/// it to "smart" will always turn it on if it's off. +/// This method will send 2 commands to the AC to do the trick +/// @param[in] on Whether to ensure the AC is on or off +void IRKelonAC::ensurePower(bool on) { + // Try to avoid turning on the compressor for this operation. + // "Dry grade", when in "smart" mode, acts as a temperature offset that + // the user can configure if they feel too cold or too hot. By setting it + // to +2 we're setting the temperature to ~28°C, which will effectively + // set the AC to fan mode. + int8_t previousDry = getDryGrade(); + setDryGrade(2); + setMode(kKelonModeSmart); + send(); + + setDryGrade(previousDry); + setMode(_previousMode); + + // Now we're sure it's on. Turn it back off. + if (!on) { + setTogglePower(true); + } + send(); +} + #endif // SEND_KELON /// Set up hardware to be able to send a message. diff --git a/src/ir_Kelon.h b/src/ir_Kelon.h index 26be2e6c2..0784c2d6c 100644 --- a/src/ir_Kelon.h +++ b/src/ir_Kelon.h @@ -81,6 +81,12 @@ class IRKelonAC { /// Only ever needs to be run once per object instantiation, if at all. int8_t calibrate() { return _irsend.calibrate(); } + /// Since the AC does not support actually setting the power state to a known + /// value, this utility allow ensuring the AC is on or off by exploiting + /// the fact that the AC, according to the user manual, will always turn on + /// when setting it to "smart" or "super" mode. + void ensurePower(bool on); + #endif From 1171ba6e52bc8bec873588b94fcfcbf46324c754 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Sat, 12 Jun 2021 17:59:52 +0200 Subject: [PATCH 16/36] Kelon: Fix Doxygen --- src/ir_Kelon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index cd88aa048..5a7937836 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -382,7 +382,7 @@ uint8_t IRKelonAC::convertMode(const stdAc::opmode_t mode) { } /// Convert a standard A/C fan speed (stdAc::fanspeed_t) into it a native speed. -/// @param[in] mode A stdAc::fanspeed_t fan speed +/// @param[in] fan A stdAc::fanspeed_t fan speed /// @return The native speed equivalent. uint8_t IRKelonAC::convertFan(stdAc::fanspeed_t fan) { switch (fan) { From ff8268894f58dbd243c7078a41a0bc87de3562f7 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Mon, 14 Jun 2021 20:58:40 +0200 Subject: [PATCH 17/36] Kelon: Resolve requested changes and lint issues --- src/IRac.cpp | 14 +++--- src/IRac.h | 2 +- src/IRrecv.cpp | 2 +- src/IRtext.cpp | 2 +- src/IRutils.cpp | 25 ---------- src/IRutils.h | 3 -- src/ir_Kelon.cpp | 102 ++++++++++++++++++----------------------- src/ir_Kelon.h | 100 ++++++++++++++++++++-------------------- src/locale/defaults.h | 2 +- test/ir_Kelon_test.cpp | 44 +++++++++--------- 10 files changed, 127 insertions(+), 169 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index 097536dc6..d57bae832 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -1256,7 +1256,7 @@ void IRac::hitachi424(IRHitachiAc424 *ac, #if SEND_KELON /// Send a Kelon A/C message with the supplied settings. -/// @param[in, out] ac A Ptr to an IRKelonAC object to use. +/// @param[in, out] ac A Ptr to an IRKelonAc object to use. /// @param[in] mode The operation mode setting. /// @param[in] degrees The temperature setting in degrees. /// @param[in] fan The speed setting for the fan. @@ -1265,14 +1265,14 @@ void IRac::hitachi424(IRHitachiAc424 *ac, /// @param[in] dryGrade The dehumidification intensity grade /// @param[in] togglePower Whether to toggle the unit's power /// @param[in] toggleSwing Whether to toggle the swing setting -void IRac::kelon(IRKelonAC *ac, +void IRac::kelon(IRKelonAc *ac, const stdAc::opmode_t mode, const float degrees, const stdAc::fanspeed_t fan, const bool sleep, const bool superCool, const int8_t dryGrade, const bool togglePower = false, const bool toggleSwing = false) { ac->begin(); - ac->setMode(IRKelonAC::convertMode(mode)); - ac->setFan(IRKelonAC::convertFan(fan)); + ac->setMode(IRKelonAc::convertMode(mode)); + ac->setFan(IRKelonAc::convertFan(fan)); ac->setTemp(static_cast(degrees)); ac->setSleep(sleep); ac->setSupercool(superCool); @@ -2617,7 +2617,7 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { #endif // SEND_HITACHI_AC424 #if SEND_KELON case KELON: { - IRKelonAC ac(_pin, _inverted, _modulation); + IRKelonAc ac(_pin, _inverted, _modulation); kelon(&ac, send.mode, send.degrees, send.fanspeed, send.sleep, send.turbo, 0, send.power, send.swingv != stdAc::swingv_t::kOff); break; @@ -3334,7 +3334,7 @@ namespace IRAcUtils { #endif // DECODE_FUJITSU_AC #if DECODE_KELON case decode_type_t::KELON: { - IRKelonAC ac(kGpioUnused); + IRKelonAc ac(kGpioUnused); ac.setRaw(result->value); return ac.toString(); } @@ -3826,7 +3826,7 @@ namespace IRAcUtils { #endif // DECODE_HITACHI_AC424 #if DECODE_KELON case decode_type_t::KELON: { - IRKelonAC ac(kGpioUnused); + IRKelonAc ac(kGpioUnused); ac.setRaw(decode->value); *result = ac.toCommon(); break; diff --git a/src/IRac.h b/src/IRac.h index 415b744fd..77c6c2e17 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -291,7 +291,7 @@ void electra(IRElectraAc *ac, const stdAc::swingv_t swingv); #endif // SEND_HITACHI_AC424 #if SEND_KELON - void kelon(IRKelonAC *ac, + void kelon(IRKelonAc *ac, const stdAc::opmode_t mode, const float degrees, const stdAc::fanspeed_t fan, const bool sleep, const bool superCool, const int8_t dryGrade, const bool togglePower, const bool toggleSwing); diff --git a/src/IRrecv.cpp b/src/IRrecv.cpp index 6f0eb61c1..7962a305f 100644 --- a/src/IRrecv.cpp +++ b/src/IRrecv.cpp @@ -1021,7 +1021,7 @@ bool IRrecv::decode(decode_results *results, irparams_t *save, #if DECODE_KELON DPRINTLN("Attempting Kelon decode"); if (decodeKelon(results, offset, kKelonBits)) return true; -#endif // DECODE_KELON +#endif // DECODE_KELON // Typically new protocols are added above this line. } #if DECODE_HASH diff --git a/src/IRtext.cpp b/src/IRtext.cpp index 48f288fe9..98520b2c1 100644 --- a/src/IRtext.cpp +++ b/src/IRtext.cpp @@ -99,7 +99,7 @@ const PROGMEM char* k6thSenseStr = D_STR_6THSENSE; ///< "6th Sense" const PROGMEM char* kTypeStr = D_STR_TYPE; ///< "Type" const PROGMEM char* kSpecialStr = D_STR_SPECIAL; ///< "Special" const PROGMEM char* kIdStr = D_STR_ID; ///< "Id" / Device Identifier -const PROGMEM char* kDryGradeStr = D_STR_DRY_GRADE; ///< "Dry grade" +const PROGMEM char* kDryGradeStr = D_STR_DRY_GRADE; ///< "Dry grade" const PROGMEM char* kAutoStr = D_STR_AUTO; ///< "Auto" const PROGMEM char* kAutomaticStr = D_STR_AUTOMATIC; ///< "Automatic" diff --git a/src/IRutils.cpp b/src/IRutils.cpp index b894b67e9..bf35d6cbf 100644 --- a/src/IRutils.cpp +++ b/src/IRutils.cpp @@ -67,18 +67,6 @@ String uint64ToString(uint64_t input, uint8_t base) { return result; } -/// Convert a int64_t (signed long long) to a string. -/// Arduino String/toInt/Serial.print() can't handle printing 64 bit values. -/// @param[in] input The value to print -/// @param[in] base The output base. -/// @returns A String representation of the integer. -String int64ToString(int64_t input, uint8_t base) { - if (input < 0) { - return "-" + uint64ToString(-input, base); - } - return uint64ToString(input, base); -} - #ifdef ARDUINO /// Print a uint64_t/unsigned long long to the Serial port /// Serial.print() can't handle printing long longs. (uint64_t) @@ -508,19 +496,6 @@ namespace irutils { return addLabeledString(uint64ToString(value), label, precomma); } - /// Create a String with a colon separated labeled Integer suitable for - /// Humans. - /// e.g. "Foo: 23" - /// @param[in] value The value to come after the label. - /// @param[in] label The label to precede the value. - /// @param[in] precomma Should the output string start with ", " or not? - /// @return The resulting String. - String addSignedIntToString(const int16_t value, const String label, - const bool precomma) { - return addLabeledString(int64ToString(value), label, precomma); - } - - /// Generate the model string for a given Protocol/Model pair. /// @param[in] protocol The IR protocol. /// @param[in] model The model number for that protocol. diff --git a/src/IRutils.h b/src/IRutils.h index 738cf787d..8ce04b814 100644 --- a/src/IRutils.h +++ b/src/IRutils.h @@ -20,7 +20,6 @@ const uint8_t kHighNibble = 4; const uint8_t kModeBitsSize = 3; uint64_t reverseBits(uint64_t input, uint16_t nbits); String uint64ToString(uint64_t input, uint8_t base = 10); -String int64ToString(int64_t input, uint8_t base = 10); String typeToString(const decode_type_t protocol, const bool isRepeat = false); void serialPrintUint64(uint64_t input, uint8_t base = 10); @@ -50,8 +49,6 @@ namespace irutils { const bool precomma = true); String addIntToString(const uint16_t value, const String label, const bool precomma = true); - String addSignedIntToString(const int16_t value, const String label, - const bool precomma = true); String modelToStr(const decode_type_t protocol, const int16_t model); String addModelToString(const decode_type_t protocol, const int16_t model, const bool precomma = true); diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index 5a7937836..31a197dd4 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -22,7 +22,6 @@ using irutils::addBoolToString; using irutils::addIntToString; -using irutils::addSignedIntToString; using irutils::addModeToString; using irutils::addFanToString; using irutils::addTempToString; @@ -71,17 +70,13 @@ bool IRrecv::decodeKelon(decode_results *results, uint16_t offset, if (strict && nbits != kKelonBits) { return false; } - uint16_t used; - used = matchGeneric(results->rawbuf + offset, results->state, - results->rawlen - offset, nbits, - kKelonHdrMark, kKelonHdrSpace, - kKelonBitMark, kKelonOneSpace, - kKelonBitMark, kKelonZeroSpace, - kKelonBitMark, 0, false, - _tolerance, 0, false); - - // Data bits + 2 bits header + 1 bit footer = 99 bits - if (strict && used != nbits * 2 + 3) { + if (!matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kKelonHdrMark, kKelonHdrSpace, + kKelonBitMark, kKelonOneSpace, + kKelonBitMark, kKelonZeroSpace, + kKelonBitMark, 0, false, + _tolerance, 0, false)) { return false; } @@ -90,17 +85,17 @@ bool IRrecv::decodeKelon(decode_results *results, uint16_t offset, return true; } -#endif // DECODE_KELON +#endif // DECODE_KELON /// 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? -IRKelonAC::IRKelonAC(const uint16_t pin, const bool inverted, const bool use_modulation) +IRKelonAc::IRKelonAc(const uint16_t pin, const bool inverted, const bool use_modulation) : _irsend{pin, inverted, use_modulation}, _{} { stateReset(); } /// Reset the internals of the object to a known good state. -void IRKelonAC::stateReset() { +void IRKelonAc::stateReset() { _.raw = 0L; _.preamble[0] = 0b10000011; _.preamble[1] = 0b00000110; @@ -110,7 +105,7 @@ void IRKelonAC::stateReset() { /// Send the current internal state as an IR message. /// @param[in] repeat Nr. of times the message will be repeated. -void IRKelonAC::send(const uint16_t repeat) { +void IRKelonAc::send(const uint16_t repeat) { _irsend.sendKelon(getRaw(), kKelonBits, repeat); // Reset toggle flags @@ -126,7 +121,7 @@ void IRKelonAC::send(const uint16_t repeat) { /// it to "smart" will always turn it on if it's off. /// This method will send 2 commands to the AC to do the trick /// @param[in] on Whether to ensure the AC is on or off -void IRKelonAC::ensurePower(bool on) { +void IRKelonAc::ensurePower(bool on) { // Try to avoid turning on the compressor for this operation. // "Dry grade", when in "smart" mode, acts as a temperature offset that // the user can configure if they feel too cold or too hot. By setting it @@ -150,25 +145,25 @@ void IRKelonAC::ensurePower(bool on) { #endif // SEND_KELON /// Set up hardware to be able to send a message. -void IRKelonAC::begin() { +void IRKelonAc::begin() { _irsend.begin(); } /// Request toggling power - will be reset to false after sending /// @param[in] toggle Whether to toggle the power state -void IRKelonAC::setTogglePower(const bool toggle) { +void IRKelonAc::setTogglePower(const bool toggle) { _.PowerToggle = toggle; } /// Get whether toggling power will be requested /// @return The power toggle state -bool IRKelonAC::getTogglePower() const { +bool IRKelonAc::getTogglePower() const { return _.PowerToggle; } /// Set the temperature setting. /// @param[in] degrees The temperature in degrees celsius. -void IRKelonAC::setTemp(const uint8_t degrees) { +void IRKelonAc::setTemp(const uint8_t degrees) { uint8_t temp = std::max(kKelonMinTemp, degrees); temp = std::min(kKelonMaxTemp, temp); _previousTemp = _.Temperature; @@ -177,13 +172,13 @@ void IRKelonAC::setTemp(const uint8_t degrees) { /// Get the current temperature setting. /// @return Get current setting for temp. in degrees celsius. -uint8_t IRKelonAC::getTemp() const { +uint8_t IRKelonAc::getTemp() const { return _.Temperature + kKelonMinTemp; } /// Set the speed of the fan. /// @param[in] speed 0 is auto, 1-5 is the speed -void IRKelonAC::setFan(const uint8_t speed) { +void IRKelonAc::setFan(const uint8_t speed) { uint8_t fan = std::min(speed, kKelonFanMax); _previousFan = _.Fan; @@ -193,13 +188,13 @@ void IRKelonAC::setFan(const uint8_t speed) { /// Get the current fan speed setting. /// @return The current fan speed. -uint8_t IRKelonAC::getFan() const { +uint8_t IRKelonAc::getFan() const { return ((static_cast(_.Fan) - 4) * -1) % 4;; } /// Set the dehumidification intensity. /// @param[in] grade has to be in the range [-2 : +2] -void IRKelonAC::setDryGrade(const int8_t grade) { +void IRKelonAc::setDryGrade(const int8_t grade) { int8_t drygrade = std::max(kKelonDryGradeMin, grade); drygrade = std::min(kKelonDryGradeMax, drygrade); @@ -215,17 +210,13 @@ void IRKelonAC::setDryGrade(const int8_t grade) { /// Get the current dehumidification intensity setting. In smart mode, this controls the temperature adjustment. /// @return The current dehumidification intensity. -int8_t IRKelonAC::getDryGrade() const { - auto outval = static_cast(_.DehumidifierGrade & 0b011); - if ((_.DehumidifierGrade & 0b100) == 0b100) { - outval *= -1; - } - return outval; +int8_t IRKelonAc::getDryGrade() const { + return (_.DehumidifierGrade & 0b011) * (_.DehumidifierGrade & 0b100) ? -1 : 1; } /// Set the desired operation mode. /// @param[in] mode The desired operation mode. -void IRKelonAC::setMode(const uint8_t mode) { +void IRKelonAc::setMode(const uint8_t mode) { if (_.Mode == kKelonModeSmart || _.Mode == kKelonModeFan || _.Mode == kKelonModeDry) { _.Temperature = _previousTemp; } @@ -247,7 +238,6 @@ void IRKelonAC::setMode(const uint8_t mode) { case kKelonModeDry: case kKelonModeFan: setTemp(25); - _.Mode = mode; //fallthrough case kKelonModeCool: case kKelonModeHeat: @@ -260,37 +250,37 @@ void IRKelonAC::setMode(const uint8_t mode) { /// Get the current operation mode setting. /// @return The current operation mode. -uint8_t IRKelonAC::getMode() const { +uint8_t IRKelonAc::getMode() const { return _.Mode; } /// Request toggling the vertical swing - will be reset to false after sending /// @param[in] toggle If true, the swing mode will be toggled when sent. -void IRKelonAC::setToggleSwingVertical(const bool toggle) { +void IRKelonAc::setToggleSwingVertical(const bool toggle) { _.SwingVToggle = toggle; } /// Get whether the swing mode is set to be toggled /// @return Whether the toggle bit is set -bool IRKelonAC::getToggleSwingVertical() const { +bool IRKelonAc::getToggleSwingVertical() const { return _.SwingVToggle; } /// Control the current sleep (quiet) setting. /// @param[in] on The desired setting. -void IRKelonAC::setSleep(const bool on) { +void IRKelonAc::setSleep(const bool on) { _.SleepEnabled = on; } /// Is the sleep setting on? /// @return The current value. -bool IRKelonAC::getSleep() const { +bool IRKelonAc::getSleep() const { return _.SleepEnabled; } /// Control the current super cool mode setting. /// @param[in] on The desired setting. -void IRKelonAC::setSupercool(const bool on) { +void IRKelonAc::setSupercool(const bool on) { if (on) { setTemp(kKelonMinTemp); setMode(kKelonModeCool); @@ -305,13 +295,13 @@ void IRKelonAC::setSupercool(const bool on) { /// Is the super cool mode setting on? /// @return The current value. -bool IRKelonAC::getSupercool() const { +bool IRKelonAc::getSupercool() const { return _.SuperCoolEnabled1; } /// Set the timer time and enable it. Timer is an off timer if the unit is on, it is an on timer if the unit is off. -/// @param[in] mins Timer minutes (only multiples of 30m are supported for < 10h, then only multiples of 60m) -void IRKelonAC::setTimer(uint16_t mins) { +/// @param[in] mins Nr. of minutes (only multiples of 30m are supported for < 10h, then only multiples of 60m) +void IRKelonAc::setTimer(uint16_t mins) { const uint16_t minutes = std::min(static_cast(mins), 24 * 60); if (minutes / 60 >= 10) { @@ -329,7 +319,7 @@ void IRKelonAC::setTimer(uint16_t mins) { /// Get the set timer. Timer set time is deleted once the command is sent, so calling this after send() will return 0. /// The AC unit will continue keeping track of the remaining time unless it is later disabled. /// @return The timer set minutes -uint16_t IRKelonAC::getTimer() const { +uint16_t IRKelonAc::getTimer() const { if (_.TimerHours >= 10) { return ((uint16_t) ((_.TimerHours << 1) | _.TimerHalfHour) - 10) * 60; } @@ -338,13 +328,13 @@ uint16_t IRKelonAC::getTimer() const { /// Enable or disable the timer. Note that in order to enable the timer the minutes must be set with setTimer(). /// @param[in] on Whether to enable or disable the timer -void IRKelonAC::setTimerEnabled(bool on) { +void IRKelonAc::setTimerEnabled(bool on) { _.TimerEnabled = on; } /// Get the current timer status /// @return Whether the timer is enabled. -bool IRKelonAC::getTimerEnabled() const { +bool IRKelonAc::getTimerEnabled() const { return _.TimerEnabled; } @@ -352,20 +342,20 @@ bool IRKelonAC::getTimerEnabled() const { /// Get the raw state of the object, suitable to be sent with the appropriate /// IRsend object method. /// @return A PTR to the internal state. -uint64_t IRKelonAC::getRaw() const { +uint64_t IRKelonAc::getRaw() const { return _.raw; } /// Set the raw state of the object. /// @param[in] new_code The raw state from the native IR message. -void IRKelonAC::setRaw(const uint64_t new_code) { +void IRKelonAc::setRaw(const uint64_t new_code) { _.raw = new_code; } /// Convert a standard A/C mode (stdAc::opmode_t) into it a native mode. /// @param[in] mode A stdAc::opmode_t operation mode. /// @return The native mode equivalent. -uint8_t IRKelonAC::convertMode(const stdAc::opmode_t mode) { +uint8_t IRKelonAc::convertMode(const stdAc::opmode_t mode) { switch (mode) { case stdAc::opmode_t::kCool: return kKelonModeCool; @@ -375,7 +365,6 @@ uint8_t IRKelonAC::convertMode(const stdAc::opmode_t mode) { return kKelonModeDry; case stdAc::opmode_t::kFan: return kKelonModeFan; - case stdAc::opmode_t::kAuto: default: return kKelonModeSmart; } @@ -384,7 +373,7 @@ uint8_t IRKelonAC::convertMode(const stdAc::opmode_t mode) { /// Convert a standard A/C fan speed (stdAc::fanspeed_t) into it a native speed. /// @param[in] fan A stdAc::fanspeed_t fan speed /// @return The native speed equivalent. -uint8_t IRKelonAC::convertFan(stdAc::fanspeed_t fan) { +uint8_t IRKelonAc::convertFan(stdAc::fanspeed_t fan) { switch (fan) { case stdAc::fanspeed_t::kMin: case stdAc::fanspeed_t::kLow: @@ -394,7 +383,6 @@ uint8_t IRKelonAC::convertFan(stdAc::fanspeed_t fan) { case stdAc::fanspeed_t::kHigh: case stdAc::fanspeed_t::kMax: return kKelonFanMax; - case stdAc::fanspeed_t::kAuto: default: return kKelonFanAuto; } @@ -403,7 +391,7 @@ uint8_t IRKelonAC::convertFan(stdAc::fanspeed_t fan) { /// Convert a native mode to it's stdAc::opmode_t equivalent. /// @param[in] mode A native operating mode value. /// @return The stdAc::opmode_t equivalent. -stdAc::opmode_t IRKelonAC::toCommonMode(const uint8_t mode) { +stdAc::opmode_t IRKelonAc::toCommonMode(const uint8_t mode) { switch (mode) { case kKelonModeCool: return stdAc::opmode_t::kCool; @@ -413,7 +401,6 @@ stdAc::opmode_t IRKelonAC::toCommonMode(const uint8_t mode) { return stdAc::opmode_t::kDry; case kKelonModeFan: return stdAc::opmode_t::kFan; - case kKelonModeSmart: default: return stdAc::opmode_t::kAuto; } @@ -422,7 +409,7 @@ stdAc::opmode_t IRKelonAC::toCommonMode(const uint8_t mode) { /// Convert a native fan speed to it's stdAc::fanspeed_t equivalent. /// @param[in] speed A native fan speed value. /// @return The stdAc::fanspeed_t equivalent. -stdAc::fanspeed_t IRKelonAC::toCommonFanSpeed(const uint8_t speed) { +stdAc::fanspeed_t IRKelonAc::toCommonFanSpeed(const uint8_t speed) { switch (speed) { case kKelonFanMin: return stdAc::fanspeed_t::kLow; @@ -430,7 +417,6 @@ stdAc::fanspeed_t IRKelonAC::toCommonFanSpeed(const uint8_t speed) { return stdAc::fanspeed_t::kMedium; case kKelonFanMax: return stdAc::fanspeed_t::kHigh; - case kKelonFanAuto: default: return stdAc::fanspeed_t::kAuto; } @@ -438,7 +424,7 @@ stdAc::fanspeed_t IRKelonAC::toCommonFanSpeed(const uint8_t speed) { /// Convert the internal A/C object state to it's stdAc::state_t equivalent. /// @return A stdAc::state_t containing the current settings. -stdAc::state_t IRKelonAC::toCommon() const { +stdAc::state_t IRKelonAc::toCommon() const { stdAc::state_t result{}; result.protocol = decode_type_t::KELON; result.model = -1; // Unused. @@ -464,14 +450,14 @@ stdAc::state_t IRKelonAC::toCommon() const { /// Convert the internal settings into a human readable string. /// @return A String. -String IRKelonAC::toString() const { +String IRKelonAc::toString() const { String result = ""; result.reserve(160); // Reserve some heap for the string to reduce fragging. result += addTempToString(getTemp(), true, false); result += addModeToString(_.Mode, kKelonModeSmart, kKelonModeCool, kKelonModeHeat, kKelonModeDry, kKelonModeFan); result += addFanToString(_.Fan, kKelonFanMax, kKelonFanMin, kKelonFanAuto, -1, kKelonFanMedium, kKelonFanMax); result += addBoolToString(_.SleepEnabled, kSleepStr); - result += addSignedIntToString(getDryGrade(), kDryGradeStr); + result += addLabeledString(String(getDryGrade()), kDryGradeStr); result += addLabeledString(getTimerEnabled() ? (getTimer() > 0 ? minsToString(getTimer()) : kOnStr) : kOffStr,kTimerStr); diff --git a/src/ir_Kelon.h b/src/ir_Kelon.h index 0784c2d6c..69c901858 100644 --- a/src/ir_Kelon.h +++ b/src/ir_Kelon.h @@ -47,106 +47,106 @@ union KelonProtocol { }; // Constants -const uint8_t kKelonModeHeat{0}; -const uint8_t kKelonModeSmart{1}; // (temp = 26C, but not shown) -const uint8_t kKelonModeCool{2}; -const uint8_t kKelonModeDry{3}; // (temp = 25C, but not shown) -const uint8_t kKelonModeFan{4}; // (temp = 25C, but not shown) -const uint8_t kKelonFanAuto{0}; +const uint8_t kKelonModeHeat = 0; +const uint8_t kKelonModeSmart = 1; // (temp = 26C, but not shown) +const uint8_t kKelonModeCool = 2; +const uint8_t kKelonModeDry = 3; // (temp = 25C, but not shown) +const uint8_t kKelonModeFan = 4; // (temp = 25C, but not shown) +const uint8_t kKelonFanAuto = 0; // Note! Kelon fan speeds are actually 0:AUTO, 1:MAX, 2:MED, 3:MIN // Since this is insane, I decided to invert them in the public API, they are converted back in setFan/getFan -const uint8_t kKelonFanMin{1}; -const uint8_t kKelonFanMedium{2}; -const uint8_t kKelonFanMax{3}; +const uint8_t kKelonFanMin = 1; +const uint8_t kKelonFanMedium = 2; +const uint8_t kKelonFanMax = 3; -const int8_t kKelonDryGradeMax{2}; -const int8_t kKelonDryGradeMin{-2}; -const uint8_t kKelonMinTemp{18}; -const uint8_t kKelonMaxTemp{32}; +const int8_t kKelonDryGradeMin = -2; +const int8_t kKelonDryGradeMax = +2; +const uint8_t kKelonMinTemp = 18; +const uint8_t kKelonMaxTemp = 32; -class IRKelonAC { +class IRKelonAc { public: - explicit IRKelonAC(uint16_t pin, bool inverted = false, bool use_modulation = true); + explicit IRKelonAc(uint16_t pin, bool inverted = false, bool use_modulation = true); - void stateReset(); + void stateReset(void); #if SEND_KELON - void send(uint16_t repeat = kNoRepeat); + 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() { return _irsend.calibrate(); } + int8_t calibrate(void) { return _irsend.calibrate(); } /// Since the AC does not support actually setting the power state to a known /// value, this utility allow ensuring the AC is on or off by exploiting /// the fact that the AC, according to the user manual, will always turn on /// when setting it to "smart" or "super" mode. - void ensurePower(bool on); + void ensurePower(const bool on); #endif - void begin(); + void begin(void); - void setTogglePower(bool toggle); + void setTogglePower(const bool toggle); - bool getTogglePower() const; + bool getTogglePower(void) const; - void setTemp(uint8_t degrees); + void setTemp(const uint8_t degrees); - uint8_t getTemp() const; + uint8_t getTemp(void) const; - void setFan(uint8_t speed); + void setFan(const uint8_t speed); - uint8_t getFan() const; + uint8_t getFan(void) const; - void setDryGrade(int8_t grade); + void setDryGrade(const int8_t grade); - int8_t getDryGrade() const; + int8_t getDryGrade(void) const; - void setMode(uint8_t mode); + void setMode(const uint8_t mode); - uint8_t getMode() const; + uint8_t getMode(void) const; - void setToggleSwingVertical(bool toggle); + void setToggleSwingVertical(const bool toggle); - bool getToggleSwingVertical() const; + bool getToggleSwingVertical(void) const; - void setSleep(bool on); + void setSleep(const bool on); - bool getSleep() const; + bool getSleep(void) const; - void setSupercool(bool on); + void setSupercool(const bool on); - bool getSupercool() const; + bool getSupercool(void) const; - void setTimer(uint16_t mins); + void setTimer(const uint16_t mins); - uint16_t getTimer() const; + uint16_t getTimer(void) const; - void setTimerEnabled(bool on); + void setTimerEnabled(const bool on); - bool getTimerEnabled() const; + bool getTimerEnabled(void) const; - uint64_t getRaw() const; + uint64_t getRaw(void) const; void setRaw(const uint64_t new_code); - static uint8_t convertMode(stdAc::opmode_t mode); + static uint8_t convertMode(const stdAc::opmode_t mode); - static uint8_t convertFan(stdAc::fanspeed_t fan); + static uint8_t convertFan(const stdAc::fanspeed_t fan); - static stdAc::opmode_t toCommonMode(uint8_t mode); + static stdAc::opmode_t toCommonMode(const uint8_t mode); - static stdAc::fanspeed_t toCommonFanSpeed(uint8_t speed); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); - stdAc::state_t toCommon() const; + stdAc::state_t toCommon(void) const; - String toString() const; + String toString(void) const; private: #ifndef UNIT_TEST @@ -159,9 +159,9 @@ class IRKelonAC { KelonProtocol _; // Used when exiting supercool mode - uint8_t _previousMode{0}; - uint8_t _previousTemp{kKelonMinTemp}; - uint8_t _previousFan{kKelonFanAuto}; + uint8_t _previousMode = 0; + uint8_t _previousTemp = kKelonMinTemp; + uint8_t _previousFan = kKelonFanAuto; }; #endif // IR_KELON_H_ diff --git a/src/locale/defaults.h b/src/locale/defaults.h index 004825a16..c11a4a9da 100644 --- a/src/locale/defaults.h +++ b/src/locale/defaults.h @@ -618,7 +618,7 @@ #endif // D_STR_JVC #ifndef D_STR_KELON #define D_STR_KELON "KELON" -#endif // D_STR_KELON +#endif // D_STR_KELON #ifndef D_STR_KELVINATOR #define D_STR_KELVINATOR "KELVINATOR" #endif // D_STR_KELVINATOR diff --git a/test/ir_Kelon_test.cpp b/test/ir_Kelon_test.cpp index 858a631ab..f27a85c33 100644 --- a/test/ir_Kelon_test.cpp +++ b/test/ir_Kelon_test.cpp @@ -11,7 +11,7 @@ // Test sending typical data only. TEST(TestSendKelon, SendDataOnly) { - IRsendTest irsend(4); + IRsendTest irsend(kGpioUnused); irsend.begin(); // Temp: 26C, Mode: 2 (Cool), Fan: 0 (Auto), Sleep: Off, Dry grade: 0, Timer: Off, Turbo: Off @@ -95,8 +95,8 @@ TEST(TestSendKelon, SendDataOnly) { // Tests for decodeKelon(). // Decode normal Kelon messages. TEST(TestDecodeKelon, Timer12HSmartMode) { - IRsendTest irsend(0); - IRrecv irrecv(0); + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); irsend.begin(); irsend.reset(); @@ -114,8 +114,8 @@ TEST(TestDecodeKelon, Timer12HSmartMode) { } TEST(TestDecodeKelon, Timer5_5hSuperCoolMode) { - IRsendTest irsend(0); - IRrecv irrecv(0); + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); irsend.begin(); irsend.reset(); @@ -133,8 +133,8 @@ TEST(TestDecodeKelon, Timer5_5hSuperCoolMode) { } TEST(TestDecodeKelon, ChangeSettingsWithTimerSetHeatMode) { - IRsendTest irsend(0); - IRrecv irrecv(0); + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); irsend.begin(); irsend.reset(); @@ -152,8 +152,8 @@ TEST(TestDecodeKelon, ChangeSettingsWithTimerSetHeatMode) { } TEST(TestDecodeKelon, TestPowerToggleDryMode) { - IRsendTest irsend(0); - IRrecv irrecv(0); + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); irsend.begin(); irsend.reset(); @@ -171,8 +171,8 @@ TEST(TestDecodeKelon, TestPowerToggleDryMode) { } TEST(TestDecodeKelon, TestSwingToggleDryMode) { - IRsendTest irsend(0); - IRrecv irrecv(0); + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); irsend.begin(); irsend.reset(); @@ -190,8 +190,8 @@ TEST(TestDecodeKelon, TestSwingToggleDryMode) { } TEST(TestDecodeKelon, TestDryGradeNegativeValue) { - IRsendTest irsend(0); - IRrecv irrecv(0); + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); irsend.begin(); irsend.reset(); @@ -210,15 +210,15 @@ TEST(TestDecodeKelon, TestDryGradeNegativeValue) { TEST(TestIRKelonClass, SetAndGetRaw) { uint64_t rawData = 0x100B0A010683; - IRKelonAC ac(kGpioUnused); + IRKelonAc ac(kGpioUnused); ac.setRaw(rawData); EXPECT_EQ(rawData, ac.getRaw()); } TEST(TestIRKelonClass, SetAndGetTemp) { - IRKelonAC ac(kGpioUnused); + IRKelonAc ac(kGpioUnused); - for (uint8_t temp = 18; temp <= 32; temp++) { + for (uint8_t temp = kKelonMinTemp; temp <= kKelonMaxTemp; temp++) { ac.setTemp(temp); EXPECT_EQ(temp, ac.getTemp()); } @@ -231,7 +231,7 @@ TEST(TestIRKelonClass, SetAndGetTemp) { } TEST(TestIRKelonClass, SetAndGetTimer) { - IRKelonAC ac(kGpioUnused); + IRKelonAc ac(kGpioUnused); // 0.5h to 10h timers have a granularity of 30 minutes for (uint16_t minutes = 0; minutes <= 60 * 10; minutes += 30) { @@ -251,7 +251,7 @@ TEST(TestIRKelonClass, SetAndGetTimer) { } TEST(TestIRKelonClass, CheckToggles) { - IRKelonAC ac(kGpioUnused); + IRKelonAc ac(kGpioUnused); ac.setTogglePower(true); EXPECT_TRUE(ac.getTogglePower()); @@ -273,7 +273,7 @@ TEST(TestIRKelonClass, CheckToggles) { } TEST(TestIRKelonClass, SetAndGetMode) { - IRKelonAC ac(kGpioUnused); + IRKelonAc ac(kGpioUnused); uint8_t initial_temp = 20; ac.setMode(kKelonModeHeat); @@ -316,7 +316,7 @@ TEST(TestIRKelonClass, SetAndGetMode) { } TEST(TestIRKelonClass, CheckSuperCoolMode) { - IRKelonAC ac(kGpioUnused); + IRKelonAc ac(kGpioUnused); uint8_t initial_temp = 20; uint8_t initial_fan = kKelonFanMin; @@ -350,7 +350,7 @@ TEST(TestIRKelonClass, CheckSuperCoolMode) { } TEST(TestIRKelonClass, SetAndGetDryGrade) { - IRKelonAC ac(kGpioUnused); + IRKelonAc ac(kGpioUnused); for (int i = -2; i <= 2; i++) { ac.setDryGrade(i); @@ -364,7 +364,7 @@ TEST(TestIRKelonClass, SetAndGetDryGrade) { } TEST(TestIRKelonClass, toCommon) { - IRKelonAC ac(kGpioUnused); + IRKelonAc ac(kGpioUnused); ac.setSleep(false); ac.setTemp(23); From 4d67d14ebf3267f7767040b6334f00c67bc9ea09 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Mon, 14 Jun 2021 21:12:02 +0200 Subject: [PATCH 18/36] Kelon: Replace "Dry grade" with just "Dry" --- src/IRtext.cpp | 1 - src/IRtext.h | 1 - src/ir_Kelon.cpp | 2 +- src/locale/defaults.h | 3 --- 4 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/IRtext.cpp b/src/IRtext.cpp index 98520b2c1..91233ad80 100644 --- a/src/IRtext.cpp +++ b/src/IRtext.cpp @@ -99,7 +99,6 @@ const PROGMEM char* k6thSenseStr = D_STR_6THSENSE; ///< "6th Sense" const PROGMEM char* kTypeStr = D_STR_TYPE; ///< "Type" const PROGMEM char* kSpecialStr = D_STR_SPECIAL; ///< "Special" const PROGMEM char* kIdStr = D_STR_ID; ///< "Id" / Device Identifier -const PROGMEM char* kDryGradeStr = D_STR_DRY_GRADE; ///< "Dry grade" const PROGMEM char* kAutoStr = D_STR_AUTO; ///< "Auto" const PROGMEM char* kAutomaticStr = D_STR_AUTOMATIC; ///< "Automatic" diff --git a/src/IRtext.h b/src/IRtext.h index 05fa563ae..d991276cd 100644 --- a/src/IRtext.h +++ b/src/IRtext.h @@ -46,7 +46,6 @@ extern const char* kDayStr; extern const char* kDisplayTempStr; extern const char* kDownStr; extern const char* kDryStr; -extern const char* kDryGradeStr; extern const char* kEconoStr; extern const char* kEconoToggleStr; extern const char* kEyeAutoStr; diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index 31a197dd4..cd2a995e9 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -457,7 +457,7 @@ String IRKelonAc::toString() const { result += addModeToString(_.Mode, kKelonModeSmart, kKelonModeCool, kKelonModeHeat, kKelonModeDry, kKelonModeFan); result += addFanToString(_.Fan, kKelonFanMax, kKelonFanMin, kKelonFanAuto, -1, kKelonFanMedium, kKelonFanMax); result += addBoolToString(_.SleepEnabled, kSleepStr); - result += addLabeledString(String(getDryGrade()), kDryGradeStr); + //result += addLabeledString(String(static_cast(getDryGrade())), kDryStr); result += addLabeledString(getTimerEnabled() ? (getTimer() > 0 ? minsToString(getTimer()) : kOnStr) : kOffStr,kTimerStr); diff --git a/src/locale/defaults.h b/src/locale/defaults.h index c11a4a9da..8c6096289 100644 --- a/src/locale/defaults.h +++ b/src/locale/defaults.h @@ -279,9 +279,6 @@ #ifndef D_STR_ID #define D_STR_ID "Id" #endif // D_STR_ID -#ifndef D_STR_DRY_GRADE -#define D_STR_DRY_GRADE "Dry grade" -#endif // D_STR_DRY_GRADE #ifndef D_STR_AUTO #define D_STR_AUTO "Auto" From 46877ec0034d225dd53ab4282bc051a718ecb357 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Sun, 6 Jun 2021 01:07:24 +0200 Subject: [PATCH 19/36] Allow converting signed ints to string --- src/IRutils.cpp | 25 +++++++++++++++++++++++++ src/IRutils.h | 3 +++ 2 files changed, 28 insertions(+) diff --git a/src/IRutils.cpp b/src/IRutils.cpp index bf35d6cbf..b894b67e9 100644 --- a/src/IRutils.cpp +++ b/src/IRutils.cpp @@ -67,6 +67,18 @@ String uint64ToString(uint64_t input, uint8_t base) { return result; } +/// Convert a int64_t (signed long long) to a string. +/// Arduino String/toInt/Serial.print() can't handle printing 64 bit values. +/// @param[in] input The value to print +/// @param[in] base The output base. +/// @returns A String representation of the integer. +String int64ToString(int64_t input, uint8_t base) { + if (input < 0) { + return "-" + uint64ToString(-input, base); + } + return uint64ToString(input, base); +} + #ifdef ARDUINO /// Print a uint64_t/unsigned long long to the Serial port /// Serial.print() can't handle printing long longs. (uint64_t) @@ -496,6 +508,19 @@ namespace irutils { return addLabeledString(uint64ToString(value), label, precomma); } + /// Create a String with a colon separated labeled Integer suitable for + /// Humans. + /// e.g. "Foo: 23" + /// @param[in] value The value to come after the label. + /// @param[in] label The label to precede the value. + /// @param[in] precomma Should the output string start with ", " or not? + /// @return The resulting String. + String addSignedIntToString(const int16_t value, const String label, + const bool precomma) { + return addLabeledString(int64ToString(value), label, precomma); + } + + /// Generate the model string for a given Protocol/Model pair. /// @param[in] protocol The IR protocol. /// @param[in] model The model number for that protocol. diff --git a/src/IRutils.h b/src/IRutils.h index 8ce04b814..738cf787d 100644 --- a/src/IRutils.h +++ b/src/IRutils.h @@ -20,6 +20,7 @@ const uint8_t kHighNibble = 4; const uint8_t kModeBitsSize = 3; uint64_t reverseBits(uint64_t input, uint16_t nbits); String uint64ToString(uint64_t input, uint8_t base = 10); +String int64ToString(int64_t input, uint8_t base = 10); String typeToString(const decode_type_t protocol, const bool isRepeat = false); void serialPrintUint64(uint64_t input, uint8_t base = 10); @@ -49,6 +50,8 @@ namespace irutils { const bool precomma = true); String addIntToString(const uint16_t value, const String label, const bool precomma = true); + String addSignedIntToString(const int16_t value, const String label, + const bool precomma = true); String modelToStr(const decode_type_t protocol, const int16_t model); String addModelToString(const decode_type_t protocol, const int16_t model, const bool precomma = true); From 7105e426eec43f800cb977ca756743fc5093f78e Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Mon, 14 Jun 2021 21:14:43 +0200 Subject: [PATCH 20/36] Kelon: Use signed int string helper for dry grade --- src/ir_Kelon.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index cd2a995e9..eea154d29 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -10,8 +10,6 @@ /// - Explicit swing position due to AC unit limitations /// - Fahrenheit. -#include - #include "ir_Kelon.h" #include "IRrecv.h" @@ -22,6 +20,7 @@ using irutils::addBoolToString; using irutils::addIntToString; +using irutils::addSignedIntToString; using irutils::addModeToString; using irutils::addFanToString; using irutils::addTempToString; @@ -211,7 +210,7 @@ void IRKelonAc::setDryGrade(const int8_t grade) { /// Get the current dehumidification intensity setting. In smart mode, this controls the temperature adjustment. /// @return The current dehumidification intensity. int8_t IRKelonAc::getDryGrade() const { - return (_.DehumidifierGrade & 0b011) * (_.DehumidifierGrade & 0b100) ? -1 : 1; + return (_.DehumidifierGrade & 0b011) * ((_.DehumidifierGrade & 0b100) ? -1 : 1); // NOLINT(cppcoreguidelines-narrowing-conversions) } /// Set the desired operation mode. @@ -457,7 +456,7 @@ String IRKelonAc::toString() const { result += addModeToString(_.Mode, kKelonModeSmart, kKelonModeCool, kKelonModeHeat, kKelonModeDry, kKelonModeFan); result += addFanToString(_.Fan, kKelonFanMax, kKelonFanMin, kKelonFanAuto, -1, kKelonFanMedium, kKelonFanMax); result += addBoolToString(_.SleepEnabled, kSleepStr); - //result += addLabeledString(String(static_cast(getDryGrade())), kDryStr); + result += addSignedIntToString(getDryGrade(), kDryStr); result += addLabeledString(getTimerEnabled() ? (getTimer() > 0 ? minsToString(getTimer()) : kOnStr) : kOffStr,kTimerStr); From 1cac6ab6f2e8e930e3feff278895bd5e627dcc11 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Mon, 14 Jun 2021 21:15:44 +0200 Subject: [PATCH 21/36] Kelon: Adjust tests for string changes --- test/ir_Kelon_test.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test/ir_Kelon_test.cpp b/test/ir_Kelon_test.cpp index f27a85c33..6025cb715 100644 --- a/test/ir_Kelon_test.cpp +++ b/test/ir_Kelon_test.cpp @@ -14,7 +14,7 @@ TEST(TestSendKelon, SendDataOnly) { IRsendTest irsend(kGpioUnused); irsend.begin(); - // Temp: 26C, Mode: 2 (Cool), Fan: 0 (Auto), Sleep: Off, Dry grade: 0, Timer: Off, Turbo: Off + // Temp: 26C, Mode: 2 (Cool), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: Off, Turbo: Off irsend.reset(); irsend.sendKelon(0x82000683); EXPECT_EQ( @@ -29,7 +29,7 @@ TEST(TestSendKelon, SendDataOnly) { irsend.outputStr() ); - // Temp: 18C, Mode: 2 (Cool), Fan: 1 (Low), Sleep: Off, Dry grade: 0, Timer: Off, Turbo: On + // Temp: 18C, Mode: 2 (Cool), Fan: 1 (Low), Sleep: Off, Dry: 0, Timer: Off, Turbo: On irsend.reset(); irsend.sendKelon(0x900002010683); EXPECT_EQ( @@ -44,7 +44,7 @@ TEST(TestSendKelon, SendDataOnly) { irsend.outputStr() ); - // Temp: 23C, Mode: 0 (Heat), Fan: 0 (Auto), Sleep: Off, Dry grade: 0, Timer: Off, Turbo: Off, Power Toggle: On + // Temp: 23C, Mode: 0 (Heat), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: Off, Turbo: Off, Power Toggle: On irsend.reset(); irsend.sendKelon(0x50040683); EXPECT_EQ( @@ -59,7 +59,7 @@ TEST(TestSendKelon, SendDataOnly) { irsend.outputStr() ); - // Temp: 26C, Mode: 2 (Cool), Fan: 0 (Auto), Sleep: Off, Dry grade: 0, Timer: On (9.5h), Turbo: + // Temp: 26C, Mode: 2 (Cool), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: On (9.5h), Turbo: irsend.reset(); irsend.sendKelon(0x138A000683); EXPECT_EQ( @@ -75,7 +75,7 @@ TEST(TestSendKelon, SendDataOnly) { irsend.outputStr() ); - // Temp: 26C, Mode: 2 (Cool), Fan: 0 (Auto), Sleep: Off, Dry grade: 0, Timer: On (15h), Turbo: Off: + // Temp: 26C, Mode: 2 (Cool), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: On (15h), Turbo: Off: irsend.reset(); irsend.sendKelon(0x198A000683); EXPECT_EQ( @@ -106,7 +106,7 @@ TEST(TestDecodeKelon, Timer12HSmartMode) { EXPECT_EQ(KELON, irsend.capture.decode_type); EXPECT_EQ(kKelonBits, irsend.capture.bits); EXPECT_EQ( - "Temp: 25C, Mode: 1 (Auto), Fan: 3 (High), Sleep: Off, Dry grade: 0, Timer: 12:00, Turbo: Off", + "Temp: 25C, Mode: 1 (Auto), Fan: 3 (High), Sleep: Off, Dry: 0, Timer: 12:00, Turbo: Off", IRAcUtils::resultAcToString(&irsend.capture) ); stdAc::state_t result, prev; @@ -125,7 +125,7 @@ TEST(TestDecodeKelon, Timer5_5hSuperCoolMode) { EXPECT_EQ(KELON, irsend.capture.decode_type); EXPECT_EQ(kKelonBits, irsend.capture.bits); EXPECT_EQ( - "Temp: 18C, Mode: 2 (Cool), Fan: 1 (Low), Sleep: Off, Dry grade: 0, Timer: 05:30, Turbo: On", + "Temp: 18C, Mode: 2 (Cool), Fan: 1 (Low), Sleep: Off, Dry: 0, Timer: 05:30, Turbo: On", IRAcUtils::resultAcToString(&irsend.capture) ); stdAc::state_t result, prev; @@ -144,7 +144,7 @@ TEST(TestDecodeKelon, ChangeSettingsWithTimerSetHeatMode) { EXPECT_EQ(KELON, irsend.capture.decode_type); EXPECT_EQ(kKelonBits, irsend.capture.bits); EXPECT_EQ( - "Temp: 23C, Mode: 0 (Heat), Fan: 0 (Auto), Sleep: Off, Dry grade: 0, Timer: On, Turbo: Off", + "Temp: 23C, Mode: 0 (Heat), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: On, Turbo: Off", IRAcUtils::resultAcToString(&irsend.capture) ); stdAc::state_t result, prev; @@ -163,7 +163,7 @@ TEST(TestDecodeKelon, TestPowerToggleDryMode) { EXPECT_EQ(KELON, irsend.capture.decode_type); EXPECT_EQ(kKelonBits, irsend.capture.bits); EXPECT_EQ( - "Temp: 26C, Mode: 3 (Dry), Fan: 0 (Auto), Sleep: Off, Dry grade: 0, Timer: Off, Turbo: Off, Power Toggle: On", + "Temp: 26C, Mode: 3 (Dry), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: Off, Turbo: Off, Power Toggle: On", IRAcUtils::resultAcToString(&irsend.capture) ); stdAc::state_t result, prev; @@ -182,7 +182,7 @@ TEST(TestDecodeKelon, TestSwingToggleDryMode) { EXPECT_EQ(KELON, irsend.capture.decode_type); EXPECT_EQ(kKelonBits, irsend.capture.bits); EXPECT_EQ( - "Temp: 26C, Mode: 3 (Dry), Fan: 0 (Auto), Sleep: Off, Dry grade: 0, Timer: Off, Turbo: Off, Swing(V) Toggle: On", + "Temp: 26C, Mode: 3 (Dry), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: Off, Turbo: Off, Swing(V) Toggle: On", IRAcUtils::resultAcToString(&irsend.capture) ); stdAc::state_t result, prev; @@ -201,7 +201,7 @@ TEST(TestDecodeKelon, TestDryGradeNegativeValue) { EXPECT_EQ(KELON, irsend.capture.decode_type); EXPECT_EQ(kKelonBits, irsend.capture.bits); EXPECT_EQ( - "Temp: 26C, Mode: 3 (Dry), Fan: 0 (Auto), Sleep: Off, Dry grade: -2, Timer: Off, Turbo: Off", + "Temp: 26C, Mode: 3 (Dry), Fan: 0 (Auto), Sleep: Off, Dry: -2, Timer: Off, Turbo: Off", IRAcUtils::resultAcToString(&irsend.capture) ); stdAc::state_t result, prev; From 66c35b1f0e34e22641f8eb6ed41485ec80ae3cec Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Mon, 14 Jun 2021 21:29:19 +0200 Subject: [PATCH 22/36] Kelon: Fix minor lint issues --- src/IRutils.cpp | 2 +- src/ir_Kelon.cpp | 62 ++++++++++++++++++++++++++------------ test/ir_Kelon_test.cpp | 68 +++++++++++++++++++++--------------------- 3 files changed, 78 insertions(+), 54 deletions(-) diff --git a/src/IRutils.cpp b/src/IRutils.cpp index b894b67e9..9f6cee1be 100644 --- a/src/IRutils.cpp +++ b/src/IRutils.cpp @@ -516,7 +516,7 @@ namespace irutils { /// @param[in] precomma Should the output string start with ", " or not? /// @return The resulting String. String addSignedIntToString(const int16_t value, const String label, - const bool precomma) { + const bool precomma) { return addLabeledString(int64ToString(value), label, precomma); } diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index eea154d29..7ae33608b 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -2,7 +2,8 @@ /// @file /// @brief Support for Kelan AC protocol. -/// Both sending and decoding should be functional for models of series KELON ON/OFF 9000-12000. +/// Both sending and decoding should be functional for models of series +/// KELON ON/OFF 9000-12000. /// All features of the standard remote are implemented. /// /// @note Unsupported: @@ -10,6 +11,8 @@ /// - Explicit swing position due to AC unit limitations /// - Fahrenheit. +#include + #include "ir_Kelon.h" #include "IRrecv.h" @@ -43,7 +46,8 @@ const uint16_t kKelonFreq = 38000; /// @param[in] data The data to be transmitted. /// @param[in] nbits Nr. of bits of data to be sent. /// @param[in] repeat The number of times the command is to be repeated. -void IRsend::sendKelon(const uint64_t data, const uint16_t nbits, const uint16_t repeat) { +void IRsend::sendKelon(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { sendGeneric(kKelonHdrMark, kKelonHdrSpace, kKelonBitMark, kKelonOneSpace, kKelonBitMark, kKelonZeroSpace, @@ -90,7 +94,8 @@ bool IRrecv::decodeKelon(decode_results *results, uint16_t offset, /// @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? -IRKelonAc::IRKelonAc(const uint16_t pin, const bool inverted, const bool use_modulation) +IRKelonAc::IRKelonAc(const uint16_t pin, const bool inverted, + const bool use_modulation) : _irsend{pin, inverted, use_modulation}, _{} { stateReset(); } /// Reset the internals of the object to a known good state. @@ -181,7 +186,8 @@ void IRKelonAc::setFan(const uint8_t speed) { uint8_t fan = std::min(speed, kKelonFanMax); _previousFan = _.Fan; - // Note: Kelon fan speeds are backwards! This code maps the range 0,1:3 to 0,3:1 to save the API's user's sanity. + // Note: Kelon fan speeds are backwards! This code maps the range 0,1:3 to + // 0,3:1 to save the API's user's sanity. _.Fan = ((static_cast(fan) - 4) * -1) % 4; } @@ -207,16 +213,19 @@ void IRKelonAc::setDryGrade(const int8_t grade) { _.DehumidifierGrade = outval; } -/// Get the current dehumidification intensity setting. In smart mode, this controls the temperature adjustment. +/// Get the current dehumidification intensity setting. In smart mode, this +/// controls the temperature adjustment. /// @return The current dehumidification intensity. int8_t IRKelonAc::getDryGrade() const { - return (_.DehumidifierGrade & 0b011) * ((_.DehumidifierGrade & 0b100) ? -1 : 1); // NOLINT(cppcoreguidelines-narrowing-conversions) + return static_cast(_.DehumidifierGrade & 0b011) * + ((_.DehumidifierGrade & 0b100) ? -1 : 1); } /// Set the desired operation mode. /// @param[in] mode The desired operation mode. void IRKelonAc::setMode(const uint8_t mode) { - if (_.Mode == kKelonModeSmart || _.Mode == kKelonModeFan || _.Mode == kKelonModeDry) { + if (_.Mode == kKelonModeSmart || _.Mode == kKelonModeFan || + _.Mode == kKelonModeDry) { _.Temperature = _previousTemp; } if (_.SuperCoolEnabled1) { @@ -298,8 +307,10 @@ bool IRKelonAc::getSupercool() const { return _.SuperCoolEnabled1; } -/// Set the timer time and enable it. Timer is an off timer if the unit is on, it is an on timer if the unit is off. -/// @param[in] mins Nr. of minutes (only multiples of 30m are supported for < 10h, then only multiples of 60m) +/// Set the timer time and enable it. Timer is an off timer if the unit is on, +/// it is an on timer if the unit is off. +/// Only multiples of 30m are supported for < 10h, then only multiples of 60m +/// @param[in] mins Nr. of minutes void IRKelonAc::setTimer(uint16_t mins) { const uint16_t minutes = std::min(static_cast(mins), 24 * 60); @@ -315,8 +326,10 @@ void IRKelonAc::setTimer(uint16_t mins) { setTimerEnabled(true); } -/// Get the set timer. Timer set time is deleted once the command is sent, so calling this after send() will return 0. -/// The AC unit will continue keeping track of the remaining time unless it is later disabled. +/// Get the set timer. Timer set time is deleted once the command is sent, so +/// calling this after send() will return 0. +/// The AC unit will continue keeping track of the remaining time unless it is +/// later disabled. /// @return The timer set minutes uint16_t IRKelonAc::getTimer() const { if (_.TimerHours >= 10) { @@ -325,7 +338,8 @@ uint16_t IRKelonAc::getTimer() const { return (((uint16_t) _.TimerHours) * 60) + (_.TimerHalfHour ? 30 : 0); } -/// Enable or disable the timer. Note that in order to enable the timer the minutes must be set with setTimer(). +/// Enable or disable the timer. Note that in order to enable the timer the +/// minutes must be set with setTimer(). /// @param[in] on Whether to enable or disable the timer void IRKelonAc::setTimerEnabled(bool on) { _.TimerEnabled = on; @@ -436,7 +450,8 @@ stdAc::state_t IRKelonAc::toCommon() const { // Not supported. result.power = true; // N/A, AC only supports toggling it result.swingv = stdAc::swingv_t::kAuto; // N/A, AC only supports toggling it - result.swingh = stdAc::swingh_t::kOff; // N/A, horizontal air direction can only be set by manually adjusting the flaps + // N/A, horizontal air direction can only be set by manually adjusting it + result.swingh = stdAc::swingh_t::kOff; result.light = true; result.beep = true; result.quiet = false; @@ -451,15 +466,24 @@ stdAc::state_t IRKelonAc::toCommon() const { /// @return A String. String IRKelonAc::toString() const { String result = ""; - result.reserve(160); // Reserve some heap for the string to reduce fragging. + // Reserve some heap for the string to reduce fragging. + result.reserve(160); result += addTempToString(getTemp(), true, false); - result += addModeToString(_.Mode, kKelonModeSmart, kKelonModeCool, kKelonModeHeat, kKelonModeDry, kKelonModeFan); - result += addFanToString(_.Fan, kKelonFanMax, kKelonFanMin, kKelonFanAuto, -1, kKelonFanMedium, kKelonFanMax); + result += addModeToString(_.Mode, kKelonModeSmart, kKelonModeCool, + kKelonModeHeat, kKelonModeDry, kKelonModeFan); + result += addFanToString(_.Fan, kKelonFanMax, kKelonFanMin, kKelonFanAuto, + -1, kKelonFanMedium, kKelonFanMax); result += addBoolToString(_.SleepEnabled, kSleepStr); result += addSignedIntToString(getDryGrade(), kDryStr); - result += addLabeledString(getTimerEnabled() - ? (getTimer() > 0 ? minsToString(getTimer()) : kOnStr) - : kOffStr,kTimerStr); + result += addLabeledString( + getTimerEnabled() + ? ( + getTimer() > 0 + ? minsToString(getTimer()) + : kOnStr + ) + : kOffStr, + kTimerStr); result += addBoolToString(getSupercool(), kTurboStr); if (getTogglePower()) { result += addBoolToString(true, kPowerToggleStr); diff --git a/test/ir_Kelon_test.cpp b/test/ir_Kelon_test.cpp index 6025cb715..9ad4cce77 100644 --- a/test/ir_Kelon_test.cpp +++ b/test/ir_Kelon_test.cpp @@ -14,7 +14,8 @@ TEST(TestSendKelon, SendDataOnly) { IRsendTest irsend(kGpioUnused); irsend.begin(); - // Temp: 26C, Mode: 2 (Cool), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: Off, Turbo: Off + // Temp: 26C, Mode: 2 (Cool), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: Off, + // Turbo: Off irsend.reset(); irsend.sendKelon(0x82000683); EXPECT_EQ( @@ -26,10 +27,10 @@ TEST(TestSendKelon, SendDataOnly) { "m560s600m560s1680m560s600m560s600m560s600m560s600m560s600m560s600" "m560s600m560s600m560s600m560s600m560s600m560s600m560s600m560s600" "m560s600m560s600m560s100000", - irsend.outputStr() - ); + irsend.outputStr()); - // Temp: 18C, Mode: 2 (Cool), Fan: 1 (Low), Sleep: Off, Dry: 0, Timer: Off, Turbo: On + // Temp: 18C, Mode: 2 (Cool), Fan: 1 (Low), Sleep: Off, Dry: 0, Timer: Off, + // Turbo: On irsend.reset(); irsend.sendKelon(0x900002010683); EXPECT_EQ( @@ -41,10 +42,10 @@ TEST(TestSendKelon, SendDataOnly) { "m560s600m560s600m560s600m560s600m560s600m560s600m560s600" "m560s600m560s600m560s600m560s600m560s600m560s600m560s600" "m560s600m560s1680m560s600m560s600m560s1680m560s100000", - irsend.outputStr() - ); + irsend.outputStr()); - // Temp: 23C, Mode: 0 (Heat), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: Off, Turbo: Off, Power Toggle: On + // Temp: 23C, Mode: 0 (Heat), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: Off, + // Turbo: Off, Power Toggle: On irsend.reset(); irsend.sendKelon(0x50040683); EXPECT_EQ( @@ -56,10 +57,10 @@ TEST(TestSendKelon, SendDataOnly) { "m560s600m560s1680m560s600m560s600m560s600m560s600m560s600m560s600" "m560s600m560s600m560s600m560s600m560s600m560s600m560s600m560s600" "m560s600m560s600m560s600m560s100000", - irsend.outputStr() - ); + irsend.outputStr()); - // Temp: 26C, Mode: 2 (Cool), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: On (9.5h), Turbo: + // Temp: 26C, Mode: 2 (Cool), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: + // On (9.5h), Turbo: irsend.reset(); irsend.sendKelon(0x138A000683); EXPECT_EQ( @@ -72,10 +73,10 @@ TEST(TestSendKelon, SendDataOnly) { "m560s1680m560s600m560s600m560s1680m560s600m560s600m560s600" "m560s600m560s600m560s600m560s600m560s600m560s600m560s600" "m560s600m560s100000", - irsend.outputStr() - ); + irsend.outputStr()); - // Temp: 26C, Mode: 2 (Cool), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: On (15h), Turbo: Off: + // Temp: 26C, Mode: 2 (Cool), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: + // On (15h), Turbo: Off: irsend.reset(); irsend.sendKelon(0x198A000683); EXPECT_EQ( @@ -88,8 +89,7 @@ TEST(TestSendKelon, SendDataOnly) { "m560s1680m560s600m560s600m560s1680m560s1680m560s600" "m560s600m560s600m560s600m560s600m560s600m560s600m560s600" "m560s600m560s600m560s600m560s100000", - irsend.outputStr() - ); + irsend.outputStr()); } // Tests for decodeKelon(). @@ -106,9 +106,9 @@ TEST(TestDecodeKelon, Timer12HSmartMode) { EXPECT_EQ(KELON, irsend.capture.decode_type); EXPECT_EQ(kKelonBits, irsend.capture.bits); EXPECT_EQ( - "Temp: 25C, Mode: 1 (Auto), Fan: 3 (High), Sleep: Off, Dry: 0, Timer: 12:00, Turbo: Off", - IRAcUtils::resultAcToString(&irsend.capture) - ); + "Temp: 25C, Mode: 1 (Auto), Fan: 3 (High), Sleep: Off, Dry: 0, " + "Timer: 12:00, Turbo: Off", + IRAcUtils::resultAcToString(&irsend.capture)); stdAc::state_t result, prev; ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); } @@ -125,9 +125,9 @@ TEST(TestDecodeKelon, Timer5_5hSuperCoolMode) { EXPECT_EQ(KELON, irsend.capture.decode_type); EXPECT_EQ(kKelonBits, irsend.capture.bits); EXPECT_EQ( - "Temp: 18C, Mode: 2 (Cool), Fan: 1 (Low), Sleep: Off, Dry: 0, Timer: 05:30, Turbo: On", - IRAcUtils::resultAcToString(&irsend.capture) - ); + "Temp: 18C, Mode: 2 (Cool), Fan: 1 (Low), Sleep: Off, Dry: 0, " + "Timer: 05:30, Turbo: On", + IRAcUtils::resultAcToString(&irsend.capture)); stdAc::state_t result, prev; ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); } @@ -144,9 +144,9 @@ TEST(TestDecodeKelon, ChangeSettingsWithTimerSetHeatMode) { EXPECT_EQ(KELON, irsend.capture.decode_type); EXPECT_EQ(kKelonBits, irsend.capture.bits); EXPECT_EQ( - "Temp: 23C, Mode: 0 (Heat), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: On, Turbo: Off", - IRAcUtils::resultAcToString(&irsend.capture) - ); + "Temp: 23C, Mode: 0 (Heat), Fan: 0 (Auto), Sleep: Off, Dry: 0, " + "Timer: On, Turbo: Off", + IRAcUtils::resultAcToString(&irsend.capture)); stdAc::state_t result, prev; ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); } @@ -163,9 +163,9 @@ TEST(TestDecodeKelon, TestPowerToggleDryMode) { EXPECT_EQ(KELON, irsend.capture.decode_type); EXPECT_EQ(kKelonBits, irsend.capture.bits); EXPECT_EQ( - "Temp: 26C, Mode: 3 (Dry), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: Off, Turbo: Off, Power Toggle: On", - IRAcUtils::resultAcToString(&irsend.capture) - ); + "Temp: 26C, Mode: 3 (Dry), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer:" + " Off, Turbo: Off, Power Toggle: On", + IRAcUtils::resultAcToString(&irsend.capture)); stdAc::state_t result, prev; ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); } @@ -182,9 +182,9 @@ TEST(TestDecodeKelon, TestSwingToggleDryMode) { EXPECT_EQ(KELON, irsend.capture.decode_type); EXPECT_EQ(kKelonBits, irsend.capture.bits); EXPECT_EQ( - "Temp: 26C, Mode: 3 (Dry), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: Off, Turbo: Off, Swing(V) Toggle: On", - IRAcUtils::resultAcToString(&irsend.capture) - ); + "Temp: 26C, Mode: 3 (Dry), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer:" + " Off, Turbo: Off, Swing(V) Toggle: On", + IRAcUtils::resultAcToString(&irsend.capture)); stdAc::state_t result, prev; ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); } @@ -201,9 +201,9 @@ TEST(TestDecodeKelon, TestDryGradeNegativeValue) { EXPECT_EQ(KELON, irsend.capture.decode_type); EXPECT_EQ(kKelonBits, irsend.capture.bits); EXPECT_EQ( - "Temp: 26C, Mode: 3 (Dry), Fan: 0 (Auto), Sleep: Off, Dry: -2, Timer: Off, Turbo: Off", - IRAcUtils::resultAcToString(&irsend.capture) - ); + "Temp: 26C, Mode: 3 (Dry), Fan: 0 (Auto), Sleep: Off, Dry: -2," + " Timer: Off, Turbo: Off", + IRAcUtils::resultAcToString(&irsend.capture)); stdAc::state_t result, prev; ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); } @@ -398,4 +398,4 @@ TEST(TestUtils, Housekeeping) { ASSERT_TRUE(IRac::isProtocolSupported(decode_type_t::KELON)); ASSERT_EQ(kKelonBits, IRsend::defaultBits(decode_type_t::KELON)); ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::KELON)); -} \ No newline at end of file +} From 8553005f7a29296f465139f07a4e60ad5bcf538c Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Wed, 30 Jun 2021 22:57:34 +0200 Subject: [PATCH 23/36] Kelon: Add gap between ensurePower commands --- src/ir_Kelon.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index 7ae33608b..9073fc2fd 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -136,6 +136,8 @@ void IRKelonAc::ensurePower(bool on) { setMode(kKelonModeSmart); send(); + _irsend.space(kKelonGap); + setDryGrade(previousDry); setMode(_previousMode); From c3009a236474d7445077a2024c7897afd85c0484 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Wed, 30 Jun 2021 23:39:59 +0200 Subject: [PATCH 24/36] Kelon: Send ensure power off command separately --- src/ir_Kelon.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index 9073fc2fd..39e441b23 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -140,12 +140,15 @@ void IRKelonAc::ensurePower(bool on) { setDryGrade(previousDry); setMode(_previousMode); + send(); - // Now we're sure it's on. Turn it back off. + // Now we're sure it's on. Turn it back off. The AC seems to turn back on if + // we don't send this separately if (!on) { + _irsend.space(kKelonGap); setTogglePower(true); + send(); } - send(); } #endif // SEND_KELON From 47948e27af2c5b7dd32eaacc94ef3e9fe6a28ad7 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Wed, 30 Jun 2021 23:43:06 +0200 Subject: [PATCH 25/36] Kelon: Mark Kelon as stable --- src/ir_Kelon.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index 39e441b23..e9483022e 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -42,7 +42,7 @@ const uint16_t kKelonFreq = 38000; #if SEND_KELON /// Send a Kelon message. -/// Status: Beta / Should be working. +/// Status: STABLE / Working. /// @param[in] data The data to be transmitted. /// @param[in] nbits Nr. of bits of data to be sent. /// @param[in] repeat The number of times the command is to be repeated. @@ -60,7 +60,7 @@ void IRsend::sendKelon(const uint64_t data, const uint16_t nbits, #if DECODE_KELON /// Decode the supplied Kelon message. -/// Status: Beta / Should be working. +/// Status: STABLE / Working. /// @param[in,out] results Ptr to the data to decode & where to store the result /// @param[in] offset The starting index to use when attempting to decode the /// raw data. Typically/Defaults to kStartOffset. From 1de5d685d6a3ff9f6b506cdca8d939e080afc27d Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Wed, 30 Jun 2021 23:56:37 +0200 Subject: [PATCH 26/36] Kelon: Lint --- src/IRac.cpp | 3 ++- src/IRac.h | 5 +++-- src/ir_Kelon.cpp | 10 +++++----- src/ir_Kelon.h | 15 +++++++++------ 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index d57bae832..936838f2b 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -2619,7 +2619,8 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { case KELON: { IRKelonAc ac(_pin, _inverted, _modulation); kelon(&ac, send.mode, send.degrees, send.fanspeed, send.sleep, - send.turbo, 0, send.power, send.swingv != stdAc::swingv_t::kOff); + send.turbo, 0, send.power, + send.swingv != stdAc::swingv_t::kOff); break; } #endif diff --git a/src/IRac.h b/src/IRac.h index 77c6c2e17..3e0d6720a 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -293,8 +293,9 @@ void electra(IRElectraAc *ac, #if SEND_KELON void kelon(IRKelonAc *ac, const stdAc::opmode_t mode, const float degrees, - const stdAc::fanspeed_t fan, const bool sleep, const bool superCool, - const int8_t dryGrade, const bool togglePower, const bool toggleSwing); + const stdAc::fanspeed_t fan, const bool sleep, + const bool superCool, const int8_t dryGrade, + const bool togglePower, const bool toggleSwing); #endif // SEND_KELON #if SEND_KELVINATOR void kelvinator(IRKelvinatorAC *ac, diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index e9483022e..829160d9b 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -56,7 +56,7 @@ void IRsend::sendKelon(const uint64_t data, const uint16_t nbits, repeat, 50); } -#endif // SEND_KELON +#endif // SEND_KELON #if DECODE_KELON /// Decode the supplied Kelon message. @@ -151,7 +151,7 @@ void IRKelonAc::ensurePower(bool on) { } } -#endif // SEND_KELON +#endif // SEND_KELON /// Set up hardware to be able to send a message. void IRKelonAc::begin() { @@ -251,7 +251,7 @@ void IRKelonAc::setMode(const uint8_t mode) { case kKelonModeDry: case kKelonModeFan: setTemp(25); - //fallthrough + // fallthrough case kKelonModeCool: case kKelonModeHeat: _.Mode = mode; @@ -453,8 +453,8 @@ stdAc::state_t IRKelonAc::toCommon() const { result.turbo = getSupercool(); result.sleep = getSleep() ? 0 : -1; // Not supported. - result.power = true; // N/A, AC only supports toggling it - result.swingv = stdAc::swingv_t::kAuto; // N/A, AC only supports toggling it + result.power = true; // N/A, AC only supports toggling it + result.swingv = stdAc::swingv_t::kAuto; // N/A, AC only supports toggling it // N/A, horizontal air direction can only be set by manually adjusting it result.swingh = stdAc::swingh_t::kOff; result.light = true; diff --git a/src/ir_Kelon.h b/src/ir_Kelon.h index 69c901858..a88eff639 100644 --- a/src/ir_Kelon.h +++ b/src/ir_Kelon.h @@ -2,7 +2,8 @@ /// @file /// @brief Support for Kelan AC protocol. -/// Both sending and decoding should be functional for models of series KELON ON/OFF 9000-12000. +/// Both sending and decoding should be functional for models of series KELON +/// ON/OFF 9000-12000. /// All features of the standard remote are implemented. /// /// @note Unsupported: @@ -54,7 +55,8 @@ const uint8_t kKelonModeDry = 3; // (temp = 25C, but not shown) const uint8_t kKelonModeFan = 4; // (temp = 25C, but not shown) const uint8_t kKelonFanAuto = 0; // Note! Kelon fan speeds are actually 0:AUTO, 1:MAX, 2:MED, 3:MIN -// Since this is insane, I decided to invert them in the public API, they are converted back in setFan/getFan +// Since this is insane, I decided to invert them in the public API, they are +// converted back in setFan/getFan const uint8_t kKelonFanMin = 1; const uint8_t kKelonFanMedium = 2; const uint8_t kKelonFanMax = 3; @@ -66,8 +68,9 @@ const uint8_t kKelonMaxTemp = 32; class IRKelonAc { -public: - explicit IRKelonAc(uint16_t pin, bool inverted = false, bool use_modulation = true); + public: + explicit IRKelonAc(uint16_t pin, bool inverted = false, + bool use_modulation = true); void stateReset(void); @@ -148,7 +151,7 @@ class IRKelonAc { String toString(void) const; -private: + private: #ifndef UNIT_TEST IRsend _irsend; ///< Instance of the IR send class #else // UNIT_TEST @@ -164,4 +167,4 @@ class IRKelonAc { uint8_t _previousFan = kKelonFanAuto; }; -#endif // IR_KELON_H_ +#endif // IR_KELON_H_ From 892abf99b591ed87401afffbe93f6d839235006e Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Thu, 1 Jul 2021 00:08:17 +0200 Subject: [PATCH 27/36] Fix rebase issues --- src/IRremoteESP8266.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h index 08e11767a..da389fccf 100644 --- a/src/IRremoteESP8266.h +++ b/src/IRremoteESP8266.h @@ -924,10 +924,10 @@ enum decode_type_t { XMP, TRUMA, // 100 HAIER_AC176, - KELON, TEKNOPOINT, + KELON, // Add new entries before this one, and update it to point to the last entry. - kLastDecodeType = TEKNOPOINT, + kLastDecodeType = KELON, }; // Message lengths & required repeat values From 9a97cb62f9ab529f292095fce9868bb033ccb639 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Thu, 1 Jul 2021 19:47:02 +0200 Subject: [PATCH 28/36] Kelon: Address PR review change requests --- src/IRac.cpp | 24 +++++++++++------------- src/IRac.h | 9 ++++----- src/IRrecv.cpp | 2 +- src/IRutils.h | 2 +- src/ir_Kelon.cpp | 5 +---- 5 files changed, 18 insertions(+), 24 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index 936838f2b..c4df665fd 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -1257,24 +1257,24 @@ void IRac::hitachi424(IRHitachiAc424 *ac, #if SEND_KELON /// Send a Kelon A/C message with the supplied settings. /// @param[in, out] ac A Ptr to an IRKelonAc object to use. +/// @param[in] togglePower Whether to toggle the unit's power /// @param[in] mode The operation mode setting. +/// @param[in] dryGrade The dehumidification intensity grade /// @param[in] degrees The temperature setting in degrees. /// @param[in] fan The speed setting for the fan. -/// @param[in] sleep Run the device in sleep/quiet mode. /// @param[in] superCool Run the device in Super cooling mode. -/// @param[in] dryGrade The dehumidification intensity grade -/// @param[in] togglePower Whether to toggle the unit's power /// @param[in] toggleSwing Whether to toggle the swing setting -void IRac::kelon(IRKelonAc *ac, - const stdAc::opmode_t mode, const float degrees, - const stdAc::fanspeed_t fan, const bool sleep, - const bool superCool, const int8_t dryGrade, - const bool togglePower = false, const bool toggleSwing = false) { +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on +void IRac::kelon(IRKelonAc *ac, const bool togglePower, + const stdAc::opmode_t mode, const int8_t dryGrade, + const float degrees, const stdAc::fanspeed_t fan, + const bool toggleSwing, const bool superCool, + const int16_t sleep) { ac->begin(); ac->setMode(IRKelonAc::convertMode(mode)); ac->setFan(IRKelonAc::convertFan(fan)); ac->setTemp(static_cast(degrees)); - ac->setSleep(sleep); + ac->setSleep(sleep >= 0); ac->setSupercool(superCool); ac->setDryGrade(dryGrade); @@ -2618,9 +2618,7 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { #if SEND_KELON case KELON: { IRKelonAc ac(_pin, _inverted, _modulation); - kelon(&ac, send.mode, send.degrees, send.fanspeed, send.sleep, - send.turbo, 0, send.power, - send.swingv != stdAc::swingv_t::kOff); + kelon(&ac, send.power,; break; } #endif @@ -3832,7 +3830,7 @@ namespace IRAcUtils { *result = ac.toCommon(); break; } -#endif +#endif // DECODE_KELON #if DECODE_KELVINATOR case decode_type_t::KELVINATOR: { IRKelvinatorAC ac(kGpioUnused); diff --git a/src/IRac.h b/src/IRac.h index 3e0d6720a..8f765ccbe 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -291,11 +291,10 @@ void electra(IRElectraAc *ac, const stdAc::swingv_t swingv); #endif // SEND_HITACHI_AC424 #if SEND_KELON - void kelon(IRKelonAc *ac, - const stdAc::opmode_t mode, const float degrees, - const stdAc::fanspeed_t fan, const bool sleep, - const bool superCool, const int8_t dryGrade, - const bool togglePower, const bool toggleSwing); + void kelon(IRKelonAc *ac, const bool togglePower, const stdAc::opmode_t mode, + const int8_t dryGrade, const float degrees, + const stdAc::fanspeed_t fan, const bool toggleSwing, + const bool superCool, const int16_t sleep); #endif // SEND_KELON #if SEND_KELVINATOR void kelvinator(IRKelvinatorAC *ac, diff --git a/src/IRrecv.cpp b/src/IRrecv.cpp index 7962a305f..4a6281b45 100644 --- a/src/IRrecv.cpp +++ b/src/IRrecv.cpp @@ -1020,7 +1020,7 @@ bool IRrecv::decode(decode_results *results, irparams_t *save, #endif // DECODE_TEKNOPOINT #if DECODE_KELON DPRINTLN("Attempting Kelon decode"); - if (decodeKelon(results, offset, kKelonBits)) return true; + if (decodeKelon(results, offset)) return true; #endif // DECODE_KELON // Typically new protocols are added above this line. } diff --git a/src/IRutils.h b/src/IRutils.h index 738cf787d..fdda6d7ae 100644 --- a/src/IRutils.h +++ b/src/IRutils.h @@ -51,7 +51,7 @@ namespace irutils { String addIntToString(const uint16_t value, const String label, const bool precomma = true); String addSignedIntToString(const int16_t value, const String label, - const bool precomma = true); + const bool precomma = true); String modelToStr(const decode_type_t protocol, const int16_t model); String addModelToString(const decode_type_t protocol, const int16_t model, const bool precomma = true); diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index 829160d9b..44b840f02 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -36,7 +36,7 @@ const uint16_t kKelonHdrSpace = 4600; const uint16_t kKelonBitMark = 560; const uint16_t kKelonOneSpace = 1680; const uint16_t kKelonZeroSpace = 600; -const uint32_t kKelonGap = kDefaultMessageGap; +const uint32_t kKelonGap = 2 * kDefaultMessageGap; const uint16_t kKelonFreq = 38000; #if SEND_KELON @@ -136,8 +136,6 @@ void IRKelonAc::ensurePower(bool on) { setMode(kKelonModeSmart); send(); - _irsend.space(kKelonGap); - setDryGrade(previousDry); setMode(_previousMode); send(); @@ -145,7 +143,6 @@ void IRKelonAc::ensurePower(bool on) { // Now we're sure it's on. Turn it back off. The AC seems to turn back on if // we don't send this separately if (!on) { - _irsend.space(kKelonGap); setTogglePower(true); send(); } From badcad31026350bcd792bab63857491f018d92aa Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Thu, 1 Jul 2021 19:48:28 +0200 Subject: [PATCH 29/36] Kelon: Add self to Contributors.md --- .github/Contributors.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/Contributors.md b/.github/Contributors.md index 811bfea84..afa0a51e9 100644 --- a/.github/Contributors.md +++ b/.github/Contributors.md @@ -18,6 +18,7 @@ - [Mark Kuchel](https://github.com/kuchel77) - [Christian Nilsson](https://github.com/NiKiZe) - [Zhongxian Li](https://github.com/siriuslzx) +- [Davide Depau](https://github.com/Depau) All contributors can be found on the [contributors site](https://github.com/crankyoldgit/IRremoteESP8266/graphs/contributors). From 7da0f7ee8b17668936cc87b61b34cc7aceba6ca7 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Thu, 1 Jul 2021 19:59:38 +0200 Subject: [PATCH 30/36] Kelon: Address PR review change requests --- src/IRac.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index c4df665fd..145f7d5f6 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -2286,6 +2286,13 @@ stdAc::state_t IRac::handleToggles(const stdAc::state_t desired, else result.swingv = stdAc::swingv_t::kOff; // No change, so no toggle. break; + case decode_type_t::KELON: + if ((desired.swingv == stdAc::swingv_t::kOff) ^ + (prev->swingv == stdAc::swingv_t::kOff)) // It changed, so toggle. + result.swingv = stdAc::swingv_t::kAuto; + else + result.swingv = stdAc::swingv_t::kOff; // No change, so no toggle. + // FALL-THRU case decode_type_t::AIRWELL: case decode_type_t::DAIKIN64: case decode_type_t::PANASONIC_AC32: @@ -2297,14 +2304,6 @@ stdAc::state_t IRac::handleToggles(const stdAc::state_t desired, if (desired.model == panasonic_ac_remote_model_t::kPanasonicCkp) result.power = desired.power ^ prev->power; break; - case decode_type_t::KELON: - result.power = desired.power ^ prev->power; - if ((desired.swingv == stdAc::swingv_t::kOff) ^ - (prev->swingv == stdAc::swingv_t::kOff)) // It changed, so toggle. - result.swingv = stdAc::swingv_t::kAuto; - else - result.swingv = stdAc::swingv_t::kOff; // No change, so no toggle. - break; default: {}; } From f39607260666bd72231b2e2a4929bdb262491f58 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Thu, 1 Jul 2021 20:04:32 +0200 Subject: [PATCH 31/36] Kelon: Allow providing prev common state --- src/ir_Kelon.cpp | 8 +++++--- src/ir_Kelon.h | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index 44b840f02..c42d95b5f 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -439,7 +439,7 @@ stdAc::fanspeed_t IRKelonAc::toCommonFanSpeed(const uint8_t speed) { /// Convert the internal A/C object state to it's stdAc::state_t equivalent. /// @return A stdAc::state_t containing the current settings. -stdAc::state_t IRKelonAc::toCommon() const { +stdAc::state_t IRKelonAc::toCommon(const stdAc::state_t *prev = nullptr) const { stdAc::state_t result{}; result.protocol = decode_type_t::KELON; result.model = -1; // Unused. @@ -450,8 +450,10 @@ stdAc::state_t IRKelonAc::toCommon() const { result.turbo = getSupercool(); result.sleep = getSleep() ? 0 : -1; // Not supported. - result.power = true; // N/A, AC only supports toggling it - result.swingv = stdAc::swingv_t::kAuto; // N/A, AC only supports toggling it + // N/A, AC only supports toggling it + result.power = prev == nullptr || prev->power; + // N/A, AC only supports toggling it + result.swingv = prev != nullptr ? prev->swingv : stdAc::swingv_t::kAuto; // N/A, horizontal air direction can only be set by manually adjusting it result.swingh = stdAc::swingh_t::kOff; result.light = true; diff --git a/src/ir_Kelon.h b/src/ir_Kelon.h index a88eff639..2c64c880b 100644 --- a/src/ir_Kelon.h +++ b/src/ir_Kelon.h @@ -147,7 +147,7 @@ class IRKelonAc { static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); - stdAc::state_t toCommon(void) const; + stdAc::state_t toCommon(const stdAc::state_t *prev) const; String toString(void) const; From 4743f8b071cc44d275d1e33adb98c049ab5832ae Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Thu, 1 Jul 2021 20:18:11 +0200 Subject: [PATCH 32/36] Kelon: Fix typos --- src/IRac.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index 145f7d5f6..4988b67d7 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -1262,8 +1262,8 @@ void IRac::hitachi424(IRHitachiAc424 *ac, /// @param[in] dryGrade The dehumidification intensity grade /// @param[in] degrees The temperature setting in degrees. /// @param[in] fan The speed setting for the fan. -/// @param[in] superCool Run the device in Super cooling mode. /// @param[in] toggleSwing Whether to toggle the swing setting +/// @param[in] superCool Run the device in Super cooling mode. /// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on void IRac::kelon(IRKelonAc *ac, const bool togglePower, const stdAc::opmode_t mode, const int8_t dryGrade, @@ -2617,7 +2617,8 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { #if SEND_KELON case KELON: { IRKelonAc ac(_pin, _inverted, _modulation); - kelon(&ac, send.power,; + kelon(&ac, send.power, send.mode, 0, send.degrees, send.fanspeed, + send.swingv != stdAc::swingv_t::kOff, send.turbo, send.sleep); break; } #endif From b8c8174e1176012446d5fb7448fc25226fe9a673 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Thu, 1 Jul 2021 20:32:35 +0200 Subject: [PATCH 33/36] Kelon: Move toCommon() default value to header --- src/ir_Kelon.cpp | 2 +- src/ir_Kelon.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index c42d95b5f..00041126a 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -439,7 +439,7 @@ stdAc::fanspeed_t IRKelonAc::toCommonFanSpeed(const uint8_t speed) { /// Convert the internal A/C object state to it's stdAc::state_t equivalent. /// @return A stdAc::state_t containing the current settings. -stdAc::state_t IRKelonAc::toCommon(const stdAc::state_t *prev = nullptr) const { +stdAc::state_t IRKelonAc::toCommon(const stdAc::state_t *prev) const { stdAc::state_t result{}; result.protocol = decode_type_t::KELON; result.model = -1; // Unused. diff --git a/src/ir_Kelon.h b/src/ir_Kelon.h index 2c64c880b..498650623 100644 --- a/src/ir_Kelon.h +++ b/src/ir_Kelon.h @@ -147,7 +147,7 @@ class IRKelonAc { static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); - stdAc::state_t toCommon(const stdAc::state_t *prev) const; + stdAc::state_t toCommon(const stdAc::state_t *prev = nullptr) const; String toString(void) const; From 792025fc6e4b1c473d2cb34f15dfaa480aa29078 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Thu, 1 Jul 2021 20:33:16 +0200 Subject: [PATCH 34/36] Kelon: Fix gap time in tests --- test/ir_Kelon_test.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/ir_Kelon_test.cpp b/test/ir_Kelon_test.cpp index 9ad4cce77..8c16dbeb5 100644 --- a/test/ir_Kelon_test.cpp +++ b/test/ir_Kelon_test.cpp @@ -26,7 +26,7 @@ TEST(TestSendKelon, SendDataOnly) { "m560s600m560s600m560s600m560s1680m560s600m560s600m560s600m560s600" "m560s600m560s1680m560s600m560s600m560s600m560s600m560s600m560s600" "m560s600m560s600m560s600m560s600m560s600m560s600m560s600m560s600" - "m560s600m560s600m560s100000", + "m560s600m560s600m560s200000", irsend.outputStr()); // Temp: 18C, Mode: 2 (Cool), Fan: 1 (Low), Sleep: Off, Dry: 0, Timer: Off, @@ -41,7 +41,7 @@ TEST(TestSendKelon, SendDataOnly) { "m560s600m560s600m560s600m560s600m560s1680m560s600m560s600m560s600" "m560s600m560s600m560s600m560s600m560s600m560s600m560s600" "m560s600m560s600m560s600m560s600m560s600m560s600m560s600" - "m560s600m560s1680m560s600m560s600m560s1680m560s100000", + "m560s600m560s1680m560s600m560s600m560s1680m560s200000", irsend.outputStr()); // Temp: 23C, Mode: 0 (Heat), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: Off, @@ -56,7 +56,7 @@ TEST(TestSendKelon, SendDataOnly) { "m560s600m560s600m560s600m560s600m560s600m560s600m560s600m560s1680" "m560s600m560s1680m560s600m560s600m560s600m560s600m560s600m560s600" "m560s600m560s600m560s600m560s600m560s600m560s600m560s600m560s600" - "m560s600m560s600m560s600m560s100000", + "m560s600m560s600m560s600m560s200000", irsend.outputStr()); // Temp: 26C, Mode: 2 (Cool), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: @@ -72,7 +72,7 @@ TEST(TestSendKelon, SendDataOnly) { "m560s600m560s1680m560s600m560s600m560s600m560s1680m560s1680" "m560s1680m560s600m560s600m560s1680m560s600m560s600m560s600" "m560s600m560s600m560s600m560s600m560s600m560s600m560s600" - "m560s600m560s100000", + "m560s600m560s200000", irsend.outputStr()); // Temp: 26C, Mode: 2 (Cool), Fan: 0 (Auto), Sleep: Off, Dry: 0, Timer: @@ -88,7 +88,7 @@ TEST(TestSendKelon, SendDataOnly) { "m560s1680m560s600m560s1680m560s600m560s600m560s600m560s1680" "m560s1680m560s600m560s600m560s1680m560s1680m560s600" "m560s600m560s600m560s600m560s600m560s600m560s600m560s600" - "m560s600m560s600m560s600m560s100000", + "m560s600m560s600m560s600m560s200000", irsend.outputStr()); } From 09ebeb869494817decde6c5a621a9bbf2a67a7b6 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Fri, 2 Jul 2021 23:24:33 +0200 Subject: [PATCH 35/36] Kelon: Handle toggles in toCommon() --- src/ir_Kelon.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ir_Kelon.cpp b/src/ir_Kelon.cpp index 00041126a..39b61744e 100644 --- a/src/ir_Kelon.cpp +++ b/src/ir_Kelon.cpp @@ -451,10 +451,13 @@ stdAc::state_t IRKelonAc::toCommon(const stdAc::state_t *prev) const { result.sleep = getSleep() ? 0 : -1; // Not supported. // N/A, AC only supports toggling it - result.power = prev == nullptr || prev->power; + result.power = (prev == nullptr || prev->power) ^ _.PowerToggle; // N/A, AC only supports toggling it - result.swingv = prev != nullptr ? prev->swingv : stdAc::swingv_t::kAuto; - // N/A, horizontal air direction can only be set by manually adjusting it + result.swingv = stdAc::swingv_t::kAuto; + if (prev != nullptr && + (prev->swingv != stdAc::swingv_t::kAuto) ^ _.SwingVToggle) { + result.swingv = stdAc::swingv_t::kOff; + } result.swingh = stdAc::swingh_t::kOff; result.light = true; result.beep = true; From e4c66dcca99bdfc90e21dd92cef43655384085a5 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Fri, 2 Jul 2021 23:47:42 +0200 Subject: [PATCH 36/36] Kelon: Add tests for toCommon() toggles --- test/ir_Kelon_test.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/ir_Kelon_test.cpp b/test/ir_Kelon_test.cpp index 8c16dbeb5..7212d49ee 100644 --- a/test/ir_Kelon_test.cpp +++ b/test/ir_Kelon_test.cpp @@ -391,6 +391,33 @@ TEST(TestIRKelonClass, toCommon) { EXPECT_EQ(0, common.sleep); } +TEST(TestIRKelonClass, toCommonToggles) { + IRKelonAc ac(kGpioUnused); + + stdAc::state_t common = ac.toCommon(); + stdAc::state_t prev = common; + + EXPECT_TRUE(common.power); + EXPECT_EQ(stdAc::swingv_t::kAuto, common.swingv); + + ac.setTogglePower(true); + ac.setToggleSwingVertical(true); + + common = ac.toCommon(&prev); + prev = common; + + EXPECT_FALSE(common.power); + EXPECT_EQ(stdAc::swingv_t::kOff, common.swingv); + + ac.setTogglePower(true); + ac.setToggleSwingVertical(true); + + common = ac.toCommon(&prev); + + EXPECT_TRUE(common.power); + EXPECT_EQ(stdAc::swingv_t::kAuto, common.swingv); +} + TEST(TestUtils, Housekeeping) { ASSERT_EQ("KELON", typeToString(decode_type_t::KELON)); ASSERT_EQ(decode_type_t::KELON, strToDecodeType("KELON"));