// Copyright 2009 Ken Shirriff // Copyright 2017, 2019 David Conran /// @file /// @brief Support for Sharp protocols. /// @see http://www.sbprojects.net/knowledge/ir/sharp.htm /// @see http://lirc.sourceforge.net/remotes/sharp/GA538WJSA /// @see http://www.mwftr.com/ucF08/LEC14%20PIC%20IR.pdf /// @see http://www.hifi-remote.com/johnsfine/DecodeIR.html#Sharp /// @see GlobalCache's IR Control Tower data. /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/638 /// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/SharpHeatpumpIR.cpp #include "ir_Sharp.h" #include #include #ifndef ARDUINO #include #endif #include "IRrecv.h" #include "IRsend.h" #include "IRtext.h" #include "IRutils.h" // Constants // period time = 1/38000Hz = 26.316 microseconds. const uint16_t kSharpTick = 26; const uint16_t kSharpBitMarkTicks = 10; const uint16_t kSharpBitMark = kSharpBitMarkTicks * kSharpTick; const uint16_t kSharpOneSpaceTicks = 70; const uint16_t kSharpOneSpace = kSharpOneSpaceTicks * kSharpTick; const uint16_t kSharpZeroSpaceTicks = 30; const uint16_t kSharpZeroSpace = kSharpZeroSpaceTicks * kSharpTick; const uint16_t kSharpGapTicks = 1677; const uint16_t kSharpGap = kSharpGapTicks * kSharpTick; // Address(5) + Command(8) + Expansion(1) + Check(1) const uint64_t kSharpToggleMask = ((uint64_t)1 << (kSharpBits - kSharpAddressBits)) - 1; const uint64_t kSharpAddressMask = ((uint64_t)1 << kSharpAddressBits) - 1; const uint64_t kSharpCommandMask = ((uint64_t)1 << kSharpCommandBits) - 1; using irutils::addBoolToString; using irutils::addFanToString; using irutils::addIntToString; using irutils::addLabeledString; using irutils::addModeToString; using irutils::addModelToString; using irutils::addTempToString; using irutils::minsToString; // Also used by Denon protocol #if (SEND_SHARP || SEND_DENON) /// Send a (raw) Sharp message /// @note Status: STABLE / Working fine. /// @param[in] data The message to be sent. /// @param[in] nbits The number of bits of message to be sent. /// @param[in] repeat The number of times the command is to be repeated. /// @note his procedure handles the inversion of bits required per protocol. /// The protocol spec says to send the LSB first, but legacy code & usage /// has us sending the MSB first. Grrrr. Normal invocation of encodeSharp() /// handles this for you, assuming you are using the correct/standard values. /// e.g. sendSharpRaw(encodeSharp(address, command)); void IRsend::sendSharpRaw(const uint64_t data, const uint16_t nbits, const uint16_t repeat) { uint64_t tempdata = data; for (uint16_t i = 0; i <= repeat; i++) { // Protocol demands that the data be sent twice; once normally, // then with all but the address bits inverted. // Note: Previously this used to be performed 3 times (normal, inverted, // normal), however all data points to that being incorrect. for (uint8_t n = 0; n < 2; n++) { sendGeneric(0, 0, // No Header kSharpBitMark, kSharpOneSpace, kSharpBitMark, kSharpZeroSpace, kSharpBitMark, kSharpGap, tempdata, nbits, 38, true, 0, // Repeats are handled already. 33); // Invert the data per protocol. This is always called twice, so it's // returned to original upon exiting the inner loop. tempdata ^= kSharpToggleMask; } } } /// Encode a (raw) Sharp message from it's components. /// Status: STABLE / Works okay. /// @param[in] address The value of the address to be sent. /// @param[in] command The value of the address to be sent. (8 bits) /// @param[in] expansion The value of the expansion bit to use. /// (0 or 1, typically 1) /// @param[in] check The value of the check bit to use. (0 or 1, typically 0) /// @param[in] MSBfirst Flag indicating MSB first or LSB first order. /// @return A uint32_t containing the raw Sharp message for `sendSharpRaw()`. /// @note Assumes the standard Sharp bit sizes. /// Historically sendSharp() sends address & command in /// MSB first order. This is actually incorrect. It should be sent in LSB /// order. The behaviour of sendSharp() hasn't been changed to maintain /// backward compatibility. uint32_t IRsend::encodeSharp(const uint16_t address, const uint16_t command, const uint16_t expansion, const uint16_t check, const bool MSBfirst) { // Mask any unexpected bits. uint16_t tempaddress = GETBITS16(address, 0, kSharpAddressBits); uint16_t tempcommand = GETBITS16(command, 0, kSharpCommandBits); uint16_t tempexpansion = GETBITS16(expansion, 0, 1); uint16_t tempcheck = GETBITS16(check, 0, 1); if (!MSBfirst) { // Correct bit order if needed. tempaddress = reverseBits(tempaddress, kSharpAddressBits); tempcommand = reverseBits(tempcommand, kSharpCommandBits); } // Concatenate all the bits. return (tempaddress << (kSharpCommandBits + 2)) | (tempcommand << 2) | (tempexpansion << 1) | tempcheck; } /// Send a Sharp message /// Status: DEPRECATED / Previously working fine. /// @deprecated Only use this if you are using legacy from the original /// Arduino-IRremote library. 99% of the time, you will want to use /// `sendSharpRaw()` instead /// @param[in] address Address value to be sent. /// @param[in] command Command value to be sent. /// @param[in] nbits The number of bits of message to be sent. /// @param[in] repeat The number of times the command is to be repeated. /// @note This procedure has a non-standard invocation style compared to similar /// sendProtocol() routines. This is due to legacy, compatibility, & historic /// reasons. Normally the calling syntax version is like sendSharpRaw(). /// This procedure transmits the address & command in MSB first order, which is /// incorrect. This behaviour is left as-is to maintain backward /// compatibility with legacy code. /// In short, you should use sendSharpRaw(), encodeSharp(), and the correct /// values of address & command instead of using this, & the wrong values. void IRsend::sendSharp(const uint16_t address, uint16_t const command, const uint16_t nbits, const uint16_t repeat) { sendSharpRaw(encodeSharp(address, command, 1, 0, true), nbits, repeat); } #endif // (SEND_SHARP || SEND_DENON) // Used by decodeDenon too. #if (DECODE_SHARP || DECODE_DENON) /// Decode the supplied Sharp message. /// Status: STABLE / Working fine. /// @param[in,out] results Ptr to the data to decode & where to store the result /// @param[in] offset The starting index to use when attempting to decode the /// raw data. Typically/Defaults to kStartOffset. /// @param[in] nbits The number of data bits to expect. /// @param[in] strict Flag indicating if we should perform strict matching. /// @param[in] expansion Should we expect the expansion bit to be set. /// Default is true. /// @return True if it can decode it, false if it can't. /// @note This procedure returns a value suitable for use in `sendSharpRaw()`. /// @todo Need to ensure capture of the inverted message as it can /// be missed due to the interrupt timeout used to detect an end of message. /// Several compliance checks are disabled until that is resolved. bool IRrecv::decodeSharp(decode_results *results, uint16_t offset, const uint16_t nbits, const bool strict, const bool expansion) { if (results->rawlen <= 2 * nbits + kFooter - 1 + offset) return false; // Not enough entries to be a Sharp message. // Compliance if (strict) { if (nbits != kSharpBits) return false; // Request is out of spec. // DISABLED - See TODO #ifdef UNIT_TEST // An in spec message has the data sent normally, then inverted. So we // expect twice as many entries than to just get the results. if (results->rawlen <= (2 * (2 * nbits + kFooter)) - 1 + offset) return false; #endif } uint64_t data = 0; // Match Data + Footer uint16_t used; used = matchGeneric(results->rawbuf + offset, &data, results->rawlen - offset, nbits, 0, 0, // No Header kSharpBitMark, kSharpOneSpace, kSharpBitMark, kSharpZeroSpace, kSharpBitMark, kSharpGap, true, 35); if (!used) return false; offset += used; // Compliance if (strict) { // Check the state of the expansion bit is what we expect. if ((data & 0b10) >> 1 != expansion) return false; // The check bit should be cleared in a normal message. if (data & 0b1) return false; // DISABLED - See TODO #ifdef UNIT_TEST // Grab the second copy of the data (i.e. inverted) uint64_t second_data = 0; // Match Data + Footer if (!matchGeneric(results->rawbuf + offset, &second_data, results->rawlen - offset, nbits, 0, 0, kSharpBitMark, kSharpOneSpace, kSharpBitMark, kSharpZeroSpace, kSharpBitMark, kSharpGap, true, 35)) return false; // Check that second_data has been inverted correctly. if (data != (second_data ^ kSharpToggleMask)) return false; #endif // UNIT_TEST } // Success results->decode_type = SHARP; results->bits = nbits; results->value = data; // Address & command are actually transmitted in LSB first order. results->address = reverseBits(data, nbits) & kSharpAddressMask; results->command = reverseBits((data >> 2) & kSharpCommandMask, kSharpCommandBits); return true; } #endif // (DECODE_SHARP || DECODE_DENON) #if SEND_SHARP_AC /// Send a Sharp A/C message. /// Status: Alpha / Untested. /// @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. /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/638 /// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/SharpHeatpumpIR.cpp void IRsend::sendSharpAc(const unsigned char data[], const uint16_t nbytes, const uint16_t repeat) { if (nbytes < kSharpAcStateLength) return; // Not enough bytes to send a proper message. sendGeneric(kSharpAcHdrMark, kSharpAcHdrSpace, kSharpAcBitMark, kSharpAcOneSpace, kSharpAcBitMark, kSharpAcZeroSpace, kSharpAcBitMark, kSharpAcGap, data, nbytes, 38000, false, repeat, 50); } #endif // SEND_SHARP_AC /// 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? IRSharpAc::IRSharpAc(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 IRSharpAc::begin(void) { _irsend.begin(); } #if SEND_SHARP_AC /// Send the current internal state as an IR message. /// @param[in] repeat Nr. of times the message will be repeated. void IRSharpAc::send(const uint16_t repeat) { _irsend.sendSharpAc(getRaw(), kSharpAcStateLength, repeat); } #endif // SEND_SHARP_AC /// Calculate the checksum for a given state. /// @param[in] state The array to calc the checksum of. /// @param[in] length The length/size of the array. /// @return The calculated 4-bit checksum value. uint8_t IRSharpAc::calcChecksum(uint8_t state[], const uint16_t length) { uint8_t xorsum = xorBytes(state, length - 1); xorsum ^= GETBITS8(state[length - 1], kLowNibble, kNibbleSize); xorsum ^= GETBITS8(xorsum, kHighNibble, kNibbleSize); return GETBITS8(xorsum, kLowNibble, kNibbleSize); } /// Verify the checksum is valid for a given state. /// @param[in] state The array to verify the checksum of. /// @param[in] length The length/size of the array. /// @return true, if the state has a valid checksum. Otherwise, false. bool IRSharpAc::validChecksum(uint8_t state[], const uint16_t length) { return GETBITS8(state[length - 1], kHighNibble, kNibbleSize) == IRSharpAc::calcChecksum(state, length); } /// Calculate and set the checksum values for the internal state. void IRSharpAc::checksum(void) { _.Sum = calcChecksum(_.raw); } /// Reset the state of the remote to a known good state/sequence. void IRSharpAc::stateReset(void) { static const uint8_t reset[kSharpAcStateLength] = { 0xAA, 0x5A, 0xCF, 0x10, 0x00, 0x01, 0x00, 0x00, 0x08, 0x80, 0x00, 0xE0, 0x01}; std::memcpy(_.raw, reset, kSharpAcStateLength); _temp = getTemp(); _mode = _.Mode; _fan = _.Fan; _model = getModel(true); } /// 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 *IRSharpAc::getRaw(void) { checksum(); // Ensure correct settings before sending. return _.raw; } /// Set the internal state from a valid code for this protocol. /// @param[in] new_code A valid code for this protocol. /// @param[in] length The length/size of the new_code array. void IRSharpAc::setRaw(const uint8_t new_code[], const uint16_t length) { std::memcpy(_.raw, new_code, std::min(length, kSharpAcStateLength)); _model = getModel(true); } /// Set the model of the A/C to emulate. /// @param[in] model The enum of the appropriate model. void IRSharpAc::setModel(const sharp_ac_remote_model_t model) { switch (model) { case sharp_ac_remote_model_t::A705: case sharp_ac_remote_model_t::A903: _model = model; _.Model = true; break; default: _model = sharp_ac_remote_model_t::A907; _.Model = false; } _.Model2 = (_model != sharp_ac_remote_model_t::A907); // Redo the operating mode as some models don't support all modes. setMode(_.Mode); } /// Get/Detect the model of the A/C. /// @param[in] raw Try to determine the model from the raw code only. /// @return The enum of the compatible model. sharp_ac_remote_model_t IRSharpAc::getModel(const bool raw) const { if (raw) { if (_.Model2) { if (_.Model) return sharp_ac_remote_model_t::A705; else return sharp_ac_remote_model_t::A903; } else { return sharp_ac_remote_model_t::A907; } } return _model; } /// Set the value of the Power Special setting without any checks. /// @param[in] value The value to set Power Special to. inline void IRSharpAc::setPowerSpecial(const uint8_t value) { _.PowerSpecial = value; } /// Get the value of the Power Special setting. /// @return The setting's value. uint8_t IRSharpAc::getPowerSpecial(void) const { return _.PowerSpecial; } /// Clear the "special"/non-normal bits in the power section. /// e.g. for normal/common command modes. void IRSharpAc::clearPowerSpecial(void) { setPowerSpecial(_.PowerSpecial & kSharpAcPowerOn); } /// Is one of the special power states in use? /// @return true, it is. false, it isn't. bool IRSharpAc::isPowerSpecial(void) const { switch (_.PowerSpecial) { case kSharpAcPowerSetSpecialOff: case kSharpAcPowerSetSpecialOn: case kSharpAcPowerTimerSetting: return true; default: return false; } } /// Set the requested power state of the A/C to on. void IRSharpAc::on(void) { setPower(true); } /// Set the requested power state of the A/C to off. void IRSharpAc::off(void) { setPower(false); } /// Change the power setting, including the previous power state. /// @param[in] on true, the setting is on. false, the setting is off. /// @param[in] prev_on true, the setting is on. false, the setting is off. void IRSharpAc::setPower(const bool on, const bool prev_on) { setPowerSpecial(on ? (prev_on ? kSharpAcPowerOn : kSharpAcPowerOnFromOff) : kSharpAcPowerOff); // Power operations are incompatible with clean mode. if (_.Clean) setClean(false); _.Special = kSharpAcSpecialPower; } /// Get the value of the current power setting. /// @return true, the setting is on. false, the setting is off. bool IRSharpAc::getPower(void) const { switch (_.PowerSpecial) { case kSharpAcPowerUnknown: case kSharpAcPowerOff: return false; default: return true; // Everything else is "probably" on. } } /// Set the value of the Special (button/command?) setting. /// @param[in] mode The value to set Special to. void IRSharpAc::setSpecial(const uint8_t mode) { switch (mode) { case kSharpAcSpecialPower: case kSharpAcSpecialTurbo: case kSharpAcSpecialTempEcono: case kSharpAcSpecialFan: case kSharpAcSpecialSwing: case kSharpAcSpecialTimer: case kSharpAcSpecialTimerHalfHour: _.Special = mode; break; default: _.Special = kSharpAcSpecialPower; } } /// Get the value of the Special (button/command?) setting. /// @return The setting's value. uint8_t IRSharpAc::getSpecial(void) const { return _.Special; } /// Set the temperature. /// @param[in] temp The temperature in degrees celsius. /// @param[in] save Do we save this setting as a user set one? void IRSharpAc::setTemp(const uint8_t temp, const bool save) { switch (_.Mode) { // Auto & Dry don't allow temp changes and have a special temp. case kSharpAcAuto: case kSharpAcDry: _.raw[kSharpAcByteTemp] = 0; return; default: switch (getModel()) { case sharp_ac_remote_model_t::A705: _.raw[kSharpAcByteTemp] = 0xD0; break; default: _.raw[kSharpAcByteTemp] = 0xC0; } } uint8_t degrees = std::max(temp, kSharpAcMinTemp); degrees = std::min(degrees, kSharpAcMaxTemp); if (save) _temp = degrees; _.Temp = degrees - kSharpAcMinTemp; _.Special = kSharpAcSpecialTempEcono; clearPowerSpecial(); } /// Get the current temperature setting. /// @return The current setting for temp. in degrees celsius. uint8_t IRSharpAc::getTemp(void) const { return _.Temp + kSharpAcMinTemp; } /// Get the operating mode setting of the A/C. /// @return The current operating mode setting. uint8_t IRSharpAc::getMode(void) const { return _.Mode; } /// Set the operating mode of the A/C. /// @param[in] mode The desired operating mode. /// @param[in] save Do we save this setting as a user set one? void IRSharpAc::setMode(const uint8_t mode, const bool save) { uint8_t realMode = mode; if (mode == kSharpAcHeat) { switch (getModel()) { case sharp_ac_remote_model_t::A705: case sharp_ac_remote_model_t::A903: // These models have no heat mode, use Fan mode instead. realMode = kSharpAcFan; break; default: break; } } switch (realMode) { case kSharpAcAuto: // Also kSharpAcFan case kSharpAcDry: // When Dry or Auto, Fan always 2(Auto) setFan(kSharpAcFanAuto, false); // FALLTHRU case kSharpAcCool: case kSharpAcHeat: _.Mode = realMode; break; default: setFan(kSharpAcFanAuto, false); _.Mode = kSharpAcAuto; } // Dry/Auto have no temp setting. This step will enforce it. setTemp(_temp, false); // Save the mode in case we need to revert to it. eg. Clean if (save) _mode = _.Mode; _.Special = kSharpAcSpecialPower; clearPowerSpecial(); } /// Set the speed of the fan. /// @param[in] speed The desired setting. /// @param[in] save Do we save this setting as a user set one? void IRSharpAc::setFan(const uint8_t speed, const bool save) { switch (speed) { case kSharpAcFanAuto: case kSharpAcFanMin: case kSharpAcFanMed: case kSharpAcFanHigh: case kSharpAcFanMax: _.Fan = speed; if (save) _fan = speed; break; default: _.Fan = kSharpAcFanAuto; _fan = kSharpAcFanAuto; } _.Special = kSharpAcSpecialFan; clearPowerSpecial(); } /// Get the current fan speed setting. /// @return The current fan speed/mode. uint8_t IRSharpAc::getFan(void) const { return _.Fan; } /// Get the Turbo setting of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRSharpAc::getTurbo(void) const { return (_.PowerSpecial == kSharpAcPowerSetSpecialOn) && (_.Special == kSharpAcSpecialTurbo); } /// Set the Turbo setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. /// @note If you use this method, you will need to send it before making /// other changes to the settings, as they may overwrite some of the bits /// used by this setting. void IRSharpAc::setTurbo(const bool on) { if (on) setFan(kSharpAcFanMax); setPowerSpecial(on ? kSharpAcPowerSetSpecialOn : kSharpAcPowerSetSpecialOff); _.Special = kSharpAcSpecialTurbo; } /// Get the (vertical) Swing Toggle setting of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRSharpAc::getSwingToggle(void) const { return _.Swing == kSharpAcSwingToggle; } /// Set the (vertical) Swing Toggle setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. void IRSharpAc::setSwingToggle(const bool on) { _.Swing = (on ? kSharpAcSwingToggle : kSharpAcSwingNoToggle); if (on) _.Special = kSharpAcSpecialSwing; } /// Get the Ion (Filter) setting of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRSharpAc::getIon(void) const { return _.Ion; } /// Set the Ion (Filter) setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. void IRSharpAc::setIon(const bool on) { _.Ion = on; clearPowerSpecial(); if (on) _.Special = kSharpAcSpecialSwing; } /// Get the Economical mode toggle setting of the A/C. /// @return true, the setting is on. false, the setting is off. /// @note Shares the same location as the Light setting on A705. bool IRSharpAc::_getEconoToggle(void) const { return (_.PowerSpecial == kSharpAcPowerSetSpecialOn) && (_.Special == kSharpAcSpecialTempEcono); } /// Set the Economical mode toggle setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. /// @warning Probably incompatible with `setTurbo()` /// @note Shares the same location as the Light setting on A705. void IRSharpAc::_setEconoToggle(const bool on) { if (on) _.Special = kSharpAcSpecialTempEcono; setPowerSpecial(on ? kSharpAcPowerSetSpecialOn : kSharpAcPowerSetSpecialOff); } /// Set the Economical mode toggle setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. /// @warning Probably incompatible with `setTurbo()` /// @note Available on the A907 models. void IRSharpAc::setEconoToggle(const bool on) { if (_model == sharp_ac_remote_model_t::A907) _setEconoToggle(on); } /// Get the Economical mode toggle setting of the A/C. /// @return true, the setting is on. false, the setting is off. /// @note Available on the A907 models. bool IRSharpAc::getEconoToggle(void) const { return _model == sharp_ac_remote_model_t::A907 && _getEconoToggle(); } /// Set the Light mode toggle setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. /// @warning Probably incompatible with `setTurbo()` /// @note Not available on the A907 model. void IRSharpAc::setLightToggle(const bool on) { if (_model != sharp_ac_remote_model_t::A907) _setEconoToggle(on); } /// Get the Light toggle setting of the A/C. /// @return true, the setting is on. false, the setting is off. /// @note Not available on the A907 model. bool IRSharpAc::getLightToggle(void) const { return _model != sharp_ac_remote_model_t::A907 && _getEconoToggle(); } /// Get how long the timer is set for, in minutes. /// @return The time in nr of minutes. uint16_t IRSharpAc::getTimerTime(void) const { return _.TimerHours * kSharpAcTimerIncrement * 2 + ((_.Special == kSharpAcSpecialTimerHalfHour) ? kSharpAcTimerIncrement : 0); } /// Is the Timer enabled? /// @return true, the setting is on. false, the setting is off. bool IRSharpAc::getTimerEnabled(void) const { return _.TimerEnabled; } /// Get the current timer type. /// @return true, It's an "On" timer. false, It's an "Off" timer. bool IRSharpAc::getTimerType(void) const { return _.TimerType; } /// Set or cancel the timer function. /// @param[in] enable Is the timer to be enabled (true) or canceled(false)? /// @param[in] timer_type An On (true) or an Off (false). Ignored if canceled. /// @param[in] mins Nr. of minutes the timer is to be set to. /// @note Rounds down to 30 min increments. (max: 720 mins (12h), 0 is Off) void IRSharpAc::setTimer(bool enable, bool timer_type, uint16_t mins) { uint8_t half_hours = std::min(mins / kSharpAcTimerIncrement, kSharpAcTimerHoursMax * 2); if (half_hours == 0) enable = false; if (!enable) { half_hours = 0; timer_type = kSharpAcOffTimerType; } _.TimerEnabled = enable; _.TimerType = timer_type; _.TimerHours = half_hours / 2; // Handle non-round hours. _.Special = (half_hours % 2) ? kSharpAcSpecialTimerHalfHour : kSharpAcSpecialTimer; setPowerSpecial(kSharpAcPowerTimerSetting); } /// Get the Clean setting of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRSharpAc::getClean(void) const { return _.Clean; } /// Set the Economical mode toggle setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. /// @note Officially A/C unit needs to be "Off" before clean mode can be entered void IRSharpAc::setClean(const bool on) { // Clean mode appears to be just default dry mode, with an extra bit set. if (on) { setMode(kSharpAcDry, false); setPower(true, false); } else { // Restore the previous operation mode & fan speed. setMode(_mode, false); setFan(_fan, false); } _.Clean = on; clearPowerSpecial(); } /// 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 IRSharpAc::convertMode(const stdAc::opmode_t mode) { switch (mode) { case stdAc::opmode_t::kCool: return kSharpAcCool; case stdAc::opmode_t::kHeat: return kSharpAcHeat; case stdAc::opmode_t::kDry: return kSharpAcDry; // No Fan mode. default: return kSharpAcAuto; } } /// Convert a stdAc::fanspeed_t enum into it's native speed. /// @param[in] speed The enum to be converted. /// @param[in] model The enum of the appropriate model. /// @return The native equivalent of the enum. uint8_t IRSharpAc::convertFan(const stdAc::fanspeed_t speed, const sharp_ac_remote_model_t model) { switch (model) { case sharp_ac_remote_model_t::A705: case sharp_ac_remote_model_t::A903: switch (speed) { case stdAc::fanspeed_t::kLow: return kSharpAcFanA705Low; case stdAc::fanspeed_t::kMedium: return kSharpAcFanA705Med; default: {}; // Fall thru to the next/default clause if not the above // special cases. } // FALL THRU default: switch (speed) { case stdAc::fanspeed_t::kMin: case stdAc::fanspeed_t::kLow: return kSharpAcFanMin; case stdAc::fanspeed_t::kMedium: return kSharpAcFanMed; case stdAc::fanspeed_t::kHigh: return kSharpAcFanHigh; case stdAc::fanspeed_t::kMax: return kSharpAcFanMax; default: return kSharpAcFanAuto; } } } /// 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 IRSharpAc::toCommonMode(const uint8_t mode) const { switch (mode) { case kSharpAcCool: return stdAc::opmode_t::kCool; case kSharpAcHeat: return stdAc::opmode_t::kHeat; case kSharpAcDry: return stdAc::opmode_t::kDry; case kSharpAcAuto: // Also kSharpAcFan switch (getModel()) { case sharp_ac_remote_model_t::A705: return stdAc::opmode_t::kFan; default: return stdAc::opmode_t::kAuto; } break; 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 IRSharpAc::toCommonFanSpeed(const uint8_t speed) const { switch (getModel()) { case sharp_ac_remote_model_t::A705: case sharp_ac_remote_model_t::A903: switch (speed) { case kSharpAcFanA705Low: return stdAc::fanspeed_t::kLow; case kSharpAcFanA705Med: return stdAc::fanspeed_t::kMedium; } // FALL-THRU default: switch (speed) { case kSharpAcFanMax: return stdAc::fanspeed_t::kMax; case kSharpAcFanHigh: return stdAc::fanspeed_t::kHigh; case kSharpAcFanMed: return stdAc::fanspeed_t::kMedium; case kSharpAcFanMin: return stdAc::fanspeed_t::kMin; default: return stdAc::fanspeed_t::kAuto; } } } /// Convert the current internal state into its stdAc::state_t equivalent. /// @return The stdAc equivalent of the native settings. stdAc::state_t IRSharpAc::toCommon(void) const { stdAc::state_t result; result.protocol = decode_type_t::SHARP_AC; result.model = getModel(); result.power = getPower(); result.mode = toCommonMode(_.Mode); result.celsius = true; result.degrees = getTemp(); result.fanspeed = toCommonFanSpeed(_.Fan); result.turbo = getTurbo(); result.swingv = getSwingToggle() ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; result.filter = _.Ion; result.econo = getEconoToggle(); result.light = getLightToggle(); result.clean = _.Clean; // Not supported. result.swingh = stdAc::swingh_t::kOff; result.quiet = false; result.beep = false; result.sleep = -1; result.clock = -1; return result; } /// Convert the current internal state into a human readable string. /// @return A human readable string. String IRSharpAc::toString(void) const { String result = ""; const sharp_ac_remote_model_t model = getModel(); result.reserve(160); // Reserve some heap for the string to reduce fragging. result += addModelToString(decode_type_t::SHARP_AC, getModel(), false); result += addLabeledString(isPowerSpecial() ? "-" : (getPower() ? kOnStr : kOffStr), kPowerStr); result += addModeToString( _.Mode, // Make the value invalid if the model doesn't support an Auto mode. (model == sharp_ac_remote_model_t::A907) ? kSharpAcAuto : 255, kSharpAcCool, kSharpAcHeat, kSharpAcDry, kSharpAcFan); result += addTempToString(getTemp()); switch (model) { case sharp_ac_remote_model_t::A705: case sharp_ac_remote_model_t::A903: result += addFanToString(_.Fan, kSharpAcFanMax, kSharpAcFanA705Low, kSharpAcFanAuto, kSharpAcFanAuto, kSharpAcFanA705Med); break; default: result += addFanToString(_.Fan, kSharpAcFanMax, kSharpAcFanMin, kSharpAcFanAuto, kSharpAcFanAuto, kSharpAcFanMed); } result += addBoolToString(getTurbo(), kTurboStr); result += addBoolToString(getSwingToggle(), kSwingVToggleStr); result += addBoolToString(_.Ion, kIonStr); switch (model) { case sharp_ac_remote_model_t::A705: case sharp_ac_remote_model_t::A903: result += addLabeledString(getLightToggle() ? kToggleStr : "-", kLightStr); break; default: result += addLabeledString(getEconoToggle() ? kToggleStr : "-", kEconoStr); } result += addBoolToString(_.Clean, kCleanStr); if (_.TimerEnabled) result += addLabeledString(minsToString(getTimerTime()), _.TimerType ? kOnTimerStr : kOffTimerStr); return result; } #if DECODE_SHARP_AC /// Decode the supplied Sharp A/C message. /// Status: STABLE / Known working. /// @param[in,out] results Ptr to the data to decode & where to store the result /// @param[in] offset The starting index to use when attempting to decode the /// raw data. Typically/Defaults to kStartOffset. /// @param[in] nbits The number of data bits to expect. /// @param[in] strict Flag indicating if we should perform strict matching. /// @return True if it can decode it, false if it can't. /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/638 /// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/SharpHeatpumpIR.cpp bool IRrecv::decodeSharpAc(decode_results *results, uint16_t offset, const uint16_t nbits, const bool strict) { // Compliance if (strict && nbits != kSharpAcBits) return false; // Match Header + Data + Footer uint16_t used; used = matchGeneric(results->rawbuf + offset, results->state, results->rawlen - offset, nbits, kSharpAcHdrMark, kSharpAcHdrSpace, kSharpAcBitMark, kSharpAcOneSpace, kSharpAcBitMark, kSharpAcZeroSpace, kSharpAcBitMark, kSharpAcGap, true, _tolerance, kMarkExcess, false); if (used == 0) return false; offset += used; // Compliance if (strict) { if (!IRSharpAc::validChecksum(results->state)) return false; } // Success results->decode_type = SHARP_AC; results->bits = nbits; // No need to record the state as we stored it as we decoded it. // As we use result->state, we don't record value, address, or command as it // is a union data type. return true; } #endif // DECODE_SHARP_AC