diff --git a/src/IRac.cpp b/src/IRac.cpp index 571e31c8e..553fcd759 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -246,6 +246,9 @@ bool IRac::isProtocolSupported(const decode_type_t protocol) { #if SEND_FUJITSU_AC case decode_type_t::FUJITSU_AC: #endif +#if SEND_FUJITSU_AC264 + case decode_type_t::FUJITSU_AC264: +#endif #if SEND_GOODWEATHER case decode_type_t::GOODWEATHER: #endif @@ -1273,6 +1276,59 @@ void IRac::fujitsu(IRFujitsuAC *ac, const fujitsu_ac_remote_model_t model, } #endif // SEND_FUJITSU_AC +#if SEND_FUJITSU_AC264 +/// Send a Fujitsu A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRFujitsuAC264 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] quiet Run the device in quiet/silent mode. +/// @param[in] turbo Toggle the device's turbo/powerful mode. +/// @param[in] econo Run the device in economical mode. +/// @param[in] clean Turn on the self-cleaning mode. e.g. Mould, dry filters etc +/// @param[in] sleep Nr. of minutes for sleep mode. <= 0 is Off, > 0 is on. +/// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignored. +void IRac::fujitsu264(IRFujitsuAC264 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const bool quiet, const bool turbo, const bool econo, + const bool clean, const int16_t sleep, + const int16_t clock) { + ac->begin(); + if (on) { + // Do all special messages (except "Off") first, + // These need to be sent separately. + // Some functions are only available on some models. + if (turbo) { + ac->togglePowerful(); + // Powerful is a separate command. + ac->send(); + } + // Normal operation. + ac->setMode(ac->convertMode(mode)); + if (mode == stdAc::opmode_t::kAuto) + ac->setTempAuto(degrees); + else + ac->setTemp(degrees); + ac->setFanSpeed(ac->convertFanSpeed(fan)); + ac->setSwing(swingv != stdAc::swingv_t::kOff); + if (quiet) ac->setFanSpeed(kFujitsuAc264FanSpeedQuiet); + ac->setEconomy(econo); + ac->setClean(clean); + ac->setSleepTimer(sleep > 0 ? sleep : 0); + if (clock >= 0) ac->setClock(clock); + ac->on(); + } else { + // Off is special case/message. We don't need to send other messages. + ac->off(); + } + ac->send(); +} +#endif // SEND_FUJITSU_AC264 + #if SEND_GOODWEATHER /// Send a Goodweather A/C message with the supplied settings. /// @param[in, out] ac A Ptr to an IRGoodweatherAc object to use. @@ -3242,6 +3298,16 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { break; } #endif // SEND_FUJITSU_AC +#if SEND_FUJITSU_AC264 + case FUJITSU_AC264: + { + IRFujitsuAC264 ac(_pin, _inverted, _modulation); + fujitsu264(&ac, send.power, send.mode, send.degrees, send.fanspeed, + send.swingv, send.quiet, send.turbo, send.econo, + send.clean, send.sleep); + break; + } +#endif // SEND_FUJITSU_AC264 #if SEND_GOODWEATHER case GOODWEATHER: { @@ -4212,6 +4278,13 @@ namespace IRAcUtils { return ac.toString(); } #endif // DECODE_FUJITSU_AC +#if DECODE_FUJITSU_AC264 + case decode_type_t::FUJITSU_AC264: { + IRFujitsuAC264 ac(kGpioUnused); + ac.setRaw(result->state, result->bits / 8); + return ac.toString(); + } +#endif // DECODE_FUJITSU_AC264 #if DECODE_GOODWEATHER case decode_type_t::GOODWEATHER: { IRGoodweatherAc ac(kGpioUnused); @@ -4713,6 +4786,14 @@ namespace IRAcUtils { break; } #endif // DECODE_FUJITSU_AC +#if DECODE_FUJITSU_AC264 + case decode_type_t::FUJITSU_AC264: { + IRFujitsuAC264 ac(kGpioUnused); + ac.setRaw(decode->state, decode->bits / 8); + *result = ac.toCommon(prev); + break; + } +#endif // DECODE_FUJITSU_AC264 #if DECODE_GOODWEATHER case decode_type_t::GOODWEATHER: { IRGoodweatherAc ac(kGpioUnused); diff --git a/src/IRac.h b/src/IRac.h index e3c261d0d..d7056e873 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -276,6 +276,16 @@ void electra(IRElectraAc *ac, const bool quiet, const bool turbo, const bool econo, const bool filter, const bool clean, const int16_t sleep = -1); #endif // SEND_FUJITSU_AC +#if SEND_FUJITSU_AC264 + void fujitsu264(IRFujitsuAC264 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const bool quiet, const bool turbo, const bool econo, + const bool clean, const int16_t sleep = -1, + const int16_t clock = -1); +#endif // SEND_FUJITSU_AC264 #if SEND_GOODWEATHER void goodweather(IRGoodweatherAc *ac, const bool on, const stdAc::opmode_t mode, diff --git a/src/IRrecv.cpp b/src/IRrecv.cpp index 173526104..3cf8cc7e8 100644 --- a/src/IRrecv.cpp +++ b/src/IRrecv.cpp @@ -693,6 +693,16 @@ bool IRrecv::decode(decode_results *results, irparams_t *save, DPRINTLN("Attempting Fujitsu A/C decode"); if (decodeFujitsuAC(results, offset)) return true; #endif +#if DECODE_FUJITSU_AC264 + // FujitsuAC264 should be checked before FujitsuAC + // Fujitsu A/C needs to precede Panasonic and Denon as it has a short + // message which looks exactly the same as a Panasonic/Denon message. + DPRINTLN("Attempting Fujitsu A/C264 decode"); + if (decodeFujitsuAC264(results, offset, kFujitsuAc264Bits) || + decodeFujitsuAC264(results, offset, kFujitsuAc264BitsMiddle) || + decodeFujitsuAC264(results, offset, kFujitsuAc264BitsShort)) + return true; +#endif #if DECODE_DENON // Denon needs to precede Panasonic as it is a special case of Panasonic. DPRINTLN("Attempting Denon decode"); diff --git a/src/IRrecv.h b/src/IRrecv.h index 7adf5eb1b..41e2eabe2 100644 --- a/src/IRrecv.h +++ b/src/IRrecv.h @@ -562,6 +562,12 @@ class IRrecv { const uint16_t nbits = kFujitsuAcBits, const bool strict = false); #endif +#if DECODE_FUJITSU_AC264 + bool decodeFujitsuAC264(decode_results* results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kFujitsuAc264Bits, + const bool strict = true); +#endif // DECODE_FUJITSU_AC264 #if DECODE_LASERTAG bool decodeLasertag(decode_results *results, uint16_t offset = kStartOffset, const uint16_t nbits = kLasertagBits, diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h index 949de1ecf..c40ae4f1f 100644 --- a/src/IRremoteESP8266.h +++ b/src/IRremoteESP8266.h @@ -952,6 +952,13 @@ #define SEND_YORK _IR_ENABLE_DEFAULT_ #endif // SEND_YORK +#ifndef DECODE_FUJITSU_AC264 +#define DECODE_FUJITSU_AC264 _IR_ENABLE_DEFAULT_ +#endif // DECODE_FUJITSU_AC264 +#ifndef SEND_FUJITSU_AC264 +#define SEND_FUJITSU_AC264 _IR_ENABLE_DEFAULT_ +#endif // SEND_FUJITSU_AC264 + #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 || \ @@ -970,7 +977,7 @@ DECODE_KELON168 || DECODE_HITACHI_AC296 || DECODE_CARRIER_AC128 || \ DECODE_DAIKIN200 || DECODE_HAIER_AC160 || DECODE_TCL96AC || \ DECODE_BOSCH144 || DECODE_SANYO_AC152 || DECODE_DAIKIN312 || \ - DECODE_CARRIER_AC84 || DECODE_YORK || \ + DECODE_CARRIER_AC84 || DECODE_YORK || DECODE_FUJITSU_AC264 || \ 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 @@ -1137,8 +1144,9 @@ enum decode_type_t { WOWWEE, CARRIER_AC84, // 125 YORK, + FUJITSU_AC264, // Add new entries before this one, and update it to point to the last entry. - kLastDecodeType = YORK, + kLastDecodeType = FUJITSU_AC264, }; // Message lengths & required repeat values @@ -1241,6 +1249,13 @@ const uint16_t kFujitsuAcStateLength = 16; const uint16_t kFujitsuAcStateLengthShort = 7; const uint16_t kFujitsuAcBits = kFujitsuAcStateLength * 8; const uint16_t kFujitsuAcMinBits = (kFujitsuAcStateLengthShort - 1) * 8; +const uint16_t kFujitsuAc264DefaultRepeat = kNoRepeat; +const uint16_t kFujitsuAc264StateLength = 33; +const uint16_t kFujitsuAc264StateLengthMiddle = 16; +const uint16_t kFujitsuAc264StateLengthShort = 7; +const uint16_t kFujitsuAc264Bits = kFujitsuAc264StateLength * 8; +const uint16_t kFujitsuAc264BitsMiddle = kFujitsuAc264StateLengthMiddle * 8; +const uint16_t kFujitsuAc264BitsShort = kFujitsuAc264StateLengthShort * 8; const uint16_t kGicableBits = 16; const uint16_t kGicableMinRepeat = kSingleRepeat; const uint16_t kGoodweatherBits = 48; diff --git a/src/IRsend.cpp b/src/IRsend.cpp index 10e440b32..c9503de4c 100644 --- a/src/IRsend.cpp +++ b/src/IRsend.cpp @@ -798,6 +798,8 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) { return kXmpBits; case YORK: return kYorkBits; + case FUJITSU_AC264: + return kFujitsuAc264Bits; // No default amount of bits. case FUJITSU_AC: case MWM: @@ -1246,6 +1248,11 @@ bool IRsend::send(const decode_type_t type, const uint8_t *state, sendFujitsuAC(state, nbytes); break; #endif // SEND_FUJITSU_AC +#if SEND_FUJITSU_AC264 + case FUJITSU_AC264: + sendFujitsuAC264(state, nbytes); + break; +#endif #if SEND_GREE case GREE: sendGree(state, nbytes); diff --git a/src/IRsend.h b/src/IRsend.h index 38491372a..923f004e3 100644 --- a/src/IRsend.h +++ b/src/IRsend.h @@ -469,6 +469,11 @@ class IRsend { void sendFujitsuAC(const unsigned char data[], const uint16_t nbytes, const uint16_t repeat = kFujitsuAcMinRepeat); #endif +#if SEND_FUJITSU_AC264 + void sendFujitsuAC264(const unsigned char data[], + const uint16_t nbytes = kFujitsuAc264StateLength, + const uint16_t repeat = kNoRepeat); +#endif // SEND_FUJITSU_AC264 #if SEND_INAX void sendInax(const uint64_t data, const uint16_t nbits = kInaxBits, const uint16_t repeat = kInaxMinRepeat); diff --git a/src/IRtext.cpp b/src/IRtext.cpp index 9cb39b772..0dab569de 100644 --- a/src/IRtext.cpp +++ b/src/IRtext.cpp @@ -555,6 +555,8 @@ IRTEXT_CONST_BLOB_DECL(kAllProtocolNamesStr) { D_STR_CARRIER_AC84, D_STR_UNSUPPORTED) "\x0" COND(DECODE_YORK || SEND_YORK, D_STR_YORK, D_STR_UNSUPPORTED) "\x0" + COND(DECODE_FUJITSU_AC264 || SEND_FUJITSU_AC264, + D_STR_FUJITSU_AC264, 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/IRutils.cpp b/src/IRutils.cpp index e9af0b28f..4e950be46 100644 --- a/src/IRutils.cpp +++ b/src/IRutils.cpp @@ -184,6 +184,7 @@ bool hasACState(const decode_type_t protocol) { case DAIKIN312: case ELECTRA_AC: case FUJITSU_AC: + case FUJITSU_AC264: case GREE: case HAIER_AC: case HAIER_AC_YRW02: diff --git a/src/ir_Fujitsu.cpp b/src/ir_Fujitsu.cpp index 0c2a84331..bc1340cec 100644 --- a/src/ir_Fujitsu.cpp +++ b/src/ir_Fujitsu.cpp @@ -1,6 +1,7 @@ // Copyright 2017 Jonny Graham // Copyright 2017-2022 David Conran // Copyright 2021 siriuslzx +// Copyright 2023 Takeshi Shimizu /// @file /// @brief Support for Fujitsu A/C protocols. @@ -38,6 +39,7 @@ using irutils::addModelToString; using irutils::addFanToString; using irutils::addTempFloatToString; using irutils::minsToString; +using irutils::addSignedIntToString; #if SEND_FUJITSU_AC /// Send a Fujitsu A/C formatted message. @@ -1098,3 +1100,993 @@ bool IRrecv::decodeFujitsuAC(decode_results* results, uint16_t offset, return true; // All good. } #endif // DECODE_FUJITSU_AC + +#if SEND_FUJITSU_AC264 +/// Send a Fujitsu 264 bit A/C formatted message. +/// Status: STABLE / Known Good. +/// @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::sendFujitsuAC264(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + sendGeneric(kFujitsuAcHdrMark, kFujitsuAcHdrSpace, kFujitsuAcBitMark, + kFujitsuAcOneSpace, kFujitsuAcBitMark, kFujitsuAcZeroSpace, + kFujitsuAcBitMark, kFujitsuAcMinGap, data, nbytes, 38, false, + repeat, 50); +} +#endif // SEND_FUJITSU_AC264 + +// Code to emulate Fujitsu 264 bit A/C IR remote control unit. + +/// Class Constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRFujitsuAC264::IRFujitsuAC264(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 IRFujitsuAC264::begin(void) { _irsend.begin(); } + +/// Reset the state of the remote to a known good state/sequence. +void IRFujitsuAC264::stateReset(void) { + for (size_t i = 0; i < kFujitsuAc264StateLength; i++) { + _.raw[i] = 0; + } + _ispoweredon = false; + _isecofan = false; + _isoutsidequiet = false; + _settemp = 0; + setTemp(24); + _cmd = _.Cmd = kFujitsuAc264CmdCool; + _.TempAuto = 0; + _.Mode = kFujitsuAc264ModeCool; + _.FanSpeed = kFujitsuAc264FanSpeedHigh; + _.FanAngle = kFujitsuAc264FanAngleStay; + _.Swing = false; + _.Economy = false; + _.Clean = false; + _.ClockHours = 0; + _.ClockMins = 0; + _.SleepTimerEnable = false; + _.SleepTimer = 0; + _.TimerEnable = kFujitsuAc264OnOffTimerDisable; + _.OnTimer = 0; + _.OffTimer = 0; + _.raw[0] = 0x14; + _.raw[1] = 0x63; + _.raw[2] = 0x00; + _.raw[3] = 0x10; + _.raw[4] = 0x10; +} + +#if SEND_FUJITSU_AC264 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRFujitsuAC264::send(const uint16_t repeat) { + _irsend.sendFujitsuAC(getRaw(), getStateLength(), repeat); + _settemp = _.Temp; // Preserve the sent setting + _ispoweredon = (_cmd < kFujitsuAc264SpCmdTurnOff); + if (_cmd == kFujitsuAc264SpCmdEcoFanOn) + _isecofan = true; + if (_cmd == kFujitsuAc264SpCmdEcoFanOff) + _isecofan = false; + if (_cmd == kFujitsuAc264SpCmdOutsideQuietOn) + _isoutsidequiet = true; + if (_cmd == kFujitsuAc264SpCmdOutsideQuietOff) + _isoutsidequiet = false; +} +#endif // SEND_FUJITSU_AC264 + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t* IRFujitsuAC264::getRaw(void) { + checkSum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] newState A valid code for this protocol. +/// @param[in] length Size of the newState array. +/// @return True, if successful; Otherwise false. (i.e. size check) +bool IRFujitsuAC264::setRaw(const uint8_t newState[], const uint16_t length) { + if (length > kFujitsuAc264StateLength) return false; + for (uint16_t i = 0; i < kFujitsuAc264StateLength; i++) { + if (i < length) + _.raw[i] = newState[i]; + else + _.raw[i] = 0; + } + switch (length) { + case kFujitsuAc264StateLengthShort: + if (std::memcmp(_.raw, kFujitsuAc264StatesTurnOff, + kFujitsuAc264StateLengthShort) == 0) + _cmd = kFujitsuAc264SpCmdTurnOff; + if (std::memcmp(_.raw, kFujitsuAc264StatesTogglePowerful, + kFujitsuAc264StateLengthShort) == 0) + _cmd = kFujitsuAc264SpCmdTogglePowerful; + if (std::memcmp(_.raw, kFujitsuAc264StatesEcoFanOff, + kFujitsuAc264StateLengthShort) == 0) + _cmd = kFujitsuAc264SpCmdEcoFanOff; + if (std::memcmp(_.raw, kFujitsuAc264StatesEcoFanOn, + kFujitsuAc264StateLengthShort) == 0) + _cmd = kFujitsuAc264SpCmdEcoFanOn; + break; + case kFujitsuAc264StateLengthMiddle: + if (std::memcmp(_.raw, kFujitsuAc264StatesOutsideQuietOff, + kFujitsuAc264StateLengthMiddle) == 0) + _cmd = kFujitsuAc264SpCmdOutsideQuietOff; + if (std::memcmp(_.raw, kFujitsuAc264StatesOutsideQuietOn, + kFujitsuAc264StateLengthMiddle) == 0) + _cmd = kFujitsuAc264SpCmdOutsideQuietOn; + if (std::memcmp(_.raw, kFujitsuAc264StatesToggleSterilization, + kFujitsuAc264StateLengthMiddle) == 0) + _cmd = kFujitsuAc264SpCmdToggleSterilization; + break; + case kFujitsuAc264StateLength: + setPower(true); + _cmd = _.Cmd; + break; + default: + return false; + } + return true; +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length of the state array. +/// @return True, if the state has a valid checksum. Otherwise, false. +bool IRFujitsuAC264::validChecksum(uint8_t state[], const uint16_t length) { + uint8_t sum = 0; + uint8_t sum_complement = 0; + uint8_t checksum = 0; + + if (length == kFujitsuAc264StateLengthShort) { + checksum = state[kFujitsuAc264StateLengthShort - 1]; + sum = state[kFujitsuAc264StateLengthShort - 2]; + sum_complement = 0xFF; + } else if (length == kFujitsuAc264StateLengthMiddle) { + checksum = state[kFujitsuAc264StateLengthMiddle - 1]; + sum = sumBytes(state, kFujitsuAc264StateLengthMiddle - 1); + sum_complement = 0x9E; + // The current command is normal + } else if (length == kFujitsuAc264StateLength) { + checksum = state[kFujitsuAc264StateLength - 1]; + sum = sumBytes(state, kFujitsuAc264StateLength - 1); + sum_complement = 0xAF; + } else { + return false; + } + return checksum == (uint8_t) (sum_complement - sum); // Does it match? +} + +/// Calculate and set the checksum values for the internal state. +void IRFujitsuAC264::checkSum(void) { + if (!isSpecialCommand()) { // The current command is not special + _.raw[5] = 0xFE; + _.RestLength = 0x1A; + _.Protocol = 0x40; + _.raw[13] = 0x00; + _.raw[15] |= 0x12; + _.raw[16] |= 0x06; + _.raw[17] = 0x00; + _.raw[21] |= 0x40; + _.raw[22] |= 0x10; + _.raw[25] = 0x00; + _.raw[26] = 0x00; + _.raw[27] = 0x00; + _.raw[28] |= 0xF0; + _.raw[29] = 0xFF; + _.raw[30] = 0xFF; + + uint8_t checksum = 0; + uint8_t checksum_complement = 0; + checksum = sumBytes(_.raw, kFujitsuAc264StateLength - 1); + checksum_complement = 0xAF; + _.raw[kFujitsuAc264StateLength - 1] = checksum_complement - checksum; + } +} + +/// Is the current command a special command? +/// @return True, if special command (kFujitsuAc264SpCmd*); +/// false, if normal command (kFujitsuAc264Cmd*). +bool IRFujitsuAC264::isSpecialCommand(void) const { + return (_cmd & 0xF0) == 0xF0; +} + +/// Get the length (size) of the state code for the current configuration. +/// @return The length of the state array required for this config. +uint8_t IRFujitsuAC264::getStateLength(void) { + uint8_t stateLength = 0; + + switch (_cmd) { + case kFujitsuAc264SpCmdTurnOff: + case kFujitsuAc264SpCmdTogglePowerful: + case kFujitsuAc264SpCmdEcoFanOff: + case kFujitsuAc264SpCmdEcoFanOn: + stateLength = kFujitsuAc264StateLengthShort; + break; + case kFujitsuAc264SpCmdOutsideQuietOff: + case kFujitsuAc264SpCmdOutsideQuietOn: + case kFujitsuAc264SpCmdToggleSterilization: + stateLength = kFujitsuAc264StateLengthMiddle; + break; + default: + stateLength = kFujitsuAc264StateLength; + break; + } + return stateLength; +} + +/// Set the requested power state of the A/C to on. +/// @note Mode should be set after this function. +void IRFujitsuAC264::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRFujitsuAC264::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on True, the setting is on. false, the setting is off. +/// @note If on = true, a mode should be set after calling this function. +void IRFujitsuAC264::setPower(const bool on) { + if (on) { + _cmd = kFujitsuAc264CmdCool; + } else { + _cmd = kFujitsuAc264SpCmdTurnOff; + std::memcpy(_.raw, kFujitsuAc264StatesTurnOff, + kFujitsuAc264StateLengthShort); + } +} + +/// Get the value of the current power setting. +/// @return True, the setting is on. false, the setting is off. +bool IRFujitsuAC264::getPower(void) const { return _ispoweredon; } + +/// Check if the temperature setting is changed. +/// @return True if the temperature is not changed. +bool IRFujitsuAC264::isTempStayed(void) const { return _settemp == _.Temp; } + +/// Set the temperature. +/// @param[in] temp The temperature in degrees Celcius. +/// @note The fractional part which is truncated to multiple of 0.5. +void IRFujitsuAC264::setTemp(const float temp) { + float _temp; + + if (temp > kFujitsuAc264MaxTemp) + _temp = kFujitsuAc264MaxTemp; + else if ((temp < kFujitsuAc264MinTemp) && (_.Mode != kFujitsuAc264ModeHeat)) + _temp = kFujitsuAc264MinTemp; + else if (temp < kFujitsuAc264MinHeat) + _temp = kFujitsuAc264MinHeat; + else + _temp = temp; + + _.Temp = (_temp - (kFujitsuAc264TempOffsetC / 2)) * 2; + _cmd = _.Cmd = kFujitsuAc264CmdTemp; + _.SubCmd = isTempStayed(); +} + +/// Get the current temperature setting. +/// @return The current setting for temperature in degrees Celcius. +float IRFujitsuAC264::getTemp(void) const { + return static_cast(_.Temp / 2.0) + (kFujitsuAc264TempOffsetC / 2); +} + +/// Set the temperature in auto mode. +/// @param[in] temp The temperature in auto mode in degrees Celcius. +/// @note The fractional part which is truncated to multiple of 0.5. +void IRFujitsuAC264::setTempAuto(const float temp) { + int8_t _tempx10; + + _tempx10 = (int8_t) (temp * 10); + _tempx10 -= _tempx10 % 5; + if (temp > kFujitsuAc264MaxTempAuto) + _tempx10 = kFujitsuAc264MaxTempAuto * 10; + else if (temp < kFujitsuAc264MinTempAuto) + _tempx10 = kFujitsuAc264MinTempAuto * 10; + + _.TempAuto = _tempx10; + _cmd = _.Cmd = kFujitsuAc264CmdTemp; +} + +/// Get the current temperature in auto mode setting. +/// @return The current setting for temp in auto mode in degrees Celcius. +float IRFujitsuAC264::getTempAuto(void) const { + return static_cast(static_cast(_.TempAuto) / 10.0); +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +/// @param[in] weakdry True if dry mode is in weak. +void IRFujitsuAC264::setMode(const uint8_t mode, const bool weakdry) { + switch (mode) { + case kFujitsuAc264ModeAuto: + _.Mode = kFujitsuAc264ModeAuto; + _cmd = _.Cmd = kFujitsuAc264CmdAuto; + break; + case kFujitsuAc264ModeCool: + _.Mode = kFujitsuAc264ModeCool; + _cmd = _.Cmd = kFujitsuAc264CmdCool; + break; + case kFujitsuAc264ModeFan: + _.Mode = kFujitsuAc264ModeFan; + _cmd = _.Cmd = kFujitsuAc264CmdFan; + break; + case kFujitsuAc264ModeHeat: + _.Mode = kFujitsuAc264ModeHeat; + _cmd = _.Cmd = kFujitsuAc264CmdHeat; + break; + case kFujitsuAc264ModeDry: + _.Mode = kFujitsuAc264ModeDry; + _.WeakDry = weakdry; + _cmd = _.Cmd = kFujitsuAc264CmdDry; + break; + default: + _.Mode = kFujitsuAc264ModeAuto; + _cmd = _.Cmd = kFujitsuAc264CmdAuto; + break; + } + _.SubCmd = 1; +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRFujitsuAC264::getMode(void) const { return _.Mode; } + +/// Get the weak dry mode setting of the A/C. +/// @return The weak dry mode setting. +bool IRFujitsuAC264::isWeakDry(void) const { return _.WeakDry; } + +/// Set the speed of the fan. +/// @param[in] fanSpeed The desired setting. +void IRFujitsuAC264::setFanSpeed(const uint8_t fanSpeed) { + // Set the fan to auto if out of range. + if ((fanSpeed == kFujitsuAc264FanSpeedQuiet) || + (fanSpeed == kFujitsuAc264FanSpeedLow) || + (fanSpeed == kFujitsuAc264FanSpeedMed) || + (fanSpeed == kFujitsuAc264FanSpeedHigh)) + _.FanSpeed = fanSpeed; + else + _.FanSpeed = kFujitsuAc264FanSpeedAuto; + _cmd = _.Cmd = kFujitsuAc264CmdFanSpeed; + _.SubCmd = 0; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRFujitsuAC264::getFanSpeed(void) const { return _.FanSpeed; } + +/// Set the angle of the fan. +/// @param[in] fanAngle The desired setting. +void IRFujitsuAC264::setFanAngle(const uint8_t fanAngle) { + // Set the fan to stay if out of range. + if ((fanAngle > kFujitsuAc264FanAngle7) || + (fanAngle < kFujitsuAc264FanAngle1)) { + _.FanAngle = kFujitsuAc264FanAngleStay; + } else { + _.FanAngle = fanAngle; + } + _cmd = _.Cmd = kFujitsuAc264CmdFanAngle; + _.SubCmd = 0; +} + +/// Get the current fan angle setting. +/// @return The current fan angle. +uint8_t IRFujitsuAC264::getFanAngle(void) const { return _.FanAngle; } + +/// Set weather the swing of fan is enabled or not. +/// @param[in] on True if swing is enabled, false if disabled. +void IRFujitsuAC264::setSwing(const bool on) { + _.Swing = on; + _.FanAngle = kFujitsuAc264FanAngleStay; // Set the fan to stay. + _cmd = _.Cmd = kFujitsuAc264CmdSwing; + _.SubCmd = 0; +} + +/// Get the requested swing operation mode of the A/C unit. +/// @return True if swing is enabled, false if disabled. +bool IRFujitsuAC264::getSwing(void) const { return _.Swing; } + +/// Set weather economy mode is enabled or not. +/// @param[in] on True if economy mode is enabled, false if disabled. +void IRFujitsuAC264::setEconomy(const bool on) { + _.Economy = on; + _cmd = _.Cmd = kFujitsuAc264CmdEconomy; + _.SubCmd = 0; +} + +/// Get the requested economy mode of the A/C unit. +/// @return True if economy mode is enabled, false if disabled. +bool IRFujitsuAC264::getEconomy(void) const { return _.Economy; } + +/// Set weather clean mode is enabled or not. +/// @param[in] on True if swing is enabled, false if disabled. +void IRFujitsuAC264::setClean(const bool on) { + _.Clean = on; + _cmd = _.Cmd = kFujitsuAc264CmdClean; + _.SubCmd = 0; +} + +/// Get the requested clean mode of the A/C unit. +/// @return True if clean is enabled, false if disabled. +bool IRFujitsuAC264::getClean(void) const { return _.Clean; } + +/// Toggle the sterilization. +/// @note This command is valid only when AC's power is off. +void IRFujitsuAC264::toggleSterilization(void) { + if (getPower()) + return; + _cmd = kFujitsuAc264SpCmdToggleSterilization; + std::memcpy(_.raw, kFujitsuAc264StatesToggleSterilization, + kFujitsuAc264StateLengthMiddle); +} + +/// Set weather outside quiet mode is enabled or not. +/// @param[in] on True if outside quiet is enabled, false if disabled. +/// @note This command is valid only when AC's power is off. +void IRFujitsuAC264::setOutsideQuiet(const bool on) { + if (getPower()) + return; + if (on) { + _cmd = kFujitsuAc264SpCmdOutsideQuietOn; + std::memcpy(_.raw, kFujitsuAc264StatesOutsideQuietOn, + kFujitsuAc264StateLengthMiddle); + } else { + _cmd = kFujitsuAc264SpCmdOutsideQuietOff; + std::memcpy(_.raw, kFujitsuAc264StatesOutsideQuietOff, + kFujitsuAc264StateLengthMiddle); + } +} + +/// Get the requested outside quiet mode of the A/C unit. +/// @return True if outside quiet is enabled, false if disabled. +bool IRFujitsuAC264::getOutsideQuiet(void) const { return _isoutsidequiet; } + +/// Set weather economy fan mode is enabled or not. +/// @param[in] on True if economy fan mode is enabled, false if disabled. +/// @note This command is valid only when AC's power is off. +void IRFujitsuAC264::setEcoFan(const bool on) { + if (getPower()) + return; + if (on) { + _cmd = kFujitsuAc264SpCmdEcoFanOn; + std::memcpy(_.raw, kFujitsuAc264StatesEcoFanOn, + kFujitsuAc264StateLengthShort); + } else { + _cmd = kFujitsuAc264SpCmdEcoFanOff; + std::memcpy(_.raw, kFujitsuAc264StatesEcoFanOff, + kFujitsuAc264StateLengthShort); + } +} + +/// Get the requested economy fan mode of the A/C unit. +/// @return True if economy fan mode is enabled, false if disabled. +bool IRFujitsuAC264::getEcoFan(void) const { return _isecofan; } + +/// Toggle the powerful mode. +/// @note This command is valid only when AC's power is on. +void IRFujitsuAC264::togglePowerful(void) { + if (!getPower()) + return; + _cmd = kFujitsuAc264SpCmdTogglePowerful; + std::memcpy(_.raw, kFujitsuAc264StatesTogglePowerful, + kFujitsuAc264StateLengthShort); +} + +/// Set the clock on the A/C unit. +/// @param[in] mins_since_midnight Nr. of minutes past midnight. +void IRFujitsuAC264::setClock(const uint16_t mins_since_midnight) { + uint16_t mins = mins_since_midnight; + if (mins_since_midnight >= 24 * 60) mins = 0; // Bounds check. + // Hours. + _.ClockHours = mins / 60; + // Minutes. + _.ClockMins = mins % 60; +} + +/// Get the clock time to be sent to the A/C unit. +/// @return The number of minutes past midnight. +uint16_t IRFujitsuAC264::getClock(void) const { + return (_.ClockHours * 60 + _.ClockMins); +} + +/// Set the sleep timer setting of the A/C. +/// @param[in] mins Minutes to set the timer to. 0 means disabled. +void IRFujitsuAC264::setSleepTimer(const uint16_t mins) { + if (mins == 0) { + _.SleepTimerEnable = false; + _.SleepTimer = 0; + _cmd = _.Cmd = kFujitsuAc264CmdCancelSleepTimer; + _.SubCmd = 0; + } else if (mins <= kFujitsuAc264SleepTimerMax) { + _.SleepTimerEnable = true; + _.SleepTimer = 0x800 + mins; + _cmd = _.Cmd = kFujitsuAc264CmdSleepTime; + _.SubCmd = 1; + } +} + +/// Get the sleep timer setting of the A/C. +/// @return Minutes left on the timer. 0 means disabled/not supported. +uint16_t IRFujitsuAC264::getSleepTimer(void) const { + if (_.SleepTimerEnable) + return (_.SleepTimer - 0x800); + return 0; +} + +/// Set the Timer enable of the A/C message. +/// @param[in] timer_enable The kind of timer to enable for the message. +void IRFujitsuAC264::setTimerEnable(const uint8_t timer_enable) { + switch (timer_enable) { + case kFujitsuAc264OnTimerEnable: + _.TimerEnable = timer_enable; + _cmd = _.Cmd = kFujitsuAc264CmdOnTimer; + _.SubCmd = 0; + break; + case kFujitsuAc264OffTimerEnable: + case kFujitsuAc264OnOffTimerEnable: + _.TimerEnable = timer_enable; + _cmd = _.Cmd = kFujitsuAc264CmdOffTimer; + _.SubCmd = 0; + break; + case kFujitsuAc264OnOffTimerDisable: + _.TimerEnable = timer_enable; + _cmd = _.Cmd = kFujitsuAc264CmdCancelOnOffTimer; + _.SubCmd = 0; + break; + default: + _.TimerEnable = kFujitsuAc264OnOffTimerDisable; + _cmd = _.Cmd = kFujitsuAc264CmdCancelOnOffTimer; + _.SubCmd = 0; + break; + } +} + +/// Get the Timer enable of the A/C message. +/// @return The current timer enable in numeric form. +uint8_t IRFujitsuAC264::getTimerEnable(void) const { return _.TimerEnable; } + +/// Set the on timer setting of the A/C. +/// @param[in] mins10 Time in 10 minutes unit, when the A/C will turn on. +/// 0 means 0:00 AM, 1 means 0:10 AM. +void IRFujitsuAC264::setOnTimer(const uint8_t mins10) { + if (mins10 <= kFujitsuAc26OnOffTimerMax) + _.OnTimer = mins10; +} + +/// Get the on timer setting of the A/C. +/// @return Time in 10 minutes unit, when the A/C will turn on. +/// 0 means 0:00 AM, 1 means 0:10 AM. +uint8_t IRFujitsuAC264::getOnTimer(void) const { return _.OnTimer; } + +/// Set the off timer setting of the A/C. +/// @param[in] mins10 Time in 10 minutes unit, when the A/C will turn off. +/// 0 means 0:00 AM, 1 means 0:10 AM. +void IRFujitsuAC264::setOffTimer(const uint8_t mins10) { + if (mins10 <= kFujitsuAc26OnOffTimerMax) + _.OffTimer = mins10; +} + +/// Get the off timer setting of the A/C. +/// @return Time in 10 minutes unit, when the A/C will turn off. +/// 0 means 0:00 AM, 1 means 0:10 AM. +uint8_t IRFujitsuAC264::getOffTimer(void) const { return _.OffTimer; } + +/// Set the requested (normal) command part for the A/C message. +/// @param[in] cmd Command to be set. +/// @note Only normal commands (=!isSpecialCommand()) can be set +/// with this function. +void IRFujitsuAC264::setCmd(const uint8_t cmd) { + switch (cmd) { + case kFujitsuAc264CmdCool: + case kFujitsuAc264CmdHeat: + case kFujitsuAc264CmdDry: + case kFujitsuAc264CmdAuto: + case kFujitsuAc264CmdFan: + case kFujitsuAc264CmdSleepTime: + _cmd = _.Cmd = cmd; + _.SubCmd = 1; + break; + case kFujitsuAc264CmdTemp: + case kFujitsuAc264CmdSwing: + case kFujitsuAc264CmdEconomy: + case kFujitsuAc264CmdClean: + case kFujitsuAc264CmdFanSpeed: + case kFujitsuAc264CmdFanAngle: + case kFujitsuAc264CmdCancelSleepTimer: + case kFujitsuAc264CmdOnTimer: + case kFujitsuAc264CmdOffTimer: + case kFujitsuAc264CmdCancelOnOffTimer: + _cmd = _.Cmd = cmd; + _.SubCmd = 0; + break; + default: + break; + } +} + +/// Get the requested command part for the A/C message. +/// @return The command code. +uint8_t IRFujitsuAC264::getCmd(void) const { + return _cmd; +} + +/// 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 IRFujitsuAC264::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kFujitsuAc264ModeCool; + case stdAc::opmode_t::kHeat: return kFujitsuAc264ModeHeat; + case stdAc::opmode_t::kDry: return kFujitsuAc264ModeDry; + case stdAc::opmode_t::kFan: return kFujitsuAc264ModeFan; + default: return kFujitsuAc264ModeAuto; + } +} + +/// 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 IRFujitsuAC264::convertFanSpeed(stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: return kFujitsuAc264FanSpeedQuiet; + case stdAc::fanspeed_t::kLow: return kFujitsuAc264FanSpeedLow; + case stdAc::fanspeed_t::kMedium: return kFujitsuAc264FanSpeedMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kFujitsuAc264FanSpeedHigh; + default: return kFujitsuAc264FanSpeedAuto; + } +} + +/// 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 IRFujitsuAC264::toCommonMode(const uint8_t mode) { + switch (mode) { + case kFujitsuAc264ModeCool: return stdAc::opmode_t::kCool; + case kFujitsuAc264ModeHeat: return stdAc::opmode_t::kHeat; + case kFujitsuAc264ModeDry: return stdAc::opmode_t::kDry; + case kFujitsuAc264ModeFan: 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 IRFujitsuAC264::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kFujitsuAc264FanSpeedHigh: return stdAc::fanspeed_t::kMax; + case kFujitsuAc264FanSpeedMed: return stdAc::fanspeed_t::kMedium; + case kFujitsuAc264FanSpeedLow: return stdAc::fanspeed_t::kLow; + case kFujitsuAc264FanSpeedQuiet: 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 a previous state. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRFujitsuAC264::toCommon(const stdAc::state_t *prev) { + stdAc::state_t result{}; + if (prev != NULL) result = *prev; + result.protocol = decode_type_t::FUJITSU_AC264; + checkSum(); + result.power = _cmd != kFujitsuAc264SpCmdTurnOff; + // Only update these settings if it is not a special command message, + // or we have no previous state info for those settings. + if (!isSpecialCommand() || prev == NULL) { + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.FanSpeed); + result.clean = getClean(); + result.swingv = getSwing()? stdAc::swingv_t::kAuto : + stdAc::swingv_t::kOff; + result.econo = getEconomy(); + result.clock = getClock(); + uint16_t sleep_time = getSleepTimer(); + result.sleep = sleep_time? sleep_time: -1; + } + result.quiet = getEcoFan(); + // Not supported. + result.turbo = false; + result.swingh = stdAc::swingh_t::kOff; + result.light = false; + result.filter = false; + result.beep = false; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRFujitsuAC264::toString(void) const { + String result = ""; + result.reserve(180); // Reserve some heap for the string to reduce fragging. + if (isSpecialCommand()) { // Special commands + result += kCommandStr; + result += kColonSpaceStr; + switch (_cmd) { + case kFujitsuAc264SpCmdTurnOff: + result += "Power Off"; + break; + case kFujitsuAc264SpCmdTogglePowerful: + result += kPowerfulStr; + break; + case kFujitsuAc264SpCmdEcoFanOff: + result += "Eco Fan "; + result += kOffStr; + break; + case kFujitsuAc264SpCmdEcoFanOn: + result += "Eco Fan "; + result += kOnStr; + break; + case kFujitsuAc264SpCmdOutsideQuietOff: + result += "Outside Quiet "; + result += kOffStr; + break; + case kFujitsuAc264SpCmdOutsideQuietOn: + result += "Outside Quiet "; + result += kOnStr; + break; + case kFujitsuAc264SpCmdToggleSterilization: + result += "Sterilization"; + break; + default: + result += kNAStr; + } + } else { // Normal commands + result += addBoolToString(true, kPowerStr, false); + // Mode + result += addModeToString(_.Mode, kFujitsuAc264ModeAuto, + kFujitsuAc264ModeCool, kFujitsuAc264ModeHeat, + kFujitsuAc264ModeDry, kFujitsuAc264ModeFan); + // Temp + float degrees = getTemp(); + result += addTempFloatToString(getTemp()); + // Temp in Auto + degrees = getTempAuto(); + String degrees_str = (degrees >= 0)? uint64ToString(degrees): + String(kDashStr) + uint64ToString(-degrees); + result += addLabeledString(degrees_str, "Temp (Auto)"); + if (((uint16_t)(2 * degrees)) & 1) result += F(".5"); + result += 'C'; + // Fan Speed + result += addFanToString(_.FanSpeed, kFujitsuAc264FanSpeedHigh, + kFujitsuAc264FanSpeedLow, kFujitsuAc264FanSpeedAuto, + kFujitsuAc264FanSpeedQuiet, kFujitsuAc264FanSpeedMed); + // Fan Angle + result += addIntToString(_.FanAngle, "Fan Angle"); + result += kSpaceLBraceStr; + switch (_.FanAngle) { + case kFujitsuAc264FanAngle1: + result += kHighestStr; + break; + case kFujitsuAc264FanAngle2: + result += kHighStr; + break; + case kFujitsuAc264FanAngle4: + result += kMiddleStr; + break; + case kFujitsuAc264FanAngle6: + result += kLowStr; + break; + case kFujitsuAc264FanAngle7: + result += kLowestStr; + break; + case kFujitsuAc264FanAngle3: + result += kUpperStr; + result += ' '; + result += kMiddleStr; + break; + case kFujitsuAc264FanAngle5: + result += kLowerStr; + result += ' '; + result += kMiddleStr; + break; + case kFujitsuAc264FanAngleStay: + result += "Stay"; + break; + default: + result += kUnknownStr; + } + result += ')'; + // Swing + result += addBoolToString(getSwing(), kSwingStr); + // Low Power + result += addBoolToString(getEconomy(), "Economy"); + // Clean + result += addBoolToString(getClean(), kCleanStr); + // Cmd + result += kCommaSpaceStr; + result += kCommandStr; + result += kColonSpaceStr; + switch (_.Cmd) { + case kFujitsuAc264CmdCool: + result += kCoolStr; + break; + case kFujitsuAc264CmdHeat: + result += kHeatStr; + break; + case kFujitsuAc264CmdDry: + if (_.WeakDry) + result += "Weak "; + result += kDryStr; + break; + case kFujitsuAc264CmdAuto: + result += kAutoStr; + break; + case kFujitsuAc264CmdFan: + result += kFanStr; + break; + case kFujitsuAc264CmdTemp: + result += kTempStr; + break; + case kFujitsuAc264CmdSwing: + result += kSwingStr; + break; + case kFujitsuAc264CmdSleepTime: + result += kSleepTimerStr; + break; + case kFujitsuAc264CmdEconomy: + result += "Economy"; + break; + case kFujitsuAc264CmdClean: + result += kCleanStr; + break; + case kFujitsuAc264CmdFanSpeed: + result += "Fan Speed"; + break; + case kFujitsuAc264CmdFanAngle: + result += "Fan Angle"; + break; + case kFujitsuAc264CmdCancelSleepTimer: + result += "Cancel Sleep Timer"; + break; + case kFujitsuAc264CmdOffTimer: + result += kOffTimerStr; + break; + case kFujitsuAc264CmdOnTimer: + result += kOnTimerStr; + break; + case kFujitsuAc264CmdCancelOnOffTimer: + result += "Cancel On/Off Timer"; + break; + default: + result += kNAStr; + } + // Clock + uint16_t mins = 0; + mins = getClock(); + result += addLabeledString(mins ? minsToString(mins) : kNAStr, + "Current Time"); + // Sleep Timer + mins = getSleepTimer(); + result += addLabeledString(mins ? minsToString(mins) : kOffStr, + kSleepTimerStr); + // On/Off Timer + uint8_t timer_enable = getTimerEnable(); + mins = getOnTimer(); + result += addLabeledString((timer_enable & kFujitsuAc264OnTimerEnable)? + minsToString(mins * 10): kOffStr, kOnTimerStr); + mins = getOffTimer(); + result += addLabeledString((timer_enable & kFujitsuAc264OffTimerEnable)? + minsToString(mins * 10): kOffStr, kOffTimerStr); + } + return result; +} + +#if DECODE_FUJITSU_AC264 +/// Decode the supplied Fujitsu 264 bit AC IR message if possible. +/// Status: STABLE / Working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// 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 A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeFujitsuAC264(decode_results* results, uint16_t offset, + const uint16_t nbits, + const bool strict) { + uint16_t dataBitsSoFar = 0; + uint8_t restLength = 0; + + // Have we got enough data to successfully decode? + if (results->rawlen < (2 * kFujitsuAc264BitsShort) + kHeader + kFooter - 1 + + offset) + return false; // Can't possibly be a valid message. + + // Compliance + if (strict) { + switch (nbits) { + case kFujitsuAc264Bits: + case kFujitsuAc264BitsMiddle: + case kFujitsuAc264BitsShort: break; + default: return false; // Must be called with the correct nr. of bits. + } + } + + // Header / Some of the Data + uint16_t used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, kFujitsuAc264BitsShort, + kFujitsuAcHdrMark, kFujitsuAcHdrSpace, // Header + kFujitsuAcBitMark, kFujitsuAcOneSpace, // Data + kFujitsuAcBitMark, kFujitsuAcZeroSpace, + 0, 0, // No Footer (yet) + false, _tolerance + kFujitsuAcExtraTolerance, 0, + false); // LSBF + offset += used; + dataBitsSoFar += kFujitsuAc264BitsShort; + + // Check if it has the Fujitsu AC264 protocol header + if (results->state[0] != 0x14 || results->state[1] != 0x63 || + results->state[2] != 0x00 || results->state[3] != 0x10 || + results->state[4] != 0x10) + return false; + + // Identify which command it is + switch (results->state[5]) { + case 0xFE: // Command length is normal or middle + restLength = results->state[6]; + // check the rest length + if ((restLength != 0x1A) && (restLength != 0x09)) + return false; + break; + case 0x51: // Command length is short + case 0x50: // Command length is short + case 0x39: // Command length is short + case 0x02: // Command length is short + if (results->state[6] == (uint8_t)~results->state[5]) { // checksum + results->decode_type = FUJITSU_AC264; + results->bits = dataBitsSoFar; + return true; + } else { + return false; + } + default: + return false; + } + + // Keep reading bytes until we either run out of message or state to fill. + match_result_t data_result; + for (uint16_t i = kFujitsuAc264StateLengthShort; + offset <= results->rawlen - 16 && i < kFujitsuAc264StateLengthShort + + restLength; i++, dataBitsSoFar += 8, offset += data_result.used) { + data_result = matchData( + &(results->rawbuf[offset]), 8, kFujitsuAcBitMark, kFujitsuAcOneSpace, + kFujitsuAcBitMark, kFujitsuAcZeroSpace, + _tolerance + kFujitsuAcExtraTolerance, 0, false); + if (data_result.success == false) break; // Fail + results->state[i] = data_result.data; + } + + // Compliance + if (strict) { + if (dataBitsSoFar != nbits) return false; + } + + // Compliance + switch (dataBitsSoFar) { + case kFujitsuAc264BitsMiddle: + case kFujitsuAc264Bits: + // Check if the state[5] is matched with the protocol. + if (results->state[5] != 0xFE) return false; + break; + default: + return false; // Unexpected size. + } + + if (!IRFujitsuAC264::validChecksum(results->state, dataBitsSoFar / 8)) + return false; + + // Success + results->decode_type = FUJITSU_AC264; + results->bits = dataBitsSoFar; + return true; // All good. +} +#endif // DECODE_FUJITSU_AC264 diff --git a/src/ir_Fujitsu.h b/src/ir_Fujitsu.h index ed4979dac..b880a4934 100644 --- a/src/ir_Fujitsu.h +++ b/src/ir_Fujitsu.h @@ -1,6 +1,7 @@ // Copyright 2017 Jonny Graham // Copyright 2018-2022 David Conran // Copyright 2021 siriuslzx +// Copyright 2023 Takeshi Shimizu /// @file /// @brief Support for Fujitsu A/C protocols. @@ -42,12 +43,14 @@ // Brand: OGeneral, Model: AR-RCL1E remote (ARRAH2E) // Brand: Fujitsu General, Model: AR-JW17 remote (ARDB1) // Brand: Fujitsu General, Model: AR-JW19 remote (ARDB1) +// Brand: Fujitsu, Model: AS-AH402M A/C (FUJITSU_AC264 AR-RLB2J) #ifndef IR_FUJITSU_H_ #define IR_FUJITSU_H_ #define __STDC_LIMIT_MACROS #include +#include #ifdef ARDUINO #include #endif @@ -153,6 +156,189 @@ const uint8_t kFujitsuAcOffTimer = 0b10; // 2 const uint8_t kFujitsuAcOnTimer = 0b11; // 3 const uint16_t kFujitsuAcTimerMax = 12 * 60; ///< Minutes. +/// Native representation of a Fujitsu 264 bit A/C message. +union Fujitsu264Protocol { + struct { + uint8_t raw[kFujitsuAc264StateLength]; ///< The state of the IR remote. + }; + struct { + uint8_t middlecode[kFujitsuAc264StateLengthMiddle]; + uint8_t shortcode[kFujitsuAc264StateLengthShort]; + }; + struct { + // Byte 0~1 + uint8_t :8; // Fixed header + uint8_t :8; // Fixed header + // Byte 2 + uint8_t :8; + // Byte 3-4 + uint8_t :8; + uint8_t :8; + // Byte 5 + uint8_t :8; + // Byte 6 + uint8_t RestLength :8; // Nr. of bytes in the message after this byte. + // Byte 7 + uint8_t Protocol :8; // Seems like a protocol version number. + // Byte 8 + uint64_t SubCmd :1; + uint64_t :1; + uint64_t Temp :6; // Temperature in modes except auto + // Byte 9 + uint64_t Mode :4; + uint64_t SleepTimerEnable :1; + uint64_t :3; + // Byte 10 + uint64_t FanSpeed :4; + uint64_t Swing :1; + uint64_t :3; + // Byte 11~12 + uint64_t SleepTimer :12; + uint64_t :4; + // Byte 13 + uint64_t :8; + // Byte 14 + uint64_t TempAuto :8; // Temperature in auto mode + // Byte 15 + uint64_t Economy :1; + uint64_t :7; + // Byte 16 + uint8_t Clean :1; + uint8_t :7; + // Byte 17 + uint8_t :8; + // Byte 18 + uint8_t Cmd :8; + // Byte 19 + uint8_t ClockHours :8; + // Byte 20 + uint8_t ClockMins :8; + // Byte 21 + uint8_t :5; + uint8_t WeakDry :1; // Valid only when mode = dry + uint8_t :2; + // Byte 22 + uint8_t TimerEnable :4; + uint8_t :4; + // Byte 23 + uint8_t OnTimer :8; + // Byte 24 + uint8_t OffTimer :8; + // Byte 25~27 + uint8_t :8; + uint8_t :8; + uint8_t :8; + // Byte 28 + uint8_t FanAngle :4; + uint8_t :4; + // Byte 29~31 + uint8_t :8; + uint8_t :8; + uint8_t :8; + // Byte 32 + uint8_t CheckSum :8; + }; +}; + +// Constants +const uint8_t kFujitsuAc264ModeAuto = 0x0; // 0b0000 +const uint8_t kFujitsuAc264ModeCool = 0x1; // 0b0001 +const uint8_t kFujitsuAc264ModeFan = 0x3; // 0b0011 +const uint8_t kFujitsuAc264ModeHeat = 0x4; // 0b0100 +const uint8_t kFujitsuAc264ModeDry = 0x5; // 0b0101 + +const uint8_t kFujitsuAc264CmdCool = 0x01; // 0b00000001 +const uint8_t kFujitsuAc264CmdHeat = 0x02; // 0b00000010 +const uint8_t kFujitsuAc264CmdDry = 0x03; // 0b00000011 +const uint8_t kFujitsuAc264CmdAuto = 0x04; // 0b00000100 +const uint8_t kFujitsuAc264CmdFan = 0x05; // 0b00000101 +const uint8_t kFujitsuAc264CmdTemp = 0x07; // 0b00000111 +const uint8_t kFujitsuAc264CmdSwing = 0x0B; // 0b00001011 +const uint8_t kFujitsuAc264CmdSleepTime = 0x0E; // 0b00001110 +const uint8_t kFujitsuAc264CmdEconomy = 0x14; // 0b00010100 +const uint8_t kFujitsuAc264CmdClean = 0x1B; // 0b00011011 +const uint8_t kFujitsuAc264CmdFanSpeed = 0x1E; // 0b00011110 +const uint8_t kFujitsuAc264CmdFanAngle = 0x22; // 0b00100010 +const uint8_t kFujitsuAc264CmdCancelSleepTimer = 0x30; // 0b00110000 +const uint8_t kFujitsuAc264CmdOnTimer = 0x39; // 0b00111001 +const uint8_t kFujitsuAc264CmdOffTimer = 0x3A; // 0b00111010 +const uint8_t kFujitsuAc264CmdCancelOnOffTimer = 0x3B; // 0b00111011 + +const uint8_t kFujitsuAc264SpCmdTogglePowerful = 0xF0; // 0b11110000 +const uint8_t kFujitsuAc264SpCmdTurnOff = 0xF1; // 0b11110001 +const uint8_t kFujitsuAc264SpCmdEcoFanOff = 0xF2; // 0b11110010 +const uint8_t kFujitsuAc264SpCmdEcoFanOn = 0xF3; // 0b11110011 +const uint8_t kFujitsuAc264SpCmdOutsideQuietOff = 0xF4; // 0b11110100 +const uint8_t kFujitsuAc264SpCmdOutsideQuietOn = 0xF5; // 0b11110101 +const uint8_t kFujitsuAc264SpCmdToggleSterilization = 0xF6; // 0b11110110 + +const uint8_t kFujitsuAc264FanSpeedAuto = 0x0; // 0b0000 +const uint8_t kFujitsuAc264FanSpeedQuiet = 0x1; // 0b0001 +const uint8_t kFujitsuAc264FanSpeedLow = 0x3; // 0b0011 +const uint8_t kFujitsuAc264FanSpeedMed = 0x6; // 0b0110 +const uint8_t kFujitsuAc264FanSpeedHigh = 0x8; // 0b1000 + +const uint8_t kFujitsuAc264FanAngle1 = 0x1; // 0b0001 +const uint8_t kFujitsuAc264FanAngle2 = 0x2; // 0b0010 +const uint8_t kFujitsuAc264FanAngle3 = 0x3; // 0b0011 +const uint8_t kFujitsuAc264FanAngle4 = 0x4; // 0b0100 +const uint8_t kFujitsuAc264FanAngle5 = 0x5; // 0b0101 +const uint8_t kFujitsuAc264FanAngle6 = 0x6; // 0b0110 +const uint8_t kFujitsuAc264FanAngle7 = 0x7; // 0b0111 +const uint8_t kFujitsuAc264FanAngleStay = 0xF; // 0b1111 + +const uint8_t kFujitsuAc264MinHeat = 16; // 16C +const uint8_t kFujitsuAc264MinTemp = 18; // 18C +const uint8_t kFujitsuAc264MaxTemp = 30; // 30C +const uint8_t kFujitsuAc264TempOffsetC = 16; +const int8_t kFujitsuAc264MinTempAuto = -2; // -2C +const int8_t kFujitsuAc264MaxTempAuto = 2; // 2C + +const uint16_t kFujitsuAc264SleepTimerMax = 12 * 60; ///< Minutes. +const uint8_t kFujitsuAc264OnOffTimerDisable = 0x0; // 0b0000 +const uint8_t kFujitsuAc264OnTimerEnable = 0x1; // 0b0001 +const uint8_t kFujitsuAc264OffTimerEnable = 0x2; // 0b0010 +const uint8_t kFujitsuAc264OnOffTimerEnable = 0x3; // 0b0011 +const uint16_t kFujitsuAc26OnOffTimerMax = 24 * 6 - 1; ///< 10 Minutes. + +/// Special command for Power Off +const uint8_t kFujitsuAc264StatesTurnOff + [kFujitsuAc264StateLengthShort] = { + 0x14, 0x63, 0x00, 0x10, 0x10, 0x02, 0xFD}; +/// Special command for Toggle Powerful +const uint8_t kFujitsuAc264StatesTogglePowerful + [kFujitsuAc264StateLengthShort] = { + 0x14, 0x63, 0x00, 0x10, 0x10, 0x39, 0xC6}; +/// Special command for Eco Fan Off +const uint8_t kFujitsuAc264StatesEcoFanOff + [kFujitsuAc264StateLengthShort] = { + 0x14, 0x63, 0x00, 0x10, 0x10, 0x51, 0xAE}; +/// Special command for Eco Fan On +const uint8_t kFujitsuAc264StatesEcoFanOn + [kFujitsuAc264StateLengthShort] = { + 0x14, 0x63, 0x00, 0x10, 0x10, 0x50, 0xAF}; +/// Special command for Outside Quiet Off +/// @note This command uses the same protocol with FujitsuAC's ARRAH2E, +/// but has different meaning. +const uint8_t kFujitsuAc264StatesOutsideQuietOff + [kFujitsuAc264StateLengthMiddle] = { + 0x14, 0x63, 0x00, 0x10, 0x10, 0xFE, 0x09, 0xC1, + 0x40, 0x01, 0x00, 0x00, 0xFE, 0xBF, 0x00, 0x41}; +/// Special command for Outside Quiet Off +/// @note This command uses the same protocol with FujitsuAC's ARRAH2E, +/// but has different meaning. +const uint8_t kFujitsuAc264StatesOutsideQuietOn + [kFujitsuAc264StateLengthMiddle] = { + 0x14, 0x63, 0x00, 0x10, 0x10, 0xFE, 0x09, 0xC1, + 0x40, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x41}; +/// Special command for Toggle Sterilization +/// @note This command uses the same protocol with FujitsuAC's ARRAH2E, +/// but has different meaning. +const uint8_t kFujitsuAc264StatesToggleSterilization + [kFujitsuAc264StateLengthMiddle] = { + 0x14, 0x63, 0x00, 0x10, 0x10, 0xFE, 0x09, 0xC1, + 0x60, 0x03, 0x00, 0x00, 0xFC, 0x9F, 0x00, 0x41}; + // Legacy defines. #define FUJITSU_AC_MODE_AUTO kFujitsuAcModeAuto #define FUJITSU_AC_MODE_COOL kFujitsuAcModeCool @@ -264,4 +450,96 @@ class IRFujitsuAC { void setOffSleepTimer(const uint16_t nr_mins); }; +/// Class for handling detailed Fujitsu 264 bit A/C messages. +class IRFujitsuAC264 { + public: + explicit IRFujitsuAC264(const uint16_t pin, + const bool inverted = false, + const bool use_modulation = true); +#if SEND_FUJITSU_AC264 + void send(const uint16_t repeat = kFujitsuAc264DefaultRepeat); + /// 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_FUJITSU_AC264 + void begin(void); + uint8_t* getRaw(void); + bool setRaw(const uint8_t newState[], const uint16_t length); + static bool validChecksum(uint8_t state[], + const uint16_t length = kFujitsuAc264StateLength); + + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + bool isTempStayed(void) const; + void setTemp(const float temp); + float getTemp(void) const; + void setTempAuto(const float temp); + float getTempAuto(void) const; + void setMode(const uint8_t mode, const bool weakdry = false); + uint8_t getMode(void) const; + bool isWeakDry(void) const; + void setFanSpeed(const uint8_t fanSpeed); + uint8_t getFanSpeed(void) const; + void setFanAngle(const uint8_t fanAngle); + uint8_t getFanAngle(void) const; + void setSwing(const bool on); + bool getSwing(void) const; + void setEconomy(const bool on); + bool getEconomy(void) const; + void setClean(const bool on); + bool getClean(void) const; + + void toggleSterilization(void); + void setOutsideQuiet(const bool on); + bool getOutsideQuiet(void) const; + void setEcoFan(const bool on); + bool getEcoFan(void) const; + void togglePowerful(void); + + void setClock(const uint16_t mins_since_midnight); + uint16_t getClock(void) const; + void setSleepTimer(const uint16_t mins); + uint16_t getSleepTimer(void) const; + void setTimerEnable(const uint8_t timer_enable); + uint8_t getTimerEnable(void) const; + void setOnTimer(const uint8_t mins10); + uint8_t getOnTimer(void) const; + void setOffTimer(const uint8_t mins10); + uint8_t getOffTimer(void) const; + + void setCmd(const uint8_t cmd); + uint8_t getCmd(void) const; + uint8_t getStateLength(void); + + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFanSpeed(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); + 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 + Fujitsu264Protocol _; + uint8_t _cmd; + bool _ispoweredon; + bool _isecofan; + bool _isoutsidequiet; + uint8_t _settemp; + void stateReset(void); + void checkSum(void); + bool isSpecialCommand(void) const; +}; + #endif // IR_FUJITSU_H_ diff --git a/src/locale/defaults.h b/src/locale/defaults.h index 438cc5da3..f268b88eb 100644 --- a/src/locale/defaults.h +++ b/src/locale/defaults.h @@ -844,6 +844,9 @@ D_STR_INDIRECT " " D_STR_MODE #ifndef D_STR_FUJITSU_AC #define D_STR_FUJITSU_AC "FUJITSU_AC" #endif // D_STR_FUJITSU_AC +#ifndef D_STR_FUJITSU_AC264 +#define D_STR_FUJITSU_AC264 "FUJITSU_AC264" +#endif // D_STR_FUJITSU_AC264 #ifndef D_STR_GICABLE #define D_STR_GICABLE "GICABLE" #endif // D_STR_GICABLE diff --git a/test/ir_Fujitsu_test.cpp b/test/ir_Fujitsu_test.cpp index ed5ee59f1..cf3faec2b 100644 --- a/test/ir_Fujitsu_test.cpp +++ b/test/ir_Fujitsu_test.cpp @@ -1,4 +1,5 @@ // Copyright 2017 Jonny Graham, David Conran +// Copyright 2023 Takeshi Shimizu #include "IRac.h" #include "IRrecv_test.h" #include "IRsend.h" @@ -1171,6 +1172,13 @@ TEST(TestUtils, Housekeeping) { ASSERT_TRUE(IRac::isProtocolSupported(decode_type_t::FUJITSU_AC)); ASSERT_EQ(0, IRsend::defaultBits(decode_type_t::FUJITSU_AC)); // No default ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::FUJITSU_AC)); + + ASSERT_EQ("FUJITSU_AC264", typeToString(decode_type_t::FUJITSU_AC264)); + ASSERT_EQ(decode_type_t::FUJITSU_AC264, strToDecodeType("FUJITSU_AC264")); + ASSERT_TRUE(hasACState(decode_type_t::FUJITSU_AC264)); + ASSERT_TRUE(IRac::isProtocolSupported(decode_type_t::FUJITSU_AC264)); + ASSERT_EQ(264, IRsend::defaultBits(decode_type_t::FUJITSU_AC264)); + ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::FUJITSU_AC264)); } TEST(TestIRFujitsuACClass, Temperature) { @@ -1446,3 +1454,767 @@ TEST(TestIRFujitsuACClass, Improve10CHeat) { EXPECT_EQ(fujitsu_ac_remote_model_t::ARRAH2E, ac.toCommon().model); EXPECT_EQ(kFujitsuAcMinHeat, ac.toCommon().degrees); } + +// Tests for Fujitsu 264 bit A/C methods. +TEST(TestDecodeFujitsuAc264, RealExample) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + uint16_t rawData[531] = {3378, 1534, 498, 322, 526, 294, 524, 1116, 522, 302, + 492, 1166, 472, 350, 470, 352, 466, 352, 468, 1172, 468, 1176, 466, 352, + 466, 354, 466, 356, 466, 1176, 464, 1174, 468, 354, 466, 356, 464, 352, + 468, 356, 466, 352, 466, 354, 466, 356, 464, 354, 468, 352, 466, 356, 466, + 352, 466, 356, 464, 356, 464, 1178, 464, 356, 466, 356, 464, 356, 462, 356, + 466, 356, 464, 356, 462, 356, 464, 1178, 464, 356, 466, 356, 462, 358, 464, + 354, 464, 1176, 464, 1178, 464, 1180, 462, 1176, 454, 1188, 442, 1202, 440, + 1198, 442, 380, 464, 1176, 440, 382, 442, 1200, 438, 1202, 440, 380, 440, + 382, 438, 382, 440, 404, 416, 382, 440, 380, 440, 382, 438, 380, 440, 380, + 440, 1202, 440, 404, 414, 1202, 440, 382, 440, 380, 438, 1204, 440, 380, + 440, 382, 436, 400, 422, 1226, 416, 1226, 416, 404, 416, 406, 414, 406, + 414, 406, 414, 406, 416, 404, 416, 404, 418, 404, 414, 406, 416, 404, 418, + 404, 416, 404, 416, 404, 416, 404, 414, 404, 416, 406, 416, 404, 414, 404, + 416, 406, 414, 406, 414, 406, 416, 404, 414, 406, 414, 408, 414, 404, 414, + 408, 412, 406, 416, 404, 414, 406, 414, 408, 414, 406, 414, 408, 412, 406, + 414, 408, 412, 408, 414, 404, 414, 408, 412, 406, 414, 404, 416, 406, 412, + 408, 414, 404, 414, 410, 412, 408, 412, 406, 412, 408, 414, 408, 414, 406, + 412, 1230, 412, 408, 414, 408, 412, 1228, 414, 408, 412, 408, 412, 406, + 414, 408, 412, 1228, 414, 1228, 412, 408, 416, 408, 410, 408, 412, 408, + 414, 408, 412, 408, 412, 410, 410, 408, 410, 408, 412, 408, 414, 408, 412, + 408, 414, 408, 412, 1228, 414, 408, 412, 408, 410, 410, 410, 410, 412, 406, + 412, 408, 412, 410, 410, 1232, 410, 408, 410, 410, 410, 412, 386, 1254, + 412, 410, 408, 412, 412, 408, 410, 1230, 412, 1232, 410, 1230, 408, 1232, + 410, 1232, 386, 432, 386, 436, 408, 410, 386, 434, 404, 418, 386, 434, 386, + 432, 390, 432, 386, 1254, 388, 1254, 386, 434, 388, 432, 388, 434, 386, + 436, 384, 456, 362, 1256, 386, 434, 386, 434, 386, 434, 384, 436, 386, 434, + 386, 1256, 386, 458, 360, 434, 388, 1254, 386, 434, 388, 434, 386, 434, + 388, 434, 384, 460, 360, 436, 384, 458, 360, 436, 386, 460, 362, 434, 386, + 458, 360, 460, 360, 460, 362, 460, 360, 458, 362, 460, 360, 458, 360, 460, + 362, 460, 360, 460, 360, 460, 362, 456, 362, 458, 360, 462, 360, 458, 362, + 458, 362, 456, 364, 458, 362, 460, 360, 460, 362, 458, 362, 458, 360, 460, + 360, 458, 360, 1282, 360, 1280, 362, 1280, 360, 1282, 360, 1280, 360, 1280, + 360, 1282, 360, 1278, 362, 1280, 360, 1282, 360, 1280, 362, 1280, 360, + 1282, 360, 1280, 362, 1280, 360, 1282, 360, 1282, 362, 1280, 360, 1280, + 360, 1282, 358, 1282, 360, 1282, 360, 1282, 358, 1282, 360, 462, 358, 462, + 358, 462, 360, 460, 360, 462, 360, 484, 334, 462, 360, 462, 358, 458, 360, + 462, 356, 1282, 360, 1282, 360, 1308, 332, 486, 334, 1284, 358, 462, 360 + }; // FUJITSU_AC264 + + uint8_t expectedState[kFujitsuAc264StateLength] = { + 0x14, 0x63, 0x00, 0x10, 0x10, 0xFE, 0x1A, 0x40, + 0x89, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, + 0x06, 0x00, 0x01, 0x11, 0x1F, 0x60, 0x10, 0x24, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, + 0x5C}; + + irsend.begin(); + irsend.reset(); + irsend.sendRaw(rawData, 531, 38000); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + ASSERT_EQ(FUJITSU_AC264, irsend.capture.decode_type); + ASSERT_EQ(kFujitsuAc264Bits, irsend.capture.bits); + EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits); + IRFujitsuAC264 ac(kGpioUnused); + ac.setRaw(irsend.capture.state, irsend.capture.bits / 8); + EXPECT_EQ("Power: On, Mode: 1 (Cool), Temp: 25C, Temp (Auto): 0C, " + "Fan: 0 (Auto), Fan Angle: 15 (Stay), Swing: Off, Economy: Off, " + "Clean: Off, Command: Cool, Current Time: 17:31, Sleep Timer: Off, " + "On Timer: Off, Off Timer: Off", + ac.toString()); +} + +TEST(TestDecodeFujitsuAc264, SyntheticExample) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + + uint8_t sendCode[kFujitsuAc264StateLength] = { + 0x14, 0x63, 0x00, 0x10, 0x10, 0xFE, 0x1A, 0x40, + 0x89, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, + 0x06, 0x00, 0x01, 0x11, 0x1F, 0x60, 0x10, 0x24, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, + 0x5C}; + + irsend.begin(); + irsend.reset(); + irsend.sendFujitsuAC264(sendCode); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + ASSERT_EQ(FUJITSU_AC264, irsend.capture.decode_type); + ASSERT_EQ(kFujitsuAc264Bits, irsend.capture.bits); + EXPECT_STATE_EQ(sendCode, irsend.capture.state, irsend.capture.bits); + EXPECT_EQ( + "f38000d50" + "m3324s1574" + "m448s390m448s390m448s1182m448s390m448s1182m448s390m448s390m448s390" + "m448s1182m448s1182m448s390m448s390m448s390m448s1182m448s1182m448s390" + "m448s390m448s390m448s390m448s390m448s390m448s390m448s390m448s390" + "m448s390m448s390m448s390m448s390m448s1182m448s390m448s390m448s390" + "m448s390m448s390m448s390m448s390m448s1182m448s390m448s390m448s390" + "m448s390m448s1182m448s1182m448s1182m448s1182m448s1182m448s1182m448s1182" + "m448s390m448s1182m448s390m448s1182m448s1182m448s390m448s390m448s390" + "m448s390m448s390m448s390m448s390m448s390m448s390m448s1182m448s390" + "m448s1182m448s390m448s390m448s1182m448s390m448s390m448s390m448s1182" + "m448s1182m448s390m448s390m448s390m448s390m448s390m448s390m448s390" + "m448s390m448s390m448s390m448s390m448s390m448s390m448s390m448s390" + "m448s390m448s390m448s390m448s390m448s390m448s390m448s390m448s390" + "m448s390m448s390m448s390m448s390m448s390m448s390m448s390m448s390" + "m448s390m448s390m448s390m448s390m448s390m448s390m448s390m448s390" + "m448s390m448s390m448s390m448s390m448s390m448s390m448s390m448s390" + "m448s390m448s1182m448s390m448s390m448s1182m448s390m448s390m448s390" + "m448s390m448s1182m448s1182m448s390m448s390m448s390m448s390m448s390" + "m448s390m448s390m448s390m448s390m448s390m448s390m448s390m448s390" + "m448s1182m448s390m448s390m448s390m448s390m448s390m448s390m448s390" + "m448s1182m448s390m448s390m448s390m448s1182m448s390m448s390m448s390" + "m448s1182m448s1182m448s1182m448s1182m448s1182m448s390m448s390m448s390" + "m448s390m448s390m448s390m448s390m448s390m448s1182m448s1182m448s390" + "m448s390m448s390m448s390m448s390m448s1182m448s390m448s390m448s390" + "m448s390m448s390m448s1182m448s390m448s390m448s1182m448s390m448s390" + "m448s390m448s390m448s390m448s390m448s390m448s390m448s390m448s390" + "m448s390m448s390m448s390m448s390m448s390m448s390m448s390m448s390" + "m448s390m448s390m448s390m448s390m448s390m448s390m448s390m448s390" + "m448s390m448s390m448s390m448s390m448s390m448s390m448s390m448s390" + "m448s1182m448s1182m448s1182m448s1182m448s1182m448s1182m448s1182m448s1182" + "m448s1182m448s1182m448s1182m448s1182m448s1182m448s1182m448s1182m448s1182" + "m448s1182m448s1182m448s1182m448s1182m448s1182m448s1182m448s1182m448s1182" + "m448s390m448s390m448s390m448s390m448s390m448s390m448s390m448s390" + "m448s390m448s390m448s1182m448s1182m448s1182m448s390m448s1182m448s390" + "m448s8100", + irsend.outputStr()); +} + +TEST(TestFujitsuAc264Class, toCommon) { + IRFujitsuAC264 ac(kGpioUnused); + ac.setPower(true); + ac.setMode(kFujitsuAc264ModeCool); + ac.setTemp(20); + ac.setFanSpeed(kFujitsuAc264FanSpeedHigh); + ac.setSwing(true); + ac.setEcoFan(false); + ac.setClock(1 * 60 + 23); // "1:23" + ac.setSleepTimer(2 * 60); + // Now test it. + ASSERT_EQ(decode_type_t::FUJITSU_AC264, ac.toCommon().protocol); + ASSERT_EQ(-1, ac.toCommon().model); + ASSERT_TRUE(ac.toCommon().power); + ASSERT_TRUE(ac.toCommon().celsius); + ASSERT_EQ(20, ac.toCommon().degrees); + ASSERT_FALSE(ac.toCommon().quiet); + ASSERT_EQ(stdAc::opmode_t::kCool, ac.toCommon().mode); + ASSERT_EQ(stdAc::fanspeed_t::kMax, ac.toCommon().fanspeed); + ASSERT_EQ(stdAc::swingv_t::kAuto, ac.toCommon().swingv); + ASSERT_EQ(83, ac.toCommon().clock); + ASSERT_EQ(120, ac.toCommon().sleep); + // Unsupported. + ASSERT_EQ(stdAc::swingh_t::kOff, ac.toCommon().swingh); + ASSERT_FALSE(ac.toCommon().turbo); + ASSERT_FALSE(ac.toCommon().clean); + ASSERT_FALSE(ac.toCommon().econo); + ASSERT_FALSE(ac.toCommon().light); + ASSERT_FALSE(ac.toCommon().filter); + ASSERT_FALSE(ac.toCommon().beep); +} + +TEST(TestFujitsuAc264Class, Power) { + IRFujitsuAC264 ac(kGpioUnused); + ac.begin(); + + // Value from getPower is not updated untill ac.send() + ac.on(); + EXPECT_FALSE(ac.getPower()); + + ac.on(); + ac.send(); + EXPECT_TRUE(ac.getPower()); + + // Value from getPower is not updated untill ac.send() + ac.off(); + EXPECT_TRUE(ac.getPower()); + + ac.off(); + ac.send(); + EXPECT_FALSE(ac.getPower()); + + // Value from getPower is not updated untill ac.send() + ac.setPower(true); + EXPECT_FALSE(ac.getPower()); + + ac.setPower(true); + ac.send(); + EXPECT_TRUE(ac.getPower()); + + // Value from getPower is not updated untill ac.send() + ac.setPower(false); + EXPECT_TRUE(ac.getPower()); + + ac.setPower(false); + ac.send(); + EXPECT_FALSE(ac.getPower()); +} + +TEST(TestFujitsuAc264Class, Temperature) { + IRFujitsuAC264 ac(kGpioUnused); + ac.begin(); + + ac.setMode(kFujitsuAc264ModeHeat); + // Value over maximum is fixed to maximum (30C) + ac.setTemp(40); + EXPECT_EQ(30, ac.getTemp()); + + // Value under minimum is fixed to minimum (16C in Heat) + ac.setTemp(10); + EXPECT_EQ(16, ac.getTemp()); + + ac.setMode(kFujitsuAc264ModeCool); + // Value over maximum is fixed to maximum (30C) + ac.setTemp(40); + EXPECT_EQ(30, ac.getTemp()); + + // Value under minimum is fixed to minimum (18C) + ac.setTemp(10); + EXPECT_EQ(18, ac.getTemp()); + + // Valid values in suppoerted range + ac.setMode(kFujitsuAc264ModeHeat); + for (float i = 16; i <= 30; i += 0.5) { + ac.setTemp(i); + EXPECT_EQ(i, ac.getTemp()); + } + + // Valid values in suppoerted range + ac.setMode(kFujitsuAc264ModeCool); + for (float i = 18; i <= 30; i += 0.5) { + ac.setTemp(i); + EXPECT_EQ(i, ac.getTemp()); + } + + // Value in supported range, but not multiple of 0.5 + // Fractional part of the value which is not multiple of 0.5 is truncated. + ac.setTemp(22.9); + EXPECT_EQ(22.5, ac.getTemp()); + + ac.setTemp(20.1); + EXPECT_EQ(20, ac.getTemp()); +} + +TEST(TestFujitsuAc264Class, TemperatureAuto) { + IRFujitsuAC264 ac(kGpioUnused); + ac.begin(); + + // Value over maximum is fixed to maximum (+2C) + ac.setTempAuto(10); + EXPECT_EQ(2, ac.getTempAuto()); + + // Value under minimum is fixed to minimum (-2C) + ac.setTempAuto(-10); + EXPECT_EQ(-2, ac.getTempAuto()); + + // Valid values in suppoerted range + for (float i = -2; i <= 2; i += 0.5) { + ac.setTempAuto(i); + EXPECT_EQ(i, ac.getTempAuto()); + } + + // Value in supported range, but not multiple of 0.5 + // Fractional part of the value which is not multiple of 0.5 is truncated. + ac.setTempAuto(0.8); + EXPECT_EQ(0.5, ac.getTempAuto()); + + ac.setTempAuto(1.2); + EXPECT_EQ(1, ac.getTempAuto()); + + ac.setTempAuto(-1.4); + EXPECT_EQ(-1, ac.getTempAuto()); +} + +TEST(TestFujitsuAc264Class, Mode) { + IRFujitsuAC264 ac(kGpioUnused); + ac.begin(); + + // Unexpected value should default to Auto. + ac.setMode(2); + EXPECT_EQ(kFujitsuAc264ModeAuto, ac.getMode()); + EXPECT_FALSE(ac.isWeakDry()); + + // Unexpected value should default to Auto. + ac.setMode(255); + EXPECT_EQ(kFujitsuAc264ModeAuto, ac.getMode()); + EXPECT_FALSE(ac.isWeakDry()); + + ac.setMode(kFujitsuAc264ModeAuto); + EXPECT_EQ(kFujitsuAc264ModeAuto, ac.getMode()); + EXPECT_FALSE(ac.isWeakDry()); + + ac.setMode(kFujitsuAc264ModeCool); + EXPECT_EQ(kFujitsuAc264ModeCool, ac.getMode()); + EXPECT_FALSE(ac.isWeakDry()); + + ac.setMode(kFujitsuAc264ModeFan); + EXPECT_EQ(kFujitsuAc264ModeFan, ac.getMode()); + EXPECT_FALSE(ac.isWeakDry()); + + ac.setMode(kFujitsuAc264ModeHeat); + EXPECT_EQ(kFujitsuAc264ModeHeat, ac.getMode()); + EXPECT_FALSE(ac.isWeakDry()); + + ac.setMode(kFujitsuAc264ModeDry); + EXPECT_EQ(kFujitsuAc264ModeDry, ac.getMode()); + EXPECT_FALSE(ac.isWeakDry()); + + ac.setMode(kFujitsuAc264ModeDry, true); + EXPECT_EQ(kFujitsuAc264ModeDry, ac.getMode()); + EXPECT_TRUE(ac.isWeakDry()); +} + +TEST(TestFujitsuAc264Class, FanSpeed) { + IRFujitsuAC264 ac(kGpioUnused); + ac.begin(); + + // Unexpected value should default to Auto. + ac.setFanSpeed(0); + EXPECT_EQ(kFujitsuAc264FanSpeedAuto, ac.getFanSpeed()); + + // Unexpected value should default to Auto. + ac.setFanSpeed(255); + EXPECT_EQ(kFujitsuAc264FanSpeedAuto, ac.getFanSpeed()); + + // Unexpected value should default to Auto. + ac.setFanSpeed(2); + EXPECT_EQ(kFujitsuAc264FanSpeedAuto, ac.getFanSpeed()); + + // Unexpected value should default to Auto. + ac.setFanSpeed(4); + EXPECT_EQ(kFujitsuAc264FanSpeedAuto, ac.getFanSpeed()); + + // Unexpected value should default to Auto. + ac.setFanSpeed(5); + EXPECT_EQ(kFujitsuAc264FanSpeedAuto, ac.getFanSpeed()); + + // Unexpected value should default to Auto. + ac.setFanSpeed(7); + EXPECT_EQ(kFujitsuAc264FanSpeedAuto, ac.getFanSpeed()); + + // Beyond Max should default to Auto. + ac.setFanSpeed(kFujitsuAc264FanSpeedHigh + 1); + EXPECT_EQ(kFujitsuAc264FanSpeedAuto, ac.getFanSpeed()); + + ac.setFanSpeed(kFujitsuAc264FanSpeedAuto); + EXPECT_EQ(kFujitsuAc264FanSpeedAuto, ac.getFanSpeed()); + + ac.setFanSpeed(kFujitsuAc264FanSpeedQuiet); + EXPECT_EQ(kFujitsuAc264FanSpeedQuiet, ac.getFanSpeed()); + + ac.setFanSpeed(kFujitsuAc264FanSpeedLow); + EXPECT_EQ(kFujitsuAc264FanSpeedLow, ac.getFanSpeed()); + + ac.setFanSpeed(kFujitsuAc264FanSpeedMed); + EXPECT_EQ(kFujitsuAc264FanSpeedMed, ac.getFanSpeed()); + + ac.setFanSpeed(kFujitsuAc264FanSpeedHigh); + EXPECT_EQ(kFujitsuAc264FanSpeedHigh, ac.getFanSpeed()); +} + +TEST(TestFujitsuAc264Class, FanAngle) { + IRFujitsuAC264 ac(kGpioUnused); + ac.begin(); + + // Unexpected value should default to Stay. + ac.setFanAngle(0); + EXPECT_EQ(kFujitsuAc264FanAngleStay, ac.getFanAngle()); + + // Unexpected value should default to Stay. + ac.setFanAngle(255); + EXPECT_EQ(kFujitsuAc264FanAngleStay, ac.getFanAngle()); + + // Unexpected value should default to Stay. + ac.setFanAngle(8); + EXPECT_EQ(kFujitsuAc264FanAngleStay, ac.getFanAngle()); + + // Unexpected value should default to Stay. + ac.setFanAngle(13); + EXPECT_EQ(kFujitsuAc264FanAngleStay, ac.getFanAngle()); + + // Unexpected value should default to Stay. + ac.setFanAngle(32); + EXPECT_EQ(kFujitsuAc264FanAngleStay, ac.getFanAngle()); + + ac.setFanAngle(kFujitsuAc264FanAngle1); + EXPECT_EQ(kFujitsuAc264FanAngle1, ac.getFanAngle()); + + ac.setFanAngle(kFujitsuAc264FanAngle2); + EXPECT_EQ(kFujitsuAc264FanAngle2, ac.getFanAngle()); + + ac.setFanAngle(kFujitsuAc264FanAngle3); + EXPECT_EQ(kFujitsuAc264FanAngle3, ac.getFanAngle()); + + ac.setFanAngle(kFujitsuAc264FanAngle4); + EXPECT_EQ(kFujitsuAc264FanAngle4, ac.getFanAngle()); + + ac.setFanAngle(kFujitsuAc264FanAngle5); + EXPECT_EQ(kFujitsuAc264FanAngle5, ac.getFanAngle()); + + ac.setFanAngle(kFujitsuAc264FanAngle6); + EXPECT_EQ(kFujitsuAc264FanAngle6, ac.getFanAngle()); + + ac.setFanAngle(kFujitsuAc264FanAngle7); + EXPECT_EQ(kFujitsuAc264FanAngle7, ac.getFanAngle()); + + ac.setFanAngle(kFujitsuAc264FanAngleStay); + EXPECT_EQ(kFujitsuAc264FanAngleStay, ac.getFanAngle()); +} + +TEST(TestFujitsuAc264Class, Swing) { + IRFujitsuAC264 ac(kGpioUnused); + ac.begin(); + + ac.setSwing(true); + EXPECT_TRUE(ac.getSwing()); + + ac.setSwing(false); + EXPECT_FALSE(ac.getSwing()); +} + +TEST(TestFujitsuAc264Class, Economy) { + IRFujitsuAC264 ac(kGpioUnused); + ac.begin(); + + ac.setEconomy(true); + EXPECT_TRUE(ac.getEconomy()); + + ac.setEconomy(false); + EXPECT_FALSE(ac.getEconomy()); +} + +TEST(TestFujitsuAc264Class, Clean) { + IRFujitsuAC264 ac(kGpioUnused); + ac.begin(); + + ac.setClean(true); + EXPECT_TRUE(ac.getClean()); + + ac.setClean(false); + EXPECT_FALSE(ac.getClean()); +} + +TEST(TestFujitsuAc264Class, Clock) { + IRFujitsuAC264 ac(kGpioUnused); + ac.begin(); + + // Unexpected value should default to 0. + ac.setClock(2000); + EXPECT_EQ(0, ac.getClock()); + + // Valid values in suppoerted range + for (uint16_t i = 0; i < 1440; ++i) { + ac.setClock(i); + EXPECT_EQ(i, ac.getClock()); + } +} + +TEST(TestFujitsuAc264Class, SleepTimer) { + IRFujitsuAC264 ac(kGpioUnused); + ac.begin(); + + // Unexpected value should default to 0 (invalid). + ac.setSleepTimer(12 * 60 + 1); + EXPECT_EQ(0, ac.getSleepTimer()); + + // Valid values in suppoerted range + for (uint16_t i = 0; i <= 12; ++i) { + ac.setSleepTimer(i * 60); + EXPECT_EQ(i * 60, ac.getSleepTimer()); + } +} + +TEST(TestFujitsuAc264Class, OnTimer) { + IRFujitsuAC264 ac(kGpioUnused); + ac.begin(); + + // Unexpected value is ignored. + ac.setOnTimer(144); + EXPECT_EQ(0, ac.getOnTimer()); + + // Valid values in suppoerted range + for (uint8_t i = 0; i < 144; ++i) { + ac.setOnTimer(i); + EXPECT_EQ(i, ac.getOnTimer()); + } + + // Unexpected value is ignored. + ac.setOnTimer(255); + EXPECT_EQ(143, ac.getOnTimer()); + + // On timer enabled + ac.setTimerEnable(kFujitsuAc264OnTimerEnable); + EXPECT_EQ(kFujitsuAc264OnTimerEnable, ac.getTimerEnable()); + + // On timer disabled + ac.setTimerEnable(kFujitsuAc264OnOffTimerDisable); + EXPECT_EQ(kFujitsuAc264OnOffTimerDisable, ac.getTimerEnable()); +} + +TEST(TestFujitsuAc264Class, OffTimer) { + IRFujitsuAC264 ac(kGpioUnused); + ac.begin(); + + // Unexpected value is ignored. + ac.setOffTimer(144); + EXPECT_EQ(0, ac.getOffTimer()); + + // Valid values in suppoerted range + for (uint8_t i = 0; i < 144; ++i) { + ac.setOffTimer(i); + EXPECT_EQ(i, ac.getOffTimer()); + } + + // Unexpected value is ignored. + ac.setOffTimer(255); + EXPECT_EQ(143, ac.getOffTimer()); + + // Off timer enabled + ac.setTimerEnable(kFujitsuAc264OffTimerEnable); + EXPECT_EQ(kFujitsuAc264OffTimerEnable, ac.getTimerEnable()); + + // On & off timer enabled + ac.setTimerEnable(kFujitsuAc264OnOffTimerEnable); + EXPECT_EQ(kFujitsuAc264OnOffTimerEnable, ac.getTimerEnable()); + + // On & Off timer disabled + ac.setTimerEnable(kFujitsuAc264OnOffTimerDisable); + EXPECT_EQ(kFujitsuAc264OnOffTimerDisable, ac.getTimerEnable()); +} + +TEST(TestFujitsuAc264Class, Sterilization) { + IRFujitsuAC264 ac(kGpioUnused); + ac.begin(); + + ac.on(); + ac.send(); + + // When AC is powered on, sterilization is ignored. + ac.toggleSterilization(); + EXPECT_EQ(kFujitsuAc264CmdCool, ac.getCmd()); + + ac.off(); + ac.send(); + + // When AC is powered off, sterilization is accepted. + ac.toggleSterilization(); + EXPECT_EQ(kFujitsuAc264SpCmdToggleSterilization, ac.getCmd()); +} + +TEST(TestFujitsuAc264Class, OutsideQuiet) { + IRFujitsuAC264 ac(kGpioUnused); + ac.begin(); + + ac.on(); + ac.send(); + + // When AC is powered on, outside quiet is ignored. + ac.setOutsideQuiet(true); + ac.send(); + EXPECT_FALSE(ac.getOutsideQuiet()); + + ac.off(); + ac.send(); + + // When AC is powered off, outside quiet is accepted. + ac.setOutsideQuiet(true); + ac.send(); + EXPECT_TRUE(ac.getOutsideQuiet()); + + ac.setOutsideQuiet(false); + ac.send(); + EXPECT_FALSE(ac.getOutsideQuiet()); +} + +TEST(TestFujitsuAc264Class, EcoFan) { + IRFujitsuAC264 ac(kGpioUnused); + ac.begin(); + + ac.on(); + ac.send(); + + // When AC is powered on, eco fan is ignored. + ac.setEcoFan(true); + ac.send(); + EXPECT_FALSE(ac.getEcoFan()); + + ac.off(); + ac.send(); + + // When AC is powered off, eco fan is accepted. + ac.setEcoFan(true); + ac.send(); + EXPECT_TRUE(ac.getEcoFan()); + + ac.setEcoFan(false); + ac.send(); + EXPECT_FALSE(ac.getEcoFan()); +} + +TEST(TestFujitsuAc264Class, Powerful) { + IRFujitsuAC264 ac(kGpioUnused); + ac.begin(); + + ac.off(); + ac.send(); + + // When AC is powered off, powerful is ignored. + ac.togglePowerful(); + ac.send(); + EXPECT_EQ(kFujitsuAc264SpCmdTurnOff, ac.getCmd()); + + ac.on(); + ac.send(); + + // When AC is powered on, powerful is accepted. + ac.togglePowerful(); + ac.send(); + EXPECT_EQ(kFujitsuAc264SpCmdTogglePowerful, ac.getCmd()); + + ac.togglePowerful(); + ac.send(); + EXPECT_EQ(kFujitsuAc264SpCmdTogglePowerful, ac.getCmd()); +} + +TEST(TestFujitsuAc264Class, NormalCommands) { + IRFujitsuAC264 ac(kGpioUnused); + ac.begin(); + + ac.on(); + ac.send(); + + // Special commands are ignored. + ac.setCmd(kFujitsuAc264SpCmdTogglePowerful); + EXPECT_EQ(kFujitsuAc264CmdCool, ac.getCmd()); + + ac.setCmd(kFujitsuAc264SpCmdTurnOff); + EXPECT_EQ(kFujitsuAc264CmdCool, ac.getCmd()); + + ac.setCmd(kFujitsuAc264SpCmdEcoFanOff); + EXPECT_EQ(kFujitsuAc264CmdCool, ac.getCmd()); + + ac.setCmd(kFujitsuAc264SpCmdEcoFanOn); + EXPECT_EQ(kFujitsuAc264CmdCool, ac.getCmd()); + + ac.setCmd(kFujitsuAc264SpCmdOutsideQuietOff); + EXPECT_EQ(kFujitsuAc264CmdCool, ac.getCmd()); + + ac.setCmd(kFujitsuAc264SpCmdOutsideQuietOn); + EXPECT_EQ(kFujitsuAc264CmdCool, ac.getCmd()); + + ac.setCmd(kFujitsuAc264SpCmdToggleSterilization); + EXPECT_EQ(kFujitsuAc264CmdCool, ac.getCmd()); + + // Normal commands can be set. + ac.setCmd(kFujitsuAc264CmdCool); + EXPECT_EQ(kFujitsuAc264CmdCool, ac.getCmd()); + + ac.setCmd(kFujitsuAc264CmdHeat); + EXPECT_EQ(kFujitsuAc264CmdHeat, ac.getCmd()); + + ac.setCmd(kFujitsuAc264CmdDry); + EXPECT_EQ(kFujitsuAc264CmdDry, ac.getCmd()); + + ac.setCmd(kFujitsuAc264CmdAuto); + EXPECT_EQ(kFujitsuAc264CmdAuto, ac.getCmd()); + + ac.setCmd(kFujitsuAc264CmdFan); + EXPECT_EQ(kFujitsuAc264CmdFan, ac.getCmd()); + + ac.setCmd(kFujitsuAc264CmdTemp); + EXPECT_EQ(kFujitsuAc264CmdTemp, ac.getCmd()); + + ac.setCmd(kFujitsuAc264CmdSwing); + EXPECT_EQ(kFujitsuAc264CmdSwing, ac.getCmd()); + + ac.setCmd(kFujitsuAc264CmdSleepTime); + EXPECT_EQ(kFujitsuAc264CmdSleepTime, ac.getCmd()); + + ac.setCmd(kFujitsuAc264CmdEconomy); + EXPECT_EQ(kFujitsuAc264CmdEconomy, ac.getCmd()); + + ac.setCmd(kFujitsuAc264CmdClean); + EXPECT_EQ(kFujitsuAc264CmdClean, ac.getCmd()); + + ac.setCmd(kFujitsuAc264CmdFanSpeed); + EXPECT_EQ(kFujitsuAc264CmdFanSpeed, ac.getCmd()); + + ac.setCmd(kFujitsuAc264CmdFanAngle); + EXPECT_EQ(kFujitsuAc264CmdFanAngle, ac.getCmd()); + + ac.setCmd(kFujitsuAc264CmdCancelSleepTimer); + EXPECT_EQ(kFujitsuAc264CmdCancelSleepTimer, ac.getCmd()); + + ac.setCmd(kFujitsuAc264CmdOnTimer); + EXPECT_EQ(kFujitsuAc264CmdOnTimer, ac.getCmd()); + + ac.setCmd(kFujitsuAc264CmdOffTimer); + EXPECT_EQ(kFujitsuAc264CmdOffTimer, ac.getCmd()); + + ac.setCmd(kFujitsuAc264CmdCancelOnOffTimer); + EXPECT_EQ(kFujitsuAc264CmdCancelOnOffTimer, ac.getCmd()); +} + +TEST(TestFujitsuAc264Class, SpecialCommands) { + IRFujitsuAC264 ac(kGpioUnused); + ac.begin(); + + uint8_t expected_turnoff[7] = {0x14, 0x63, 0x0, 0x10, 0x10, 0x02, 0xFD}; + uint8_t expected_powerful[7] = {0x14, 0x63, 0x00, 0x10, 0x10, 0x39, 0xC6}; + uint8_t expected_ecofanoff[7] = {0x14, 0x63, 0x00, 0x10, 0x10, 0x51, 0xAE}; + uint8_t expected_ecofanon[7] = {0x14, 0x63, 0x00, 0x10, 0x10, 0x50, 0xAF}; + uint8_t expected_outsidequietoff[16] = { + 0x14, 0x63, 0x00, 0x10, 0x10, 0xFE, 0x09, 0xC1, + 0x40, 0x01, 0x00, 0x00, 0xFE, 0xBF, 0x00, 0x41}; + uint8_t expected_outsidequieton[16] = { + 0x14, 0x63, 0x00, 0x10, 0x10, 0xFE, 0x09, 0xC1, + 0x40, 0x00, 0x00, 0x00, 0xFF, 0xBF, 0x00, 0x41}; + uint8_t expected_sterilization[16] = { + 0x14, 0x63, 0x00, 0x10, 0x10, 0xFE, 0x09, 0xC1, + 0x60, 0x03, 0x00, 0x00, 0xFC, 0x9F, 0x00, 0x41}; + + ac.on(); + ac.send(); + + ac.togglePowerful(); + ac.send(); + EXPECT_STATE_EQ(expected_powerful, ac.getRaw(), 7 * 8); + EXPECT_EQ(kFujitsuAc264StateLengthShort, ac.getStateLength()); + EXPECT_EQ("Command: Powerful", ac.toString()); + + ac.off(); + ac.send(); + EXPECT_STATE_EQ(expected_turnoff, ac.getRaw(), 7 * 8); + EXPECT_EQ(kFujitsuAc264StateLengthShort, ac.getStateLength()); + EXPECT_EQ("Command: Power Off", ac.toString()); + + ac.setEcoFan(true); + ac.send(); + EXPECT_STATE_EQ(expected_ecofanon, ac.getRaw(), 7 * 8); + EXPECT_EQ(kFujitsuAc264StateLengthShort, ac.getStateLength()); + EXPECT_EQ("Command: Eco Fan On", ac.toString()); + + ac.setEcoFan(false); + ac.send(); + EXPECT_STATE_EQ(expected_ecofanoff, ac.getRaw(), 7 * 8); + EXPECT_EQ(kFujitsuAc264StateLengthShort, ac.getStateLength()); + EXPECT_EQ("Command: Eco Fan Off", ac.toString()); + + ac.setOutsideQuiet(true); + ac.send(); + EXPECT_STATE_EQ(expected_outsidequieton, ac.getRaw(), 16 * 8); + EXPECT_EQ(kFujitsuAc264StateLengthMiddle, ac.getStateLength()); + EXPECT_EQ("Command: Outside Quiet On", ac.toString()); + + ac.setOutsideQuiet(false); + ac.send(); + EXPECT_STATE_EQ(expected_outsidequietoff, ac.getRaw(), 16 * 8); + EXPECT_EQ(kFujitsuAc264StateLengthMiddle, ac.getStateLength()); + EXPECT_EQ("Command: Outside Quiet Off", ac.toString()); + + ac.toggleSterilization(); + ac.send(); + EXPECT_STATE_EQ(expected_sterilization, ac.getRaw(), 16 * 8); + EXPECT_EQ(kFujitsuAc264StateLengthMiddle, ac.getStateLength()); + EXPECT_EQ("Command: Sterilization", ac.toString()); +}