diff --git a/src/IRac.cpp b/src/IRac.cpp index da59af1b0..5f3bcb8ef 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -238,6 +238,9 @@ bool IRac::isProtocolSupported(const decode_type_t protocol) { #if SEND_DELONGHI_AC case decode_type_t::DELONGHI_AC: #endif +#if SEND_DELONGHI_N + case decode_type_t::DELONGHI_N: +#endif #if SEND_ECOCLIM case decode_type_t::ECOCLIM: #endif @@ -1117,6 +1120,26 @@ void IRac::delonghiac(IRDelonghiAc *ac, } #endif // SEND_DELONGHI_AC +#if SEND_DELONGHI_N +/// Send a Delonghi N message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRDelonghi_N object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] celsius Temperature units. True is Celsius, False is Fahrenheit. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +void IRac::delonghi_n(IRDelonghi_N *ac, + const bool on, const stdAc::opmode_t mode, const bool celsius, + const float degrees, const stdAc::fanspeed_t fan) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees, !celsius); + ac->setFan(ac->convertFan(fan)); + ac->send(); +} +#endif // SEND_DELONGHI_N + #if SEND_ECOCLIM /// Send an EcoClim A/C message with the supplied settings. /// @param[in, out] ac A Ptr to an IREcoclimAc object to use. @@ -3220,6 +3243,14 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { break; } #endif // SEND_DELONGHI_AC +#if SEND_DELONGHI_N + case DELONGHI_N: + { + IRDelonghi_N ac(_pin, _inverted, _modulation); + delonghi_n(&ac, send.power, send.mode, send.celsius, degC, send.fanspeed); + break; + } +#endif // SEND_DELONGHI_N #if SEND_ECOCLIM case ECOCLIM: { @@ -4198,6 +4229,13 @@ String resultAcToString(const decode_results * const result) { return ac.toString(); } #endif // DECODE_DELONGHI_AC +#if DECODE_DELONGHI_N + case decode_type_t::DELONGHI_N: { + IRDelonghi_N ac(kGpioUnused); + ac.setRaw(result->value); // Delonghi_N uses value instead of state. + return ac.toString(); + } +#endif // DECODE_DELONGHI_N #if DECODE_ECOCLIM case decode_type_t::ECOCLIM: { if (result->bits == kEcoclimBits) { @@ -4695,6 +4733,14 @@ bool decodeToState(const decode_results *decode, stdAc::state_t *result, break; } #endif // DECODE_DELONGHI_AC +#if DECODE_DELONGHI_N + case decode_type_t::DELONGHI_N: { + IRDelonghi_N ac(kGpioUnused); + ac.setRaw(decode->value); // Uses value instead of state. + *result = ac.toCommon(); + break; + } +#endif // DECODE_DELONGHI_N #if DECODE_ECOCLIM case decode_type_t::ECOCLIM: { if (decode->bits == kEcoclimBits) { diff --git a/src/IRac.h b/src/IRac.h index ae78b8ce3..1f7266a1e 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -252,6 +252,11 @@ void daikin216(IRDaikin216 *ac, const float degrees, const stdAc::fanspeed_t fan, const bool turbo, const int16_t sleep = -1); #endif // SEND_DELONGHI_AC +#if SEND_DELONGHI_N + void delonghi_n(IRDelonghi_N *ac, + const bool on, const stdAc::opmode_t mode, const bool celsius, + const float degrees, const stdAc::fanspeed_t fan); +#endif // SEND_DELONGHI_N #if SEND_ECOCLIM void ecoclim(IREcoclimAc *ac, const bool on, const stdAc::opmode_t mode, diff --git a/src/IRrecv.cpp b/src/IRrecv.cpp index 3cd88b377..7cf5067bb 100644 --- a/src/IRrecv.cpp +++ b/src/IRrecv.cpp @@ -802,6 +802,10 @@ bool IRrecv::decode(decode_results *results, irparams_t *save, return true; #endif */ +#if DECODE_DELONGHI_N + DPRINTLN("Attempting Delonghi N decode"); + if (decodeDelonghi_N(results, offset)) return true; +#endif // DECODE_DELONGHI_N #if DECODE_NEC // Some devices send NEC-like codes that don't follow the true NEC spec. // This should detect those. e.g. Apple TV remote etc. diff --git a/src/IRrecv.h b/src/IRrecv.h index a9cbce610..2c56743dc 100644 --- a/src/IRrecv.h +++ b/src/IRrecv.h @@ -889,6 +889,11 @@ class IRrecv { const uint16_t nbits = kBluestarHeavyBits, const bool strict = true); #endif // DECODE_BLUESTARHEAVY +#if DECODE_DELONGHI_N + bool decodeDelonghi_N(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kDelonghi_N_Bits, + const bool strict = true); +#endif // DECODE_DELONGHI_N }; #endif // IRRECV_H_ diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h index 4a0e60bf2..203b029c9 100644 --- a/src/IRremoteESP8266.h +++ b/src/IRremoteESP8266.h @@ -959,6 +959,13 @@ #define SEND_BLUESTARHEAVY _IR_ENABLE_DEFAULT_ #endif // SEND_BLUESTARHEAVY +#ifndef DECODE_DELONGHI_N +#define DECODE_DELONGHI_N _IR_ENABLE_DEFAULT_ +#endif // DECODE_DELONGHI_N +#ifndef SEND_DELONGHI_N +#define SEND_DELONGHI_N _IR_ENABLE_DEFAULT_ +#endif // SEND_DELONGHI_N + #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 || \ @@ -978,6 +985,7 @@ DECODE_DAIKIN200 || DECODE_HAIER_AC160 || DECODE_TCL96AC || \ DECODE_BOSCH144 || DECODE_SANYO_AC152 || DECODE_DAIKIN312 || \ DECODE_CARRIER_AC84 || DECODE_YORK || DECODE_BLUESTARHEAVY || \ + DECODE_DELONGHI_N || \ 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 @@ -1145,8 +1153,9 @@ enum decode_type_t { CARRIER_AC84, // 125 YORK, BLUESTARHEAVY, + DELONGHI_N, // Add new entries before this one, and update it to point to the last entry. - kLastDecodeType = BLUESTARHEAVY, + kLastDecodeType = DELONGHI_N, }; // Message lengths & required repeat values @@ -1229,6 +1238,8 @@ const uint16_t kDaikin312Bits = kDaikin312StateLength * 8; const uint16_t kDaikin312DefaultRepeat = kNoRepeat; const uint16_t kDelonghiAcBits = 64; const uint16_t kDelonghiAcDefaultRepeat = kNoRepeat; +const uint16_t kDelonghi_N_Bits = 32; +const uint16_t kDelonghi_N_DefaultRepeat = kNoRepeat; const uint16_t kTechnibelAcBits = 56; const uint16_t kTechnibelAcDefaultRepeat = kNoRepeat; const uint16_t kDenonBits = 15; diff --git a/src/IRsend.cpp b/src/IRsend.cpp index 8b2309b8f..87cface00 100644 --- a/src/IRsend.cpp +++ b/src/IRsend.cpp @@ -655,6 +655,7 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) { case SAMSUNG: case SHERWOOD: case WHYNTER: + case DELONGHI_N: return 32; case AIRWELL: return 34; @@ -887,6 +888,11 @@ bool IRsend::send(const decode_type_t type, const uint64_t data, sendDelonghiAc(data, nbits, min_repeat); break; #endif +#if SEND_DELONGHI_N + case DELONGHI_N: + sendDelonghi_N(data, nbits, min_repeat); + break; +#endif #if SEND_DENON case DENON: sendDenon(data, nbits, min_repeat); diff --git a/src/IRsend.h b/src/IRsend.h index 80e5d65d5..7435c71c2 100644 --- a/src/IRsend.h +++ b/src/IRsend.h @@ -899,6 +899,10 @@ class IRsend { const uint16_t nbytes = kBluestarHeavyStateLength, const uint16_t repeat = kNoRepeat); #endif // SEND_BLUESTARHEAVY +#if SEND_DELONGHI_N + void sendDelonghi_N(uint64_t data, uint16_t nbits = kDelonghi_N_Bits, + uint16_t repeat = kDelonghi_N_DefaultRepeat); +#endif protected: #ifdef UNIT_TEST diff --git a/src/IRtext.cpp b/src/IRtext.cpp index f8a3290bb..729f837d6 100644 --- a/src/IRtext.cpp +++ b/src/IRtext.cpp @@ -561,6 +561,8 @@ IRTEXT_CONST_BLOB_DECL(kAllProtocolNamesStr) { D_STR_YORK, D_STR_UNSUPPORTED) "\x0" COND(DECODE_BLUESTARHEAVY || SEND_BLUESTARHEAVY, D_STR_BLUESTARHEAVY, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_DELONGHI_N || SEND_DELONGHI_N, + D_STR_DELONGHI_N, D_STR_UNSUPPORTED) "\x0" ///< New protocol (macro) strings should be added just above this line. "\x0" ///< This string requires double null termination. }; diff --git a/src/ir_Delonghi.cpp b/src/ir_Delonghi.cpp index 19dec3741..600dc106c 100644 --- a/src/ir_Delonghi.cpp +++ b/src/ir_Delonghi.cpp @@ -468,3 +468,436 @@ String IRDelonghiAc::toString(void) const { kOffTimerStr); return result; } + + +const uint16_t kDelonghi_N_HdrMark = 9096; +const uint16_t kDelonghi_N_BitMark = 564; +const uint16_t kDelonghi_N_HdrSpace = 4440; +const uint16_t kDelonghi_N_OneSpace = 1623; +const uint16_t kDelonghi_N_ZeroSpace = 534; +const uint16_t kDelonghi_N_Freq = 38000; +const bool kDelonghi_N_MsbFirst = true; +const uint16_t kDelonghi_N_Overhead = 3; + +#if SEND_DELONGHI_N +// Function should be safe up to 64 bits. +/// Send a Delonghi_N formatted message. +/// Status: ALPHA / Untested. +/// @param[in] data containing the IR command. +/// @param[in] nbits Nr. of bits to send. usually kDelonghi_NBits +/// @param[in] repeat Nr. of times the message is to be repeated. +void IRsend::sendDelonghi_N(const uint64_t data, + const uint16_t nbits, + const uint16_t repeat) { + enableIROut(kDelonghi_N_Freq); + for (uint16_t r = 0; r <= repeat; r++) { + uint64_t send_data = data; + // Header + mark(kDelonghi_N_HdrMark); + space(kDelonghi_N_HdrSpace); + // Data Section #1 + // e.g. data = 0x12188100, nbits = 32 + sendData(kDelonghi_N_BitMark, kDelonghi_N_OneSpace, + kDelonghi_N_BitMark, kDelonghi_N_ZeroSpace, + send_data, nbits, kDelonghi_N_MsbFirst); + send_data >>= 32; + // Footer + mark(kDelonghi_N_BitMark); + space(kDefaultMessageGap); + } +} +#endif // SEND_DELONGHI_N + +#if DECODE_DELONGHI_N +// Function should be safe up to 64 bits. +/// Decode the supplied Delonghi_N message. +/// Status: ALPHA / Untested. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @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::decodeDelonghi_N(decode_results *results, + uint16_t offset, const uint16_t nbits, + const bool strict) { + if (results->rawlen < 2 * nbits + kDelonghi_N_Overhead - offset) + return false; // Too short a message to match. + if (strict && nbits != kDelonghi_N_Bits) + return false; + + uint64_t data = 0; + match_result_t data_result; + + // Header + if (!matchMark(results->rawbuf[offset++], kDelonghi_N_HdrMark)) + return false; + if (!matchSpace(results->rawbuf[offset++], kDelonghi_N_HdrSpace)) + return false; + + // Data Section #1 + // e.g. data_result.data = 0x12188100, nbits = 32 + data_result = matchData(&(results->rawbuf[offset]), 32, + kDelonghi_N_BitMark, kDelonghi_N_OneSpace, + kDelonghi_N_BitMark, kDelonghi_N_ZeroSpace, + kUseDefTol, kMarkExcess, kDelonghi_N_MsbFirst); + 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++], kDelonghi_N_BitMark)) + return false; + + // The data should start with 0x12, + // Look at the various values in the struct + // So value should be between 0x12110000 and 0x1248FFFF + if ((data < 0x12110000) || (data > 0x1248ffff)) + return false; + + // Success + results->decode_type = decode_type_t::DELONGHI_N; + results->bits = nbits; + results->value = data; + results->command = 0; + results->address = 0; + return true; +} +#endif // DECODE_DELONGHI_N + +/// 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? +IRDelonghi_N::IRDelonghi_N(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRDelonghi_N::begin(void) { _irsend.begin(); } + +#if SEND_DELONGHI_N +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRDelonghi_N::send(const uint16_t repeat) { + _irsend.sendDelonghi_N(getRaw(), kDelonghi_N_Bits, repeat); +} +#endif // SEND_DELONGHI_N + +/// Reset the internal state to a fixed known good state. +void IRDelonghi_N::stateReset(void) { + _.raw = 0x12000000; + _.Head = 0x12; + _saved_temp = 23; // DegC (Random reasonable default value) + _saved_temp_units = 0; // Celsius +} + +/// 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. +uint32_t IRDelonghi_N::getRaw(void) { + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] state A valid code for this protocol. +void IRDelonghi_N::setRaw(const uint32_t state) { _.raw = state; } + +/// Change the power setting to On. +void IRDelonghi_N::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRDelonghi_N::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDelonghi_N::setPower(const bool on) { + _.Power = on; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRDelonghi_N::getPower(void) const { + return _.Power; +} + +/// Change the temperature scale units. +/// @param[in] fahrenheit true, use Fahrenheit. false, use Celsius. +void IRDelonghi_N::setTempUnit(const bool fahrenheit) { + _.Fahrenheit = fahrenheit; +} + +/// Get the temperature scale unit of measure currently in use. +/// @return true, is Fahrenheit. false, is Celsius. +bool IRDelonghi_N::getTempUnit(void) const { + return _.Fahrenheit; +} + +/// Set the temperature. +/// @param[in] degrees The temperature in degrees. +/// @param[in] fahrenheit Use Fahrenheit as the temperature scale. +/// @param[in] force Do we ignore any sanity checks? +void IRDelonghi_N::setTemp(const uint8_t degrees, const bool fahrenheit, + const bool force) { + uint8_t temp; + if (force) { + temp = degrees; // We've been asked to force set this value. + } else { + uint8_t temp_min = kDelonghi_N_TempMinC; + uint8_t temp_max = kDelonghi_N_TempMaxC; + setTempUnit(fahrenheit); + if (fahrenheit) { + temp_min = kDelonghi_N_TempMinF; + temp_max = kDelonghi_N_TempMaxF; + } + temp = std::max(temp_min, degrees); + temp = std::min(temp_max, temp); + _saved_temp = temp; + _saved_temp_units = fahrenheit; + if (!fahrenheit) { + temp = temp - kDelonghi_N_TempMinC; + } + } + _.Temp = reverseBits(temp, 8); +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in currently configured units/scale. +uint8_t IRDelonghi_N::getTemp(void) const { + return reverseBits(_.Temp, 8) + (_.Fahrenheit ? 0 : kDelonghi_N_TempMinC); +} + +/// Set the speed of the fan. +/// @param[in] speed The desired native setting. +void IRDelonghi_N::setFan(const uint8_t speed) { + switch (speed) { + case kDelonghi_N_FanLow: + case kDelonghi_N_FanMedium: + case kDelonghi_N_FanHigh: + _.Fan = speed; + break; + default: + _.Fan = kDelonghi_N_FanHigh; + break; + } +} + +/// Get the current native fan speed setting. +/// @return The current fan speed. +uint8_t IRDelonghi_N::getFan(void) const { + return _.Fan; +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRDelonghi_N::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: + return kDelonghi_N_FanLow; + case stdAc::fanspeed_t::kMedium: + return kDelonghi_N_FanMedium; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: + return kDelonghi_N_FanHigh; + default: + return kDelonghi_N_FanHigh;; + } +} + +/// 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 IRDelonghi_N::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kDelonghi_N_FanHigh: + return stdAc::fanspeed_t::kMax; + case kDelonghi_N_FanMedium: + return stdAc::fanspeed_t::kMedium; + case kDelonghi_N_FanLow: + return stdAc::fanspeed_t::kMin; + default: + return stdAc::fanspeed_t::kMin; + } +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRDelonghi_N::getMode(void) const { + return _.Mode; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired native operating mode. +void IRDelonghi_N::setMode(const uint8_t mode) { + switch (mode) { + case kDelonghi_N_Cool: + case kDelonghi_N_Dry: + case kDelonghi_N_Fan: + _.Mode = mode; + break; + default: + _.Mode = kDelonghi_N_Cool; + break; + } +} + +/// 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 IRDelonghi_N::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: + return kDelonghi_N_Cool; + case stdAc::opmode_t::kDry: + return kDelonghi_N_Dry; + case stdAc::opmode_t::kFan: + return kDelonghi_N_Fan; + default: + return kDelonghi_N_Cool; + } +} + +/// 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 IRDelonghi_N::toCommonMode(const uint8_t mode) { + switch (mode) { + case kDelonghi_N_Cool: + return stdAc::opmode_t::kCool; + case kDelonghi_N_Dry: + return stdAc::opmode_t::kDry; + case kDelonghi_N_Fan: + return stdAc::opmode_t::kFan; + default: + return stdAc::opmode_t::kCool; + } +} + +/// Set the enable status of the On Timer. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDelonghi_N::setOnTimerEnabled(const bool on) { + setTimerEnabled(on); +} + +/// Get the enable status of the On Timer. +/// @return true, the setting is on. false, the setting is off. +bool IRDelonghi_N::getOnTimerEnabled(void) const { + return getTimerEnabled(); +} + +/// Set the On timer to activate in nr of minutes. +/// @param[in] nr_of_mins Total nr of mins to wait before waking the device. +/// @note Max 23 hrs and 59 minutes. i.e. 1439 mins. +void IRDelonghi_N::setOnTimer(const uint16_t nr_of_mins) { + setTimer(nr_of_mins); +} + +/// Get the On timer time. +/// @return Total nr of mins before the device turns on. +uint16_t IRDelonghi_N::getOnTimer(void) const { + return getTimer(); +} + +/// Set the enable status of the Off Timer. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDelonghi_N::setOffTimerEnabled(const bool on) { + setTimerEnabled(on); +} + +/// Get the enable status of the Off Timer. +/// @return true, the setting is on. false, the setting is off. +bool IRDelonghi_N::getOffTimerEnabled(void) const { + return getTimerEnabled(); +} + +/// Set the Off timer to activate in nr of minutes. +/// @param[in] nr_of_mins Total nr of mins to wait before turning off the device +/// @note Max 23 hrs and 59 minutes. i.e. 1439 mins. +void IRDelonghi_N::setOffTimer(const uint16_t nr_of_mins) { + setTimer(nr_of_mins); +} + +/// Get the Off timer time. +/// @return Total nr of mins before the device turns off. +uint16_t IRDelonghi_N::getOffTimer(void) const { + return getTimer(); +} + +/// Set the enable status of the Timer. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRDelonghi_N::setTimerEnabled(const bool on) { + _.Timer = on; +} + +/// Get the enable status of the Timer. +/// @return true, the setting is on. false, the setting is off. +bool IRDelonghi_N::getTimerEnabled(void) const { + return _.Timer; +} + +/// Set the timer to activate in nr of minutes. +/// @param[in] nr_of_mins Total nr of mins to wait before waking +/// or sleeping the device. +/// @note Max 23 hrs and 59 minutes. i.e. 1439 mins. +void IRDelonghi_N::setTimer(const uint16_t nr_of_mins) { + uint16_t value = nr_of_mins /60; + value = std::min(kDelonghi_N_TimerMax, value); + _.Hours = reverseBits(value, 4); + // Enable or not? + setTimerEnabled(value > 0); +} + +/// Get the timer time. +/// @return Total nr of mins before the device turns on. +uint16_t IRDelonghi_N::getTimer(void) const { + return reverseBits(_.Hours, 4) * 60; +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRDelonghi_N::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::DELONGHI_N; + result.power = _.Power; + result.mode = toCommonMode(getMode()); + result.celsius = !_.Fahrenheit; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + // Not supported. + result.turbo = false; + result.sleep = false; + result.model = -1; + result.swingv = stdAc::swingv_t::kOff; + result.swingh = stdAc::swingh_t::kOff; + result.light = false; + result.filter = false; + result.econo = false; + result.quiet = false; + result.clean = false; + result.beep = false; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRDelonghi_N::toString(void) const { + String result = ""; + result.reserve(80); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, 0, kDelonghi_N_Cool, + 0, kDelonghi_N_Dry, kDelonghi_N_Fan); + result += addFanToString(_.Fan, kDelonghi_N_FanHigh, kDelonghi_N_FanLow, + 0, 0, + kDelonghi_N_FanMedium); + result += addTempToString(getTemp(), !_.Fahrenheit); + uint16_t mins = getTimer(); + result += addLabeledString((mins && _.Timer) ? minsToString(mins) + : kOffStr, + kTimerStr); + return result; +} + + diff --git a/src/ir_Delonghi.h b/src/ir_Delonghi.h index ac2394cb1..c9d06387d 100644 --- a/src/ir_Delonghi.h +++ b/src/ir_Delonghi.h @@ -7,6 +7,7 @@ // Supports: // Brand: Delonghi, Model: PAC A95 +// Brand: Delonghi, Model: PAC N75, N80, N82, N85, N87, N90, N91, N115, N135 #ifndef IR_DELONGHI_H_ #define IR_DELONGHI_H_ @@ -67,6 +68,36 @@ const uint8_t kDelonghiAcAuto = 0b100; const uint16_t kDelonghiAcTimerMax = 23 * 60 + 59; const uint8_t kDelonghiAcChecksumOffset = 56; +/// Native representation of a DelonghiN N message. +union Delonghi_N_Protocol{ + uint32_t raw; ///< The state of the IR remote. + struct { + uint8_t Temp :8; + uint8_t Power :1; + uint8_t Timer :1; + uint8_t Fahrenheit:1; + uint8_t :1; + uint8_t Hours :4; + uint8_t Mode :4; + uint8_t Fan :4; + uint8_t Head :8; // Header = 0x12 + }; +}; + +// Constants +const uint8_t kDelonghi_N_TempMinC = 16; // Deg C +const uint8_t kDelonghi_N_TempMaxC = 32; // Deg C +const uint8_t kDelonghi_N_TempMinF = 61; // Deg F +const uint8_t kDelonghi_N_TempMaxF = 89; // Deg F +const uint8_t kDelonghi_N_FanHigh = 1; +const uint8_t kDelonghi_N_FanMedium = 2; +const uint8_t kDelonghi_N_FanLow = 4; +const uint8_t kDelonghi_N_Cool = 8; +const uint8_t kDelonghi_N_Dry = 2; +const uint8_t kDelonghi_N_Fan = 1; +const uint16_t kDelonghi_N_TimerMax = 12; + + // Classes /// Class for handling detailed Delonghi A/C messages. @@ -133,4 +164,68 @@ class IRDelonghiAc { uint8_t _saved_temp_units; ///< The previously user requested temp units. void checksum(void); }; + +/// Class for handling detailed DelonghiN A/C messages. +class IRDelonghi_N { + public: + explicit IRDelonghi_N(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_DELONGHI_N + void send(const uint16_t repeat = kDelonghi_N_DefaultRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_DELONGI_N + void begin(void); + void setPower(const bool on); + bool getPower(void) const; + void on(void); + void off(void); + void setTempUnit(const bool celsius); + bool getTempUnit(void) const; + void setTemp(const uint8_t temp, const bool fahrenheit = false, + const bool force = false); + 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 setOnTimerEnabled(const bool on); + bool getOnTimerEnabled(void) const; + void setOnTimer(const uint16_t nr_of_mins); + uint16_t getOnTimer(void) const; + void setOffTimerEnabled(const bool on); + bool getOffTimerEnabled(void) const; + void setOffTimer(const uint16_t nr_of_mins); + uint16_t getOffTimer(void) const; + void setTimerEnabled(const bool on); + bool getTimerEnabled(void) const; + void setTimer(const uint16_t nr_of_mins); + uint16_t getTimer(void) const; + uint32_t getRaw(void); + void setRaw(const uint32_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(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< instance of the testing IR send class + /// @endcond +#endif + Delonghi_N_Protocol _; + uint8_t _saved_temp; ///< The previously user requested temp value. + uint8_t _saved_temp_units; ///< The previously user requested temp units. +}; + + #endif // IR_DELONGHI_H_ diff --git a/src/locale/defaults.h b/src/locale/defaults.h index a1329a97c..86669ac2b 100644 --- a/src/locale/defaults.h +++ b/src/locale/defaults.h @@ -823,6 +823,9 @@ D_STR_INDIRECT " " D_STR_MODE #ifndef D_STR_DELONGHI_AC #define D_STR_DELONGHI_AC "DELONGHI_AC" #endif // D_STR_DELONGHI_AC +#ifndef D_STR_DELONGHI_N +#define D_STR_DELONGHI_N "DELONGHI_N" +#endif // D_STR_DELONGHI_N #ifndef D_STR_DENON #define D_STR_DENON "DENON" #endif // D_STR_DENON diff --git a/test/ir_Delonghi_test.cpp b/test/ir_Delonghi_test.cpp index 9f069e40e..f0a2eefef 100644 --- a/test/ir_Delonghi_test.cpp +++ b/test/ir_Delonghi_test.cpp @@ -17,6 +17,13 @@ TEST(TestUtils, Housekeeping) { ASSERT_EQ(kDelonghiAcBits, IRsend::defaultBits(decode_type_t::DELONGHI_AC)); ASSERT_EQ(kDelonghiAcDefaultRepeat, IRsend::minRepeats(decode_type_t::DELONGHI_AC)); + ASSERT_EQ("DELONGHI_N", typeToString(decode_type_t::DELONGHI_N)); + ASSERT_EQ(decode_type_t::DELONGHI_N, strToDecodeType("DELONGHI_N")); + ASSERT_FALSE(hasACState(decode_type_t::DELONGHI_N)); + ASSERT_TRUE(IRac::isProtocolSupported(decode_type_t::DELONGHI_N)); + ASSERT_EQ(kDelonghi_N_Bits, IRsend::defaultBits(decode_type_t::DELONGHI_N)); + ASSERT_EQ(kDelonghi_N_DefaultRepeat, + IRsend::minRepeats(decode_type_t::DELONGHI_N)); } TEST(TestDecodeDelonghiAc, SyntheticSelfDecode) { @@ -360,3 +367,291 @@ TEST(TestIRDelonghiAcClass, OffTimer) { EXPECT_FALSE(ac.getOnTimerEnabled()); EXPECT_EQ(0, ac.getOnTimer()); } + +TEST(TestDecodeDelonghi_N, SyntheticSelfDecode) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + + irsend.begin(); + irsend.reset(); + irsend.sendDelonghi_N(0x12188100); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(DELONGHI_N, irsend.capture.decode_type); + EXPECT_EQ(kDelonghi_N_Bits, irsend.capture.bits); + EXPECT_EQ(0x12188100, irsend.capture.value); + EXPECT_EQ(0, irsend.capture.command); + EXPECT_EQ(0, irsend.capture.address); +} + +TEST(TestDecodeDelonghi_N, RealExample) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + irsend.begin(); + + uint16_t rawData[67] = {9096, 4440, 582, 522, 558, 548, 556, 524, 582, + 1602, 558, 572, 532, 550, 578, 1628, 558, 548, 556, 526, 578, + 524, 556, 550, 558, 1626, 582, 1628, 558, 522, 582, 522, 582, + 524, 556, 1628, 582, 522, 558, 548, 532, 572, 558, 522, 580, + 524, 558, 548, 556, 1628, 556, 550, 556, 526, 580, 522, 580, + 524, 556, 548, 558, 524, 582, 524, 580, 522, 556}; + + irsend.reset(); + irsend.sendRaw(rawData, 135, 38000); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(DELONGHI_N, irsend.capture.decode_type); + EXPECT_EQ(kDelonghi_N_Bits, irsend.capture.bits); + EXPECT_EQ(0x12188100, irsend.capture.value); + EXPECT_EQ(0, irsend.capture.command); + EXPECT_EQ(0, irsend.capture.address); + EXPECT_EQ("Power: On, Mode: 8 (Cool), Fan: 1 (High), Temp: 16C, Timer: Off", + IRAcUtils::resultAcToString(&irsend.capture)); + stdAc::state_t r, p; + ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &r, &p)); +} + +// Tests for IRDelonghi_N class. + +TEST(TestIRDelonghi_NClass, Power) { + IRDelonghi_N ac(kGpioUnused); + ac.begin(); + + ac.on(); + EXPECT_TRUE(ac.getPower()); + + ac.off(); + EXPECT_FALSE(ac.getPower()); + + ac.setPower(true); + EXPECT_TRUE(ac.getPower()); + + ac.setPower(false); + EXPECT_FALSE(ac.getPower()); + + // Ref: + // https://github.com/crankyoldgit/IRremoteESP8266/issues/1096#issuecomment-622521726 + ac.setRaw(0x12188100); // Power on + EXPECT_TRUE(ac.getPower()); + ac.setRaw(0x12188000); // Power off + EXPECT_FALSE(ac.getPower()); +} + +TEST(TestIRDelonghi_NClass, Temperature) { + IRDelonghi_N ac(kGpioUnused); + ac.begin(); + + // Celsius + ac.setTemp(0); + EXPECT_EQ(kDelonghi_N_TempMinC, ac.getTemp()); + EXPECT_FALSE(ac.getTempUnit()); + + ac.setTemp(255); + EXPECT_EQ(kDelonghi_N_TempMaxC, ac.getTemp()); + EXPECT_FALSE(ac.getTempUnit()); + + ac.setTemp(kDelonghi_N_TempMinC); + EXPECT_EQ(kDelonghi_N_TempMinC, ac.getTemp()); + EXPECT_FALSE(ac.getTempUnit()); + + ac.setTemp(kDelonghi_N_TempMaxC); + EXPECT_EQ(kDelonghi_N_TempMaxC, ac.getTemp()); + EXPECT_FALSE(ac.getTempUnit()); + + ac.setTemp(kDelonghi_N_TempMinC - 1); + EXPECT_EQ(kDelonghi_N_TempMinC, ac.getTemp()); + EXPECT_FALSE(ac.getTempUnit()); + + ac.setTemp(kDelonghi_N_TempMaxC + 1); + EXPECT_EQ(kDelonghi_N_TempMaxC, ac.getTemp()); + EXPECT_FALSE(ac.getTempUnit()); + + ac.setTemp(19); + EXPECT_EQ(19, ac.getTemp()); + EXPECT_FALSE(ac.getTempUnit()); + + ac.setTemp(21); + EXPECT_EQ(21, ac.getTemp()); + EXPECT_FALSE(ac.getTempUnit()); + + ac.setTemp(25); + EXPECT_EQ(25, ac.getTemp()); + EXPECT_FALSE(ac.getTempUnit()); + + ac.setTemp(29, false); + EXPECT_EQ(29, ac.getTemp()); + EXPECT_FALSE(ac.getTempUnit()); + + // Fahrenheit + ac.setTemp(0, true); + EXPECT_EQ(kDelonghi_N_TempMinF, ac.getTemp()); + EXPECT_TRUE(ac.getTempUnit()); + + ac.setTemp(255, true); + EXPECT_EQ(kDelonghi_N_TempMaxF, ac.getTemp()); + EXPECT_TRUE(ac.getTempUnit()); + + ac.setTemp(kDelonghi_N_TempMinF, true); + EXPECT_EQ(kDelonghi_N_TempMinF, ac.getTemp()); + EXPECT_TRUE(ac.getTempUnit()); + + ac.setTemp(kDelonghi_N_TempMaxF, true); + EXPECT_EQ(kDelonghi_N_TempMaxF, ac.getTemp()); + EXPECT_TRUE(ac.getTempUnit()); + + ac.setTemp(kDelonghi_N_TempMinF - 1, true); + EXPECT_EQ(kDelonghi_N_TempMinF, ac.getTemp()); + EXPECT_TRUE(ac.getTempUnit()); + + ac.setTemp(kDelonghi_N_TempMaxF + 1, true); + EXPECT_EQ(kDelonghi_N_TempMaxF, ac.getTemp()); + EXPECT_TRUE(ac.getTempUnit()); + + ac.setTemp(66, true); + EXPECT_EQ(66, ac.getTemp()); + EXPECT_TRUE(ac.getTempUnit()); + + ac.setTemp(75, true); + EXPECT_EQ(75, ac.getTemp()); + EXPECT_TRUE(ac.getTempUnit()); + + ac.setTemp(80, true); + EXPECT_EQ(80, ac.getTemp()); + EXPECT_TRUE(ac.getTempUnit()); + + ac.setTemp(88, true); + EXPECT_EQ(88, ac.getTemp()); + EXPECT_TRUE(ac.getTempUnit()); +} + +TEST(TestIRDelonghi_NClass, OperatingMode) { + IRDelonghi_N ac(kGpioUnused); + ac.begin(); + + ac.setMode(kDelonghi_N_Cool); + EXPECT_EQ(kDelonghi_N_Cool, ac.getMode()); + + ac.setMode(kDelonghi_N_Dry); + EXPECT_EQ(kDelonghi_N_Dry, ac.getMode()); + + ac.setMode(kDelonghi_N_Fan); + EXPECT_EQ(kDelonghi_N_Fan, ac.getMode()); + + ac.setMode(255); + EXPECT_EQ(kDelonghi_N_Cool, ac.getMode()); +} + +TEST(TestIRDelonghi_NClass, FanSpeed) { + IRDelonghi_N ac(kGpioUnused); + ac.begin(); + ac.setMode(kDelonghi_N_Cool); // All fan speeds available in this mode. + + ac.setFan(0); + EXPECT_EQ(kDelonghi_N_FanHigh, ac.getFan()); + + ac.setFan(255); + EXPECT_EQ(kDelonghi_N_FanHigh, ac.getFan()); + + ac.setFan(kDelonghi_N_FanHigh); + EXPECT_EQ(kDelonghi_N_FanHigh, ac.getFan()); + + ac.setFan(kDelonghi_N_FanLow + 1); + EXPECT_EQ(kDelonghi_N_FanHigh, ac.getFan()); + + ac.setFan(1); + EXPECT_EQ(1, ac.getFan()); + + ac.setFan(2); + EXPECT_EQ(2, ac.getFan()); + + ac.setFan(4); + EXPECT_EQ(4, ac.getFan()); + + // Confirm changing to fan mode handles speed behaviour correctly. + ac.setFan(kDelonghi_N_FanLow); + ac.setMode(kDelonghi_N_Fan); + EXPECT_EQ(kDelonghi_N_FanLow, ac.getFan()); + ac.setFan(kDelonghi_N_FanMedium); + EXPECT_EQ(kDelonghi_N_FanMedium, ac.getFan()); + ac.setFan(kDelonghi_N_FanHigh); + EXPECT_EQ(kDelonghi_N_FanHigh, ac.getFan()); +} + +TEST(TestIRDelonghi_NClass, OnTimer) { + IRDelonghi_N ac(kGpioUnused); + ac.begin(); + + ac.setOnTimerEnabled(false); + EXPECT_FALSE(ac.getOnTimerEnabled()); + ac.setOnTimerEnabled(true); + EXPECT_TRUE(ac.getOnTimerEnabled()); + ac.setOnTimerEnabled(false); + EXPECT_FALSE(ac.getOnTimerEnabled()); + + ac.setOnTimer(480); + EXPECT_TRUE(ac.getOnTimerEnabled()); + EXPECT_EQ(480, ac.getOnTimer()); + + ac.setOnTimer(60); + EXPECT_TRUE(ac.getOnTimerEnabled()); + EXPECT_EQ(60, ac.getOnTimer()); + + ac.setOnTimer(61); + EXPECT_TRUE(ac.getOnTimerEnabled()); + EXPECT_EQ(60, ac.getOnTimer()); + + ac.setOnTimerEnabled(false); + ac.setOnTimer(23 * 60 + 59); + EXPECT_TRUE(ac.getOnTimerEnabled()); + EXPECT_EQ(12 * 60, ac.getOnTimer()); + + ac.setOnTimerEnabled(false); + ac.setOnTimer(24 * 60); + EXPECT_TRUE(ac.getOnTimerEnabled()); + EXPECT_EQ(12 * 60 , ac.getOnTimer()); +} + +TEST(TestIRDelonghi_NClass, OffTimer) { + IRDelonghi_N ac(kGpioUnused); + ac.begin(); + + ac.setOffTimerEnabled(false); + EXPECT_FALSE(ac.getOffTimerEnabled()); + ac.setOffTimerEnabled(true); + EXPECT_TRUE(ac.getOffTimerEnabled()); + ac.setOffTimerEnabled(false); + EXPECT_FALSE(ac.getOffTimerEnabled()); + + ac.setOffTimer(0); + EXPECT_FALSE(ac.getOffTimerEnabled()); + EXPECT_EQ(0, ac.getOffTimer()); + + ac.setOffTimer(60); + EXPECT_TRUE(ac.getOffTimerEnabled()); + EXPECT_EQ(60, ac.getOffTimer()); + + ac.setOffTimer(61); + EXPECT_TRUE(ac.getOffTimerEnabled()); + EXPECT_EQ(60, ac.getOffTimer()); + + ac.setOffTimerEnabled(false); + ac.setOffTimer(11 * 60 + 59); + EXPECT_TRUE(ac.getOffTimerEnabled()); + EXPECT_EQ(11 * 60 , ac.getOffTimer()); + + ac.setOffTimerEnabled(false); + ac.setOffTimer(12 * 60); + EXPECT_TRUE(ac.getOffTimerEnabled()); + EXPECT_EQ(12 * 60 , ac.getOffTimer()); + + // Real Data + // From: https://github.com/crankyoldgit/IRremoteESP8266/issues/1096#issuecomment-623115619 + // Setting off timer to 8:51 when the time on the remote displayed 16:05. + // (8:51 + 24:00 - 16:05 == 32:51 - 16:05 == 16:46) i.e. Turn off in 16h46m. + ac.setRaw(0x1218e300); + EXPECT_TRUE(ac.getOffTimerEnabled()); + EXPECT_EQ(7 * 60 , ac.getOffTimer()); + EXPECT_TRUE(ac.getOnTimerEnabled()); + EXPECT_EQ(7 * 60 , ac.getOnTimer()); +} +