diff --git a/src/IRac.cpp b/src/IRac.cpp index 430814887..11d161e2f 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -244,6 +244,9 @@ bool IRac::isProtocolSupported(const decode_type_t protocol) { #if SEND_ELECTRA_AC case decode_type_t::ELECTRA_AC: #endif +#if SEND_ELECTROLUX_AC + case decode_type_t::ELECTROLUX_AC: +#endif // SEND_ELECTROLUX_AC #if SEND_FUJITSU_AC case decode_type_t::FUJITSU_AC: #endif @@ -375,9 +378,6 @@ bool IRac::isProtocolSupported(const decode_type_t protocol) { #if SEND_WHIRLPOOL_AC case decode_type_t::WHIRLPOOL_AC: #endif -#if SEND_ELECTROLUX_AC - case decode_type_t::ELETROLUX_AC: -#endif // SEND_ELECTROLUX_AC return true; default: return false; @@ -3678,10 +3678,12 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { } #endif // SEND_TRANSCOLD_AC #if SEND_ELECTROLUX_AC - case ELETROLUX_AC: + case ELECTROLUX_AC: { IRElectroluxAc ac(_pin, _inverted, _modulation); - electrolux(&ac, send.power, send.mode, send.celsius, send.degrees, send.fanspeed, send.quiet); + electrolux(&ac, send.power, send.mode, + send.celsius, send.degrees, + send.fanspeed, send.quiet); break; } #endif // SEND_ELECTROLUX_AC @@ -4565,7 +4567,7 @@ namespace IRAcUtils { } #endif // DECODE_YORK #if DECODE_ELECTROLUX_AC - case decode_type_t::ELETROLUX_AC: { + case decode_type_t::ELECTROLUX_AC: { IRElectroluxAc ac(kGpioUnused); ac.setRaw(result->value); // ELETROLUX_AC uses value instead of state. return ac.toString(); @@ -5116,7 +5118,7 @@ namespace IRAcUtils { } #endif // DECODE_YORK #if DECODE_ELECTROLUX_AC - case decode_type_t::ELETROLUX_AC: { + case decode_type_t::ELECTROLUX_AC: { IRCarrierAc64 ac(kGpioUnused); ac.setRaw(decode->value); // Uses value instead of state. *result = ac.toCommon(); diff --git a/src/IRac.h b/src/IRac.h index 3a32f61b0..3951c63a4 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -22,6 +22,7 @@ #include "ir_Fujitsu.h" #include "ir_Ecoclim.h" #include "ir_Electra.h" +#include "ir_Electrolux.h" #include "ir_Goodweather.h" #include "ir_Gree.h" #include "ir_Haier.h" @@ -50,7 +51,6 @@ #include "ir_Voltas.h" #include "ir_Whirlpool.h" #include "ir_York.h" -#include "ir_Electrolux.h" // Constants const int8_t kGpioUnused = -1; ///< A placeholder for not using an actual GPIO. diff --git a/src/IRrecv.cpp b/src/IRrecv.cpp index 8ed9cdd62..c61d721cd 100644 --- a/src/IRrecv.cpp +++ b/src/IRrecv.cpp @@ -1192,7 +1192,7 @@ bool IRrecv::decode(decode_results *results, irparams_t *save, #if DECODE_ELECTROLUX_AC DPRINTLN("Attempting Electrolux AC decode"); if (decodeElectroluxAc(results, offset)) return true; -#endif //DECODE_ELECTROLUX_AC +#endif // DECODE_ELECTROLUX_AC // Typically new protocols are added above this line. } #if DECODE_HASH diff --git a/src/IRrecv.h b/src/IRrecv.h index 50094e43a..cd6784108 100644 --- a/src/IRrecv.h +++ b/src/IRrecv.h @@ -890,10 +890,11 @@ class IRrecv { const bool strict = true); #endif // DECODE_BLUESTARHEAVY #if DECODE_ELECTROLUX_AC - bool decodeElectroluxAc(decode_results *results, uint16_t offset = kStartOffset, - const uint16_t nbits = kElectroluxAcBits, - const bool strict = true); -#endif // DECODE_ELECTROLUX_AC + bool decodeElectroluxAc(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kElectroluxAcBits, + const bool strict = true); +#endif // DECODE_ELECTROLUX_AC }; #endif // IRRECV_H_ diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h index 06031396e..95ddf3a5d 100644 --- a/src/IRremoteESP8266.h +++ b/src/IRremoteESP8266.h @@ -1152,9 +1152,9 @@ enum decode_type_t { CARRIER_AC84, // 125 YORK, BLUESTARHEAVY, - ELETROLUX_AC, + ELECTROLUX_AC, // Add new entries before this one, and update it to point to the last entry. - kLastDecodeType = ELETROLUX_AC, + kLastDecodeType = ELECTROLUX_AC, }; // Message lengths & required repeat values diff --git a/src/IRsend.cpp b/src/IRsend.cpp index 1864ee2a3..e02900365 100644 --- a/src/IRsend.cpp +++ b/src/IRsend.cpp @@ -562,6 +562,7 @@ uint16_t IRsend::minRepeats(const decode_type_t protocol) { case COOLIX: case COOLIX48: case ELITESCREENS: + case ELECTROLUX_AC: case GICABLE: case INAX: case MIDEA24: @@ -646,6 +647,7 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) { case ARRIS: case CARRIER_AC: case ELITESCREENS: + case ELECTROLUX_AC: case EPSON: case NEC: case NEC_LIKE: @@ -915,6 +917,11 @@ bool IRsend::send(const decode_type_t type, const uint64_t data, sendEpson(data, nbits, min_repeat); break; #endif +#if SEND_ELECTROLUX_AC + case ELECTROLUX_AC: + sendElectroluxAc(data, nbits, min_repeat); + break; +#endif #if SEND_GICABLE case GICABLE: sendGICable(data, nbits, min_repeat); diff --git a/src/ir_Electrolux.cpp b/src/ir_Electrolux.cpp index a7a8406c2..b30675395 100644 --- a/src/ir_Electrolux.cpp +++ b/src/ir_Electrolux.cpp @@ -6,7 +6,6 @@ // Brand: Electrolux, Model: Electrolux EACM EZ/N3 #include "ir_Electrolux.h" -#include #include #include "IRac.h" #include "IRrecv.h" @@ -20,7 +19,7 @@ const uint16_t kElectroluxAcBitMark = 752; const uint16_t kElectroluxAcHdrSpace = 2700; const uint16_t kElectroluxAcOneSpace = 2149; const uint16_t kElectroluxAcZeroSpace = 756; -const uint16_t kElectroluxAcFreq = 38000; // Hz. (Guessing the most common frequency.) +const uint16_t kElectroluxAcFreq = 38000; const uint16_t kElectroluxAcOverhead = 3; #if SEND_ELECTROLUX_AC @@ -30,20 +29,29 @@ const uint16_t kElectroluxAcOverhead = 3; /// @param[in] data containing the IR command. /// @param[in] nbits Nr. of bits to send. usually kElectroluxBits /// @param[in] repeat Nr. of times the message is to be repeated. -void IRsend::sendElectroluxAc(const uint64_t data, const uint16_t nbits, const uint16_t repeat) { - enableIROut(kElectroluxAcFreq); - for (uint16_t r = 0; r <= repeat; r++) { - uint64_t send_data = data; - // Header - mark(kElectroluxAcHdrMark); - space(kElectroluxAcHdrSpace); - // Data Section - sendData(kElectroluxAcBitMark, kElectroluxAcOneSpace, kElectroluxAcBitMark, kElectroluxAcZeroSpace, send_data, 32, true); - send_data >>= 32; - // Footer - mark(kElectroluxAcBitMark); - space(kDefaultMessageGap); // A 100% made up guess of the gap between messages. - } +void IRsend::sendElectroluxAc( + const uint64_t data, + const uint16_t nbits, + const uint16_t repeat +) { + enableIROut(kElectroluxAcFreq); + for (uint16_t r = 0; r <= repeat; r++) { + uint64_t send_data = data; + // Header + mark(kElectroluxAcHdrMark); + space(kElectroluxAcHdrSpace); + // Data Section + sendData(kElectroluxAcBitMark, kElectroluxAcOneSpace, + kElectroluxAcBitMark, kElectroluxAcZeroSpace, + send_data, nbits, true); + + send_data >>= 32; + // Footer + mark(kElectroluxAcBitMark); + + // A 100% made up guess of the gap between messages. + space(kDefaultMessageGap); + } } #endif // SEND_ELECTROLUX @@ -57,45 +65,49 @@ void IRsend::sendElectroluxAc(const uint64_t data, const uint16_t nbits, const u /// @param[in] nbits The number of data bits to expect. /// @param[in] strict Flag indicating if we should perform strict matching. /// @return A boolean. True if it can decode it, false if it can't. -bool IRrecv::decodeElectroluxAc(decode_results *results, uint16_t offset, const uint16_t nbits, const bool strict) { - - if (results->rawlen < 2 * nbits + kElectroluxAcOverhead - offset) //rawlen = 68, nbits = 104 - return false; // Too short a message to match. - if (strict && nbits != kElectroluxAcBits) - return false; - - uint64_t data = 0; - match_result_t data_result; - - // Header - if (!matchMark(results->rawbuf[offset++], kElectroluxAcHdrMark)) - return false; - if (!matchSpace(results->rawbuf[offset++], kElectroluxAcHdrSpace)) - return false; - - - - // Data Section #1 - // e.g. data_result.data = 0xED000004, nbits = 32 - data_result = matchData(&(results->rawbuf[offset]), 32, - kElectroluxAcBitMark, kElectroluxAcOneSpace, - kElectroluxAcBitMark, kElectroluxAcZeroSpace); - offset += data_result.used; - if (data_result.success == false) return false; // Fail - data <<= 32; // Make room for the new bits of data. - data |= data_result.data; - - // Footer - if (!matchMark(results->rawbuf[offset++], kElectroluxAcBitMark)) - return false; - - // Success - results->decode_type = decode_type_t::ELETROLUX_AC; - results->bits = nbits; - results->value = data; - results->command = data & 0xFFF; - results->address = 0; - return true; +bool IRrecv::decodeElectroluxAc( + decode_results *results, + uint16_t offset, + const uint16_t nbits, + const bool strict +) { + if (results->rawlen < 2 * nbits + kElectroluxAcOverhead - offset) + return false; // Too short a message to match. + if (strict && nbits != kElectroluxAcBits) + return false; + + uint64_t data = 0; + + // Header + if (!matchMark(results->rawbuf[offset++], kElectroluxAcHdrMark)) + return false; + if (!matchSpace(results->rawbuf[offset++], kElectroluxAcHdrSpace)) + return false; + + // Data Section #1 + // e.g. data_result.data = 0xED000004, nbits = 32 + match_result_t data_result = matchData( + &(results->rawbuf[offset]), 32, + kElectroluxAcBitMark, kElectroluxAcOneSpace, + kElectroluxAcBitMark, kElectroluxAcZeroSpace); + + offset += data_result.used; + if (data_result.success == false) + return false; // Fail + data <<= 32; // Make room for the new bits of data. + data |= data_result.data; + + // Footer + if (!matchMark(results->rawbuf[offset++], kElectroluxAcBitMark)) + return false; + + // Success + results->decode_type = decode_type_t::ELECTROLUX_AC; + results->bits = nbits; + results->value = data; + results->command = data & 0xFFF; + results->address = 0; + return true; } #endif // DECODE_ELECTROLUX @@ -103,119 +115,138 @@ bool IRrecv::decodeElectroluxAc(decode_results *results, uint16_t offset, const /// @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? -IRElectroluxAc::IRElectroluxAc(const uint16_t pin, const bool inverted, - const bool use_modulation) - : _irsend(pin, inverted, use_modulation) { stateReset(); } +IRElectroluxAc::IRElectroluxAc( + const uint16_t pin, + const bool inverted, + const bool use_modulation +): _irsend(pin, inverted, use_modulation) { + _ = ElectroluxAcProtocol(); + stateReset(); +} /// Reset the internal state to a fixed known good state. /// @note The state is powered off. -void IRElectroluxAc::stateReset(void) { _.raw = 0xF3008005; } +void IRElectroluxAc::stateReset() { _.raw = 0xF3008005; } #if SEND_ELECTROLUX_AC /// Send the current internal state as an IR message. /// @param[in] repeat Nr. of times the message will be repeated. void IRElectroluxAc::send(const uint16_t repeat) { - _irsend.sendElectroluxAc(getRaw(), kElectroluxAcBits, repeat); + _irsend.sendElectroluxAc(getRaw(), kElectroluxAcBits, repeat); } #endif // SEND_ELECTROLUX_AC /// Set up hardware to be able to send a message. -void IRElectroluxAc::begin(void) { _irsend.begin(); } +void IRElectroluxAc::begin() { _irsend.begin(); } /// Turn on/off the Power Airwell setting. /// @param[in] on The desired setting state. -void IRElectroluxAc::setPower(const bool on) { _.Power = on; } +void IRElectroluxAc::setPower(const bool on) { _.Power = on; } /// Get the power toggle setting from the internal state. /// @return A boolean indicating the setting. -bool IRElectroluxAc::getPower(void) const { return _.Power; } +bool IRElectroluxAc::getPower() const { return _.Power; } /// Turn on/off the fahrenheit temp mode. /// @param[in] on The desired setting state. -void IRElectroluxAc::setTempModeFahrenheit(const bool on) { _.TempModeFahrenheit = on; } +void IRElectroluxAc::setTempModeFahrenheit(const bool on) { + _.TempModeFahrenheit = on; +} /// Get the fahrenheit temp mode set from the internal state. /// @return A boolean indicating the setting. -bool IRElectroluxAc::getTempModeFahrenheit(void) const { return _.TempModeFahrenheit; } +bool IRElectroluxAc::getTempModeFahrenheit() const { + return _.TempModeFahrenheit; +} /// Set the temperature. /// @param[in] degrees The temperature in celsius or fahrenheit. void IRElectroluxAc::setTemp(const uint8_t degrees) { - if(getTempModeFahrenheit()) { - uint8_t temp = max(kElectroluxAcMinFTemp, degrees); - temp = min(kElectroluxAcMaxFTemp, temp); - _.Temp = (temp - kElectroluxAcMinFTemp); - } - else { - uint8_t temp = max(kElectroluxAcMinTemp, degrees); - temp = min(kElectroluxAcMaxTemp, temp); - temp = map(temp, kElectroluxAcMinTemp, kElectroluxAcMaxTemp, kElectroluxAcMinFTemp, kElectroluxAcMaxFTemp); - _.Temp = temp - kElectroluxAcMinFTemp; - } + if (getTempModeFahrenheit()) { + uint8_t temp = max(kElectroluxAcMinFTemp, degrees); + temp = min(kElectroluxAcMaxFTemp, temp); + _.Temp = (temp - kElectroluxAcMinFTemp); + } else { + uint8_t temp = max(kElectroluxAcMinTemp, degrees); + temp = min(kElectroluxAcMaxTemp, temp); +#ifndef UNIT_TEST + temp = map(temp, kElectroluxAcMinTemp, kElectroluxAcMaxTemp, + kElectroluxAcMinFTemp, kElectroluxAcMaxFTemp); +#else + temp = temp * 9 / 5 + 32; +#endif + _.Temp = temp - kElectroluxAcMinFTemp; + } } /// Get the current temperature from the internal state. /// @return The current temperature in Celsius. -uint8_t IRElectroluxAc::getTemp(void) const { - if(getTempModeFahrenheit()) { - return _.Temp + kElectroluxAcMinFTemp; - } - else { - uint8_t temp = map(_.Temp + kElectroluxAcMinFTemp, kElectroluxAcMinFTemp, kElectroluxAcMaxFTemp, kElectroluxAcMinTemp, kElectroluxAcMaxTemp); - return temp; - } +uint8_t IRElectroluxAc::getTemp() const { + if (getTempModeFahrenheit()) { + return _.Temp + kElectroluxAcMinFTemp; + } else { +#ifndef UNIT_TEST + uint8_t temp = map(_.Temp + kElectroluxAcMinFTemp, + kElectroluxAcMinFTemp, kElectroluxAcMaxFTemp, + kElectroluxAcMinTemp, kElectroluxAcMaxTemp); +#else + uint8_t temp = ((_.Temp + kElectroluxAcMinFTemp) - 32) * 5 / 9; +#endif + return temp; + } } /// Set the speed of the fan. /// @param[in] speed The desired setting. /// @note The speed is locked to Low when in Dry mode. void IRElectroluxAc::setFan(const uint8_t speed) { - _.Fan = (_.Mode == kElectroluxModeAuto) ? kElectroluxFanAuto - : std::min(speed, kElectroluxFanAuto); + _.Fan = (_.Mode == kElectroluxModeAuto) + ? kElectroluxFanAuto + : std::min(speed, kElectroluxFanAuto); } /// Get the current fan speed setting. /// @return The current fan speed. -uint8_t IRElectroluxAc::getFan(void) const { return _.Fan; } +uint8_t IRElectroluxAc::getFan() const { return _.Fan; } /// Set the desired operation mode. /// @param[in] mode The desired operation mode. void IRElectroluxAc::setMode(const uint8_t mode) { - switch (mode) { - case kElectroluxModeCool: - case kElectroluxModeDry: - case kElectroluxModeFan: - case kElectroluxModeAuto: - _.Mode = mode; - break; - default: - _.Mode = kElectroluxModeAuto; - } + switch (mode) { + case kElectroluxModeCool: + case kElectroluxModeDry: + case kElectroluxModeFan: + case kElectroluxModeAuto: + _.Mode = mode; + break; + default: + _.Mode = kElectroluxModeAuto; + } } /// Get the operating mode setting of the A/C. /// @return The current operating mode setting. -uint8_t IRElectroluxAc::getMode(void) const { return _.Mode; } +uint8_t IRElectroluxAc::getMode() const { return _.Mode; } /// Set the On/Off Timer time. /// @param[in] nr_of_mins Number of minutes to set the timer to. /// (< 60 is disable). /// @note The A/C protocol only supports one hour increments. -void IRElectroluxAc::setOnOffTimer(const uint16_t nr_of_hours) { - uint8_t hours = std::min((uint8_t)(nr_of_hours / 60), kElectroluxTimerMax); - // The time can be changed in sleep mode, but doesn't set the flag. - _.TimerEnable = hours > 0; - _.Timer = std::max(kElectroluxTimerMin, hours); // Hours +void IRElectroluxAc::setOnOffTimer(const uint16_t nr_of_mins) { + const uint8_t hours = std::min( + static_cast(nr_of_mins / 60), + kElectroluxTimerMax); + + // The time can be changed in sleep mode, but doesn't set the flag. + _.TimerEnabled = hours > 0; + _.Timer = std::max(kElectroluxTimerMin, hours); // Hours } /// Get the current On/Off Timer time. /// @return The number of minutes it is set for. 0 means it's off. /// @note The A/C protocol only supports one hour increments. -uint16_t IRElectroluxAc::getOnOffTimer(void) const { - if (_.TimerEnable) - return _.Timer * 60; - else - return 0; +uint16_t IRElectroluxAc::getOnOffTimer() const { + return _.TimerEnabled > 0 ? _.Timer * 60 : 0; } /// Set the Quiet setting of the A/C. @@ -224,13 +255,13 @@ void IRElectroluxAc::setQuiet(const bool on) { _.Quiet = on; } /// Get the Quiet setting of the A/C. /// @return true, the setting is on. false, the setting is off. -bool IRElectroluxAc::getQuiet(void) const { return _.Quiet; } +bool IRElectroluxAc::getQuiet() const { return _.Quiet; } /// Get a copy of the internal state as a valid code for this protocol. /// @return A valid code for this protocol based on the current internal state. -uint64_t IRElectroluxAc::getRaw(void) { - checksum(); // Ensure correct settings before sending. - return _.raw; +uint64_t IRElectroluxAc::getRaw() { + checksum(); // Ensure correct settings before sending. + return _.raw; } /// Set the internal state from a valid code for this protocol. @@ -241,144 +272,181 @@ void IRElectroluxAc::setRaw(const uint64_t state) { _.raw = state; } /// @param[in] state The value to calc the checksum of. /// @return The 4-bit checksum stored in a uint_8. uint8_t IRElectroluxAc::calcChecksum(const uint64_t state) { - uint32_t data = GETBITS64(state, kElectroluxAcChecksumSize + kElectroluxAcChecksumOffset, kElectroluxAcBits - 4); - uint8_t result = 0; - for (; data; data >>= 4) // Add each nibble together. - result += GETBITS8(data, 0, 4); - return (result ^ 0xF) & 0xF; + uint32_t data = GETBITS64( + state, + kElectroluxAcChecksumSize + kElectroluxAcChecksumOffset, + kElectroluxAcBits - 4); + + uint8_t result = 0; + for (; data; data >>= 4) // Add each nibble together. + result += GETBITS8(data, 0, 4); + return (result ^ 0xF) & 0xF; } /// Verify the checksum is valid for a given state. /// @param[in] state The array to verify the checksum of. /// @return true, if the state has a valid checksum. Otherwise, false. bool IRElectroluxAc::validChecksum(const uint64_t state) { - // Validate the checksum of the given state. - return (GETBITS8(state, kElectroluxAcChecksumOffset, - kElectroluxAcChecksumSize) == calcChecksum(state)); + // Validate the checksum of the given state. + return (GETBITS8(state, kElectroluxAcChecksumOffset, + kElectroluxAcChecksumSize) == calcChecksum(state)); } /// Convert a stdAc::opmode_t enum into its native mode. /// @param[in] mode The enum to be converted. /// @return The native equivalent of the enum. uint8_t IRElectroluxAc::convertMode(const stdAc::opmode_t mode) { - switch (mode) { - case stdAc::opmode_t::kCool: return kElectroluxModeCool; - case stdAc::opmode_t::kDry: return kElectroluxModeDry; - case stdAc::opmode_t::kFan: return kElectroluxModeFan; - default: return kElectroluxModeAuto; - } + switch (mode) { + case stdAc::opmode_t::kCool: + return kElectroluxModeCool; + case stdAc::opmode_t::kDry: + return kElectroluxModeDry; + case stdAc::opmode_t::kFan: + return kElectroluxModeFan; + default: + return kElectroluxModeAuto; + } } /// Convert a stdAc::fanspeed_t enum into it's native speed. /// @param[in] speed The enum to be converted. /// @return The native equivalent of the enum. uint8_t IRElectroluxAc::convertFan(const stdAc::fanspeed_t speed) { - switch (speed) { - case stdAc::fanspeed_t::kMin: - case stdAc::fanspeed_t::kLow: - return kElectroluxFanLow; - case stdAc::fanspeed_t::kMedium: - case stdAc::fanspeed_t::kMediumHigh: - return kElectroluxFanMedium; - case stdAc::fanspeed_t::kHigh: - case stdAc::fanspeed_t::kMax: - return kElectroluxFanHigh; - default: - return kElectroluxFanAuto; - } + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: + return kElectroluxFanLow; + case stdAc::fanspeed_t::kMedium: + case stdAc::fanspeed_t::kMediumHigh: + return kElectroluxFanMedium; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: + return kElectroluxFanHigh; + default: + return kElectroluxFanAuto; + } } /// Convert a native mode into its stdAc equivalent. /// @param[in] mode The native setting to be converted. /// @return The stdAc equivalent of the native setting. stdAc::opmode_t IRElectroluxAc::toCommonMode(const uint8_t mode) { - switch (mode) { - case kElectroluxModeCool: return stdAc::opmode_t::kCool; - case kElectroluxModeDry: return stdAc::opmode_t::kDry; - case kElectroluxModeFan: return stdAc::opmode_t::kFan; - default: return stdAc::opmode_t::kAuto; - } + switch (mode) { + case kElectroluxModeCool: + return stdAc::opmode_t::kCool; + case kElectroluxModeDry: + return stdAc::opmode_t::kDry; + case kElectroluxModeFan: + return stdAc::opmode_t::kFan; + default: + return stdAc::opmode_t::kAuto; + } } /// Convert a native fan speed into its stdAc equivalent. /// @param[in] speed The native setting to be converted. /// @return The stdAc equivalent of the native setting. stdAc::fanspeed_t IRElectroluxAc::toCommonFanSpeed(const uint8_t speed) { - switch (speed) { - case kElectroluxFanHigh: return stdAc::fanspeed_t::kMax; - case kElectroluxFanMedium: return stdAc::fanspeed_t::kMedium; - case kElectroluxFanLow: return stdAc::fanspeed_t::kMin; - default: return stdAc::fanspeed_t::kAuto; - } + switch (speed) { + case kElectroluxFanHigh: + return stdAc::fanspeed_t::kMax; + case kElectroluxFanMedium: + return stdAc::fanspeed_t::kMedium; + case kElectroluxFanLow: + return stdAc::fanspeed_t::kMin; + default: + return stdAc::fanspeed_t::kAuto; + } } /// Convert the current internal state into its stdAc::state_t equivalent. /// @param[in] prev Ptr to the previous state if required. /// @return The stdAc equivalent of the native settings. stdAc::state_t IRElectroluxAc::toCommon(const stdAc::state_t *prev) const { - stdAc::state_t result{}; - // Start with the previous state if given it. - if (prev != NULL) { - result = *prev; - } else { - // Set defaults for non-zero values that are not implicitly set for when - // there is no previous state. - // e.g. Any setting that toggles should probably go here. - result.power = false; - } - result.protocol = decode_type_t::ELETROLUX_AC; - result.power = _.Power; - result.mode = toCommonMode(_.Mode); - result.celsius = !getTempModeFahrenheit(); - result.degrees = getTemp(); - result.fanspeed = toCommonFanSpeed(_.Fan); - // Not supported. - result.model = -1; - result.turbo = false; - result.swingv = stdAc::swingv_t::kOff; - result.swingh = stdAc::swingh_t::kOff; - result.light = false; - result.filter = false; - result.econo = false; - result.quiet = getQuiet(); - result.clean = false; - result.beep = false; - result.sleep = -1; - result.clock = -1; - return result; + stdAc::state_t result{}; + // Start with the previous state if given it. + if (prev != nullptr) { + result = *prev; + } else { + // Set defaults for non-zero values that are not implicitly set for when + // there is no previous state. + // e.g. Any setting that toggles should probably go here. + result.power = false; + } + result.protocol = ELECTROLUX_AC; + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = !getTempModeFahrenheit(); + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + // Not supported. + result.model = -1; + result.turbo = false; + result.swingv = stdAc::swingv_t::kOff; + result.swingh = stdAc::swingh_t::kOff; + result.light = false; + result.filter = false; + result.econo = false; + result.quiet = getQuiet(); + result.clean = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; } /// Convert the internal state into a human readable string. /// @return The current internal state expressed as a human readable String. -String IRElectroluxAc::toString(void) const { - String result = ""; - result.reserve(120); // Reserve some heap for the string to reduce fragging. - result += addBoolToString(_.Power, kPowerStr, false); - result += addModeToString(_.Mode, kElectroluxModeAuto, kElectroluxModeCool, - 0xFF, kElectroluxModeDry, kElectroluxModeFan); - result += addTempToString(getTemp(), !getTempModeFahrenheit()); - result += addFanToString(_.Fan, kElectroluxFanHigh, kElectroluxFanLow, - kElectroluxFanAuto, kElectroluxFanAuto, - kElectroluxFanMedium); - - result += addBoolToString(getQuiet(), kQuietStr); - - if(getPower()) { - result += irutils::addLabeledString(irutils::minsToString(getOnOffTimer()), kOffTimerStr); - } - else { - result += irutils::addLabeledString(irutils::minsToString(getOnOffTimer()), kOnTimerStr); - } - return result; +String IRElectroluxAc::toString() const { + String result = ""; + result.reserve(120); // Reserve heap for the string to reduce fragging. + + result += addBoolToString( + _.Power, + kPowerStr, + false); + + result += addModeToString( + _.Mode, + kElectroluxModeAuto, + kElectroluxModeCool, + 0xFF, + kElectroluxModeDry, + kElectroluxModeFan); + + result += addTempToString( + getTemp(), + !getTempModeFahrenheit()); + + result += addFanToString( + _.Fan, + kElectroluxFanHigh, + kElectroluxFanLow, + kElectroluxFanAuto, + kElectroluxFanAuto, + kElectroluxFanMedium); + + result += addBoolToString(getQuiet(), kQuietStr); + + if (getPower()) { + result += irutils::addLabeledString( + irutils::minsToString(getOnOffTimer()), + kOffTimerStr); + } else { + result += irutils::addLabeledString( + irutils::minsToString(getOnOffTimer()), + kOnTimerStr); + } + return result; } /// Calculate and set the checksum values for the internal state. -void IRElectroluxAc::checksum(void) { - _.Sum = calcChecksum(_.raw); +void IRElectroluxAc::checksum() { + _.Sum = calcChecksum(_.raw); } /// Set the requested power state of the A/C to on. -void IRElectroluxAc::on(void) { setPower(true); } +void IRElectroluxAc::on() { setPower(true); } /// Set the requested power state of the A/C to off. -void IRElectroluxAc::off(void) { setPower(false); } \ No newline at end of file +void IRElectroluxAc::off() { setPower(false); } diff --git a/src/ir_Electrolux.h b/src/ir_Electrolux.h index d3f7ee92e..a9f6958f8 100644 --- a/src/ir_Electrolux.h +++ b/src/ir_Electrolux.h @@ -5,116 +5,143 @@ // Supports: // Brand: Electrolux, Model: Electrolux EACM EZ/N3 -#ifndef IR_ELECTROLUX_AC_H_ -#define IR_ELECTROLUX_AC_H_ +#ifndef IR_ELECTROLUX_H_ +#define IR_ELECTROLUX_H_ -#define __STDC_LIMIT_MACROS -#include +#define STDC_LIMIT_MACROS +#include #ifndef UNIT_TEST #include #endif #include "IRremoteESP8266.h" #include "IRsend.h" -#include "map" #ifdef UNIT_TEST #include "IRsend_test.h" #endif -union ElectroluxAcProtocol{ +union ElectroluxAcProtocol { uint64_t raw; // The state of the IR remote in native IR code form. - struct { - uint8_t Sum :4; - uint8_t :4; - uint8_t :5; - uint8_t TempModeFahrenheit :1; - uint8_t :1; - uint8_t Quiet :1; - uint8_t Timer :4; - uint8_t TimerEnable :1; - uint8_t Mode :3; - uint8_t Temp :5; - uint8_t Fan :2; - uint8_t Power :1; - uint64_t :0; + struct { + uint8_t Sum: 4; + uint8_t : 4; + uint8_t : 5; + uint8_t TempModeFahrenheit: 1; + uint8_t : 1; + uint8_t Quiet: 1; + uint8_t Timer: 4; + uint8_t TimerEnabled: 1; + uint8_t Mode: 3; + uint8_t Temp: 5; + uint8_t Fan: 2; + uint8_t Power: 1; + uint64_t : 0; }; }; // Constants -const uint8_t kElectroluxAcMinTemp = 16; // 16C -const uint8_t kElectroluxAcMaxTemp = 32; // 32C -const uint8_t kElectroluxAcMinFTemp = 60; // 60F -const uint8_t kElectroluxAcMaxFTemp = 90; // 90F -const uint8_t kElectroluxTimerMax = 12; // 12H -const uint8_t kElectroluxTimerMin = 1; // 1H +const uint8_t kElectroluxAcMinTemp = 16; // 16C +const uint8_t kElectroluxAcMaxTemp = 32; // 32C +const uint8_t kElectroluxAcMinFTemp = 60; // 60F +const uint8_t kElectroluxAcMaxFTemp = 90; // 90F +const uint8_t kElectroluxTimerMax = 12; // 12H +const uint8_t kElectroluxTimerMin = 1; // 1H const uint64_t kElectroluxAcKnownGoodState = 0xF3008005; const uint8_t kElectroluxAcChecksumOffset = 0; const uint8_t kElectroluxAcChecksumSize = 4; // Fan -const uint8_t kElectroluxFanLow = 2; // 0b11 -const uint8_t kElectroluxFanMedium = 1; // 0b01 -const uint8_t kElectroluxFanHigh = 0; // 0b00 -const uint8_t kElectroluxFanAuto = 3; // 0b11 +const uint8_t kElectroluxFanLow = 2; // 0b11 +const uint8_t kElectroluxFanMedium = 1; // 0b01 +const uint8_t kElectroluxFanHigh = 0; // 0b00 +const uint8_t kElectroluxFanAuto = 3; // 0b11 // Modes -const uint8_t kElectroluxModeCool = 0; // 0b000 -const uint8_t kElectroluxModeDry = 1; // 0b001 -const uint8_t kElectroluxModeFan = 2; // 0b010 -const uint8_t kElectroluxModeAuto = 4; // 0b100 - +const uint8_t kElectroluxModeCool = 0; // 0b000 +const uint8_t kElectroluxModeDry = 1; // 0b001 +const uint8_t kElectroluxModeFan = 2; // 0b010 +const uint8_t kElectroluxModeAuto = 4; // 0b100 class IRElectroluxAc { - public: - explicit IRElectroluxAc(const uint16_t pin, const bool inverted = false, - const bool use_modulation = true); - void stateReset(); + public: + explicit IRElectroluxAc(uint16_t pin, bool inverted = false, + bool use_modulation = true); + + void stateReset(); #if SEND_ELECTROLUX_AC - void send(const uint16_t repeat = kElectroluxAcDefaultRepeat); - /// Run the calibration to calculate uSec timing offsets for this platform. - /// @return The uSec timing offset needed per modulation of the IR Led. - /// @note This will produce a 65ms IR signal pulse at 38kHz. - /// Only ever needs to be run once per object instantiation, if at all. - int8_t calibrate(void) { return _irsend.calibrate(); } + void send(uint16_t repeat = kElectroluxAcDefaultRepeat); + + /// 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 // SEND_ELECTROLUX_AC - void begin(); - void on(void); - void off(void); - void setPower(const bool on); - bool getPower(void) const; - void setTemp(const uint8_t temp); - uint8_t getTemp(void) const; - void setFan(const uint8_t speed); - uint8_t getFan(void) const; - void setMode(const uint8_t mode); - uint8_t getMode(void) const; - void setOnOffTimer(const uint16_t nr_of_mins); - uint16_t getOnOffTimer(void) const; - void setQuiet(const bool on); - bool getQuiet(void) const; - void setTempModeFahrenheit(const bool on); - bool getTempModeFahrenheit(void) const; - uint64_t getRaw(void); - void setRaw(const uint64_t state); - static uint8_t calcChecksum(const uint64_t state); - static bool validChecksum(const uint64_t state); - static uint8_t convertMode(const stdAc::opmode_t mode); - static uint8_t convertFan(const stdAc::fanspeed_t speed); - static stdAc::opmode_t toCommonMode(const uint8_t mode); - static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); - stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const; - String toString(void) const; + void begin(); + + void on(); + + void off(); + + void setPower(bool on); + + bool getPower() const; + + void setTemp(uint8_t degrees); + + uint8_t getTemp() const; + + void setFan(uint8_t speed); + + uint8_t getFan() const; + + void setMode(uint8_t mode); + + uint8_t getMode() const; + + void setOnOffTimer(uint16_t nr_of_mins); + + uint16_t getOnOffTimer() const; + void setQuiet(bool on); + + bool getQuiet() const; + + void setTempModeFahrenheit(bool on); + + bool getTempModeFahrenheit() const; + + uint64_t getRaw(); + + void setRaw(uint64_t state); + + static uint8_t calcChecksum(uint64_t state); + + static bool validChecksum(uint64_t state); + + static uint8_t convertMode(stdAc::opmode_t mode); + + static uint8_t convertFan(stdAc::fanspeed_t speed); + + static stdAc::opmode_t toCommonMode(uint8_t mode); + + static stdAc::fanspeed_t toCommonFanSpeed(uint8_t speed); + + stdAc::state_t toCommon(const stdAc::state_t *prev = nullptr) const; + + String toString() const; #ifndef UNIT_TEST - private: - IRsend _irsend; ///< Instance of the IR send class -#else // UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST /// @cond IGNORE IRsendTest _irsend; ///< Instance of the testing IR send class /// @endcond -#endif // UNIT_TEST - ElectroluxAcProtocol _; - void checksum(void); +#endif // UNIT_TEST + ElectroluxAcProtocol _{}; + + void checksum(); }; -#endif // IR_ELECTROLUX_AC_H_ \ No newline at end of file +#endif // IR_ELECTROLUX_H_ diff --git a/test/ir_Electrolux_test.cpp b/test/ir_Electrolux_test.cpp new file mode 100644 index 000000000..4feca1194 --- /dev/null +++ b/test/ir_Electrolux_test.cpp @@ -0,0 +1,19 @@ +// Copyright 2024 Andrey Kravchenko (StellaLupus) + +#include "ir_Electrolux.h" +#include "IRac.h" +#include "IRrecv.h" +#include "IRrecv_test.h" +#include "IRsend.h" +#include "IRsend_test.h" +#include "gtest/gtest.h" + + +TEST(TestUtils, Housekeeping) { + ASSERT_EQ("ELETROLUX_AC", typeToString(decode_type_t::ELECTROLUX_AC)); + ASSERT_EQ(decode_type_t::ELECTROLUX_AC, strToDecodeType("ELETROLUX_AC")); + ASSERT_FALSE(hasACState(decode_type_t::ELECTROLUX_AC)); + ASSERT_TRUE(IRac::isProtocolSupported(decode_type_t::ELECTROLUX_AC)); + ASSERT_EQ(kElectroluxAcBits, IRsend::defaultBits(decode_type_t::ELECTROLUX_AC)); + ASSERT_EQ(kElectroluxAcDefaultRepeat, IRsend::minRepeats(decode_type_t::ELECTROLUX_AC)); +} \ No newline at end of file diff --git a/tools/serial_parser.py b/tools/serial_parser.py deleted file mode 100644 index 124915103..000000000 --- a/tools/serial_parser.py +++ /dev/null @@ -1,99 +0,0 @@ -import enum -import serial -import serial.tools -import serial.tools.list_ports - -def substring_after(str: str, searchStr: str): - return str[str.index(searchStr) + len(searchStr):] - -def getPort(): - ports = sorted(serial.tools.list_ports.comports()) - print("Available ports to listing:") - for id, portInfo in enumerate(ports): - print("{}. - {}: {} [{}]".format(id, portInfo.device, portInfo.description, portInfo.hwid)) - print("Select port: ", end="") - selectId = int(input()) - if selectId < len(ports) and selectId >= 0: - return ports[selectId] - else: - print("Unrecognized port number") - return getPort() - -zero = 756 -one = 2149 -space = 752 -interv = 0.2 - -def getBitFromInterv(value: int): - if(value > zero - zero * interv and value < zero + zero * interv): - return 0 - elif(value > one - one * interv and value < one + one * interv): - return 1 - else: - return 2 - -def bitListToInt(bitlist): - out = 0 - for bit in bitlist: - out = (out << 1) | bit - return out - - -def main(): - port = getPort() - print("Selected port:" + port.device) - - ser = serial.Serial( - port=port.device, - baudrate=115200, - ) - - zSum = 0 - zCnt = 0 - oSum = 0 - oCnt = 0 - sSum = 0 - sCnt = 0 - - while True: - try: - dataStr = ser.readline().decode() - except: - continue - #print(dataStr) - if("uint16_t rawData" in dataStr): - dataStrArray = str(dataStr[dataStr.index('{') + 1:dataStr.index('}')]).split(",") - data = [int(i.strip()) for i in dataStrArray] - data = data[2:] - clearData = [i for idi, i in enumerate(data) if idi % 2 == 1] - bitData = [getBitFromInterv(i) for i in data] - clearBitData = [getBitFromInterv(i) for i in clearData] - - - - for idd, d in enumerate(data): - if(idd % 2 == 0): - sSum += d - sCnt += 1 - else: - if(getBitFromInterv(d) == 0): - zSum += d - zCnt += 1 - elif(getBitFromInterv(d) == 1): - oSum += d - oCnt += 1 - - # print("zero = " + str(zSum/zCnt) + " one = " + str(oSum/oCnt) + " space = " + str(sSum/sCnt))\ - - - - # print("Data = ", end="") - # print(data) - # print("0b"+ "".join([str(i) for i in bitData])) - # print("ClearData = ", end="") - #print(clearData) - print("".join([str(i) for i in clearBitData])) - - -if __name__ == '__main__': - main() \ No newline at end of file