// Copyright 2009 Ken Shirriff // Copyright 2017-2021 David Conran // Copyright 2019 Mark Kuchel // Copyright 2018 Denes Varga /// @file /// @brief Support for Mitsubishi protocols. /// Mitsubishi (TV) decoding added from https://github.com/z3t0/Arduino-IRremote /// Mitsubishi (TV) sending & Mitsubishi A/C support added by David Conran /// @see GlobalCache's Control Tower's Mitsubishi TV data. /// @see https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Mitsubishi.cpp /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/441 /// @see https://github.com/r45635/HVAC-IR-Control/blob/master/HVAC_ESP8266/HVAC_ESP8266.ino#L84 /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/619 /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/888 /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/947 /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1398 /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1399 /// @see https://github.com/kuchel77 #include "ir_Mitsubishi.h" #include #include #ifndef ARDUINO #include #endif #include "IRrecv.h" #include "IRsend.h" #include "IRtext.h" #include "IRutils.h" #include "ir_Tcl.h" // Constants // Mitsubishi TV // period time is 1/33000Hz = 30.303 uSeconds (T) const uint16_t kMitsubishiTick = 30; const uint16_t kMitsubishiBitMarkTicks = 10; const uint16_t kMitsubishiBitMark = kMitsubishiBitMarkTicks * kMitsubishiTick; const uint16_t kMitsubishiOneSpaceTicks = 70; const uint16_t kMitsubishiOneSpace = kMitsubishiOneSpaceTicks * kMitsubishiTick; const uint16_t kMitsubishiZeroSpaceTicks = 30; const uint16_t kMitsubishiZeroSpace = kMitsubishiZeroSpaceTicks * kMitsubishiTick; const uint16_t kMitsubishiMinCommandLengthTicks = 1786; const uint16_t kMitsubishiMinCommandLength = kMitsubishiMinCommandLengthTicks * kMitsubishiTick; const uint16_t kMitsubishiMinGapTicks = 936; const uint16_t kMitsubishiMinGap = kMitsubishiMinGapTicks * kMitsubishiTick; // Mitsubishi Projector (HC3000) const uint16_t kMitsubishi2HdrMark = 8400; const uint16_t kMitsubishi2HdrSpace = kMitsubishi2HdrMark / 2; const uint16_t kMitsubishi2BitMark = 560; const uint16_t kMitsubishi2ZeroSpace = 520; const uint16_t kMitsubishi2OneSpace = kMitsubishi2ZeroSpace * 3; const uint16_t kMitsubishi2MinGap = 28500; // Mitsubishi A/C const uint16_t kMitsubishiAcHdrMark = 3400; const uint16_t kMitsubishiAcHdrSpace = 1750; const uint16_t kMitsubishiAcBitMark = 450; const uint16_t kMitsubishiAcOneSpace = 1300; const uint16_t kMitsubishiAcZeroSpace = 420; const uint16_t kMitsubishiAcRptMark = 440; const uint16_t kMitsubishiAcRptSpace = 17100; const uint8_t kMitsubishiAcExtraTolerance = 5; // Mitsubishi 136 bit A/C const uint16_t kMitsubishi136HdrMark = 3324; const uint16_t kMitsubishi136HdrSpace = 1474; const uint16_t kMitsubishi136BitMark = 467; const uint16_t kMitsubishi136OneSpace = 1137; const uint16_t kMitsubishi136ZeroSpace = 351; const uint32_t kMitsubishi136Gap = kDefaultMessageGap; // Mitsubishi 112 bit A/C const uint16_t kMitsubishi112HdrMark = 3450; const uint16_t kMitsubishi112HdrSpace = 1696; const uint16_t kMitsubishi112BitMark = 450; const uint16_t kMitsubishi112OneSpace = 1250; const uint16_t kMitsubishi112ZeroSpace = 385; const uint32_t kMitsubishi112Gap = kDefaultMessageGap; // Total tolerance percentage to use for matching the header mark. const uint8_t kMitsubishi112HdrMarkTolerance = 5; using irutils::addBoolToString; using irutils::addFanToString; using irutils::addIntToString; using irutils::addLabeledString; using irutils::addModeToString; using irutils::addSwingHToString; using irutils::addSwingVToString; using irutils::addTempToString; using irutils::addTempFloatToString; using irutils::minsToString; #if SEND_MITSUBISHI /// Send the supplied Mitsubishi 16-bit message. /// Status: STABLE / Working. /// @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 This protocol appears to have no header. /// @see https://github.com/marcosamarinho/IRremoteESP8266/blob/master/ir_Mitsubishi.cpp /// @see GlobalCache's Control Tower's Mitsubishi TV data. void IRsend::sendMitsubishi(uint64_t data, uint16_t nbits, uint16_t repeat) { sendGeneric(0, 0, // No Header kMitsubishiBitMark, kMitsubishiOneSpace, kMitsubishiBitMark, kMitsubishiZeroSpace, kMitsubishiBitMark, kMitsubishiMinGap, kMitsubishiMinCommandLength, data, nbits, 33, true, repeat, 50); } #endif // SEND_MITSUBISHI #if DECODE_MITSUBISHI /// Decode the supplied Mitsubishi 16-bit message. /// Status: STABLE / Working. /// @param[in,out] results Ptr to the data to decode & where to store the result /// @param[in] offset The starting index to use when attempting to decode the /// raw data. Typically/Defaults to kStartOffset. /// @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. /// @note This protocol appears to have no header. /// @see GlobalCache's Control Tower's Mitsubishi TV data. bool IRrecv::decodeMitsubishi(decode_results *results, uint16_t offset, const uint16_t nbits, const bool strict) { if (strict && nbits != kMitsubishiBits) return false; // Request is out of spec. uint64_t data = 0; // Match Data + Footer if (!matchGeneric(results->rawbuf + offset, &data, results->rawlen - offset, nbits, 0, 0, // No header kMitsubishiBitMark, kMitsubishiOneSpace, kMitsubishiBitMark, kMitsubishiZeroSpace, kMitsubishiBitMark, kMitsubishiMinGap, true, 30)) return false; // Success results->decode_type = MITSUBISHI; results->bits = nbits; results->value = data; results->address = 0; results->command = 0; return true; } #endif // DECODE_MITSUBISHI #if SEND_MITSUBISHI2 /// Send a supplied second variant Mitsubishi 16-bit message. /// Status: BETA / Probably works. /// @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 Based on a Mitsubishi HC3000 projector's remote. /// This protocol appears to have a mandatory in-protocol repeat. /// That is in *addition* to the entire message needing to be sent twice /// for the device to accept the command. That is separate from the repeat. /// i.e. Allegedly, the real remote requires the "Off" button pressed twice. /// You will need to add a suitable gap yourself. /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/441 void IRsend::sendMitsubishi2(uint64_t data, uint16_t nbits, uint16_t repeat) { for (uint16_t i = 0; i <= repeat; i++) { // First half of the data. sendGeneric(kMitsubishi2HdrMark, kMitsubishi2HdrSpace, kMitsubishi2BitMark, kMitsubishi2OneSpace, kMitsubishi2BitMark, kMitsubishi2ZeroSpace, kMitsubishi2BitMark, kMitsubishi2HdrSpace, data >> (nbits / 2), nbits / 2, 33, true, 0, 50); // Second half of the data. sendGeneric(0, 0, // No header for the second data block kMitsubishi2BitMark, kMitsubishi2OneSpace, kMitsubishi2BitMark, kMitsubishi2ZeroSpace, kMitsubishi2BitMark, kMitsubishi2MinGap, data & ((1 << (nbits / 2)) - 1), nbits / 2, 33, true, 0, 50); } } #endif // SEND_MITSUBISHI2 #if DECODE_MITSUBISHI2 /// Decode the supplied second variation of a Mitsubishi 16-bit message. /// Status: STABLE / Working. /// @param[in,out] results Ptr to the data to decode & where to store the result /// @param[in] offset The starting index to use when attempting to decode the /// raw data. Typically/Defaults to kStartOffset. /// @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/441 bool IRrecv::decodeMitsubishi2(decode_results *results, uint16_t offset, const uint16_t nbits, const bool strict) { if (results->rawlen <= 2 * nbits + kHeader + (kFooter * 2) - 1 + offset) return false; // Shorter than shortest possibly expected. if (strict && nbits != kMitsubishiBits) return false; // Request is out of spec. results->value = 0; // Header if (!matchMark(results->rawbuf[offset++], kMitsubishi2HdrMark)) return false; if (!matchSpace(results->rawbuf[offset++], kMitsubishi2HdrSpace)) return false; for (uint8_t i = 0; i < 2; i++) { // Match Data + Footer uint16_t used; uint64_t data = 0; used = matchGeneric(results->rawbuf + offset, &data, results->rawlen - offset, nbits / 2, 0, 0, // No header kMitsubishi2BitMark, kMitsubishi2OneSpace, kMitsubishi2BitMark, kMitsubishi2ZeroSpace, kMitsubishi2BitMark, kMitsubishi2HdrSpace, i % 2); if (!used) return false; offset += used; results->value <<= (nbits / 2); results->value |= data; } // Success results->decode_type = MITSUBISHI2; results->bits = nbits; results->address = GETBITS64(results->value, nbits / 2, nbits / 2); results->command = GETBITS64(results->value, 0, nbits / 2); return true; } #endif // DECODE_MITSUBISHI2 #if SEND_MITSUBISHI_AC /// Send a Mitsubishi 144-bit A/C formatted message. (MITSUBISHI_AC) /// Status: STABLE / Working. /// @param[in] data The message to be sent. /// @param[in] nbytes The number of bytes of message to be sent. /// @param[in] repeat The number of times the command is to be repeated. void IRsend::sendMitsubishiAC(const unsigned char data[], const uint16_t nbytes, const uint16_t repeat) { if (nbytes < kMitsubishiACStateLength) return; // Not enough bytes to send a proper message. sendGeneric(kMitsubishiAcHdrMark, kMitsubishiAcHdrSpace, kMitsubishiAcBitMark, kMitsubishiAcOneSpace, kMitsubishiAcBitMark, kMitsubishiAcZeroSpace, kMitsubishiAcRptMark, kMitsubishiAcRptSpace, data, nbytes, 38, false, repeat, 50); } #endif // SEND_MITSUBISHI_AC #if DECODE_MITSUBISHI_AC /// Decode the supplied Mitsubish 144-bit A/C message. /// Status: BETA / Probably works /// @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. /// @see https://www.analysir.com/blog/2015/01/06/reverse-engineering-mitsubishi-ac-infrared-protocol/ bool IRrecv::decodeMitsubishiAC(decode_results *results, uint16_t offset, const uint16_t nbits, const bool strict) { if (results->rawlen <= ((kMitsubishiACBits * 2) + 2) + offset) { DPRINTLN("Shorter than shortest possibly expected."); return false; // Shorter than shortest possibly expected. } if (strict && nbits != kMitsubishiACBits) { DPRINTLN("Request is out of spec."); return false; // Request is out of spec. } for (uint8_t i = 0; i < kMitsubishiACStateLength; i++) results->state[i] = 0; bool failure = false; uint8_t rep = 0; do { failure = false; // Header: // Sometime happens that junk signals arrives before the real message bool headerFound = false; while (!headerFound && offset < (results->rawlen - (kMitsubishiACBits * 2 + 2))) { headerFound = matchMark(results->rawbuf[offset], kMitsubishiAcHdrMark) && matchSpace(results->rawbuf[offset + 1], kMitsubishiAcHdrSpace); offset += 2; } if (!headerFound) { DPRINTLN("Header mark not found."); return false; } DPRINT("Header mark found at #"); DPRINTLN(offset - 2); // Decode byte-by-byte: match_result_t data_result; for (uint8_t i = 0; i < kMitsubishiACStateLength && !failure; i++) { results->state[i] = 0; data_result = matchData(&(results->rawbuf[offset]), 8, kMitsubishiAcBitMark, kMitsubishiAcOneSpace, kMitsubishiAcBitMark, kMitsubishiAcZeroSpace, _tolerance + kMitsubishiAcExtraTolerance, 0, false); if (data_result.success == false) { failure = true; DPRINT("Byte decode failed at #"); DPRINTLN((uint16_t)i); } else { results->state[i] = data_result.data; offset += data_result.used; DPRINT((uint16_t)results->state[i]); DPRINT(","); } DPRINTLN(""); } // HEADER validation: if (failure || results->state[0] != 0x23 || results->state[1] != 0xCB || results->state[2] != 0x26 || results->state[3] != 0x01 || results->state[4] != 0x00) { DPRINTLN("Header mismatch."); failure = true; } else { // DATA part: // FOOTER checksum: if (!IRMitsubishiAC::validChecksum(results->state)) { DPRINTLN("Checksum error."); failure = true; } } if (rep != kMitsubishiACMinRepeat && failure) { bool repeatMarkFound = false; while (!repeatMarkFound && offset < (results->rawlen - (kMitsubishiACBits * 2 + 4))) { repeatMarkFound = matchMark(results->rawbuf[offset], kMitsubishiAcRptMark) && matchSpace(results->rawbuf[offset + 1], kMitsubishiAcRptSpace); offset += 2; } if (!repeatMarkFound) { DPRINTLN("First attempt failure and repeat mark not found."); return false; } } rep++; // Check if the repeat is correct if we need strict decode: if (strict && !failure) { DPRINTLN("Strict repeat check enabled."); // Repeat mark and space: if (!matchMark(results->rawbuf[offset++], kMitsubishiAcRptMark) || !matchSpace(results->rawbuf[offset++], kMitsubishiAcRptSpace)) { DPRINTLN("Repeat mark error."); return false; } // Header mark and space: if (!matchMark(results->rawbuf[offset++], kMitsubishiAcHdrMark) || !matchSpace(results->rawbuf[offset++], kMitsubishiAcHdrSpace)) { DPRINTLN("Repeat header error."); return false; } // Payload: for (uint8_t i = 0; i < kMitsubishiACStateLength; i++) { data_result = matchData(&(results->rawbuf[offset]), 8, kMitsubishiAcBitMark, kMitsubishiAcOneSpace, kMitsubishiAcBitMark, kMitsubishiAcZeroSpace, _tolerance + kMitsubishiAcExtraTolerance, 0, false); if (data_result.success == false || data_result.data != results->state[i]) { DPRINTLN("Repeat payload error."); return false; } offset += data_result.used; } } // strict repeat check } while (failure && rep <= kMitsubishiACMinRepeat); results->decode_type = MITSUBISHI_AC; results->bits = nbits; return !failure; } #endif // DECODE_MITSUBISHI_AC // Code to emulate Mitsubishi A/C IR remote control unit. // Inspired and derived from the work done at: // https://github.com/r45635/HVAC-IR-Control /// 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? /// @warning Consider this very alpha code. Seems to work, but not validated. IRMitsubishiAC::IRMitsubishiAC(const uint16_t pin, const bool inverted, const bool use_modulation) : _irsend(pin, inverted, use_modulation) { stateReset(); } /// Reset the state of the remote to a known good state/sequence. void IRMitsubishiAC::stateReset(void) { // The state of the IR remote in IR code form. // Known good state obtained from: // https://github.com/r45635/HVAC-IR-Control/blob/master/HVAC_ESP8266/HVAC_ESP8266.ino#L108 static const uint8_t kReset[kMitsubishiACStateLength] = { 0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x08, 0x06, 0x30, 0x45, 0x67}; setRaw(kReset); } /// Set up hardware to be able to send a message. void IRMitsubishiAC::begin(void) { _irsend.begin(); } #if SEND_MITSUBISHI_AC /// Send the current internal state as an IR message. /// @param[in] repeat Nr. of times the message will be repeated. void IRMitsubishiAC::send(const uint16_t repeat) { _irsend.sendMitsubishiAC(getRaw(), kMitsubishiACStateLength, repeat); } #endif // SEND_MITSUBISHI_AC /// 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 *IRMitsubishiAC::getRaw(void) { checksum(); return _.raw; } /// Set the internal state from a valid code for this protocol. /// @param[in] data A valid code for this protocol. void IRMitsubishiAC::setRaw(const uint8_t *data) { std::memcpy(_.raw, data, kMitsubishiACStateLength); } /// Calculate and set the checksum values for the internal state. void IRMitsubishiAC::checksum(void) { _.Sum = calculateChecksum(_.raw); } /// Verify the checksum is valid for a given state. /// @param[in] data The array to verify the checksum of. /// @return true, if the state has a valid checksum. Otherwise, false. bool IRMitsubishiAC::validChecksum(const uint8_t *data) { return calculateChecksum(data) == data[kMitsubishiACStateLength - 1]; } /// Calculate the checksum for a given state. /// @param[in] data The value to calc the checksum of. /// @return The calculated checksum value. uint8_t IRMitsubishiAC::calculateChecksum(const uint8_t *data) { return sumBytes(data, kMitsubishiACStateLength - 1); } /// Set the requested power state of the A/C to on. void IRMitsubishiAC::on(void) { setPower(true); } /// Set the requested power state of the A/C to off. void IRMitsubishiAC::off(void) { setPower(false); } /// Change the power setting. /// @param[in] on true, the setting is on. false, the setting is off. void IRMitsubishiAC::setPower(bool on) { _.Power = on; } /// Get the value of the current power setting. /// @return true, the setting is on. false, the setting is off. bool IRMitsubishiAC::getPower(void) const { return _.Power; } /// Set the temperature. /// @param[in] degrees The temperature in degrees celsius. /// @note The temperature resolution is 0.5 of a degree. void IRMitsubishiAC::setTemp(const float degrees) { // Make sure we have desired temp in the correct range. float celsius = std::max(degrees, kMitsubishiAcMinTemp); celsius = std::min(celsius, kMitsubishiAcMaxTemp); // Convert to integer nr. of half degrees. uint8_t nrHalfDegrees = celsius * 2; // Do we have a half degree celsius? _.HalfDegree = nrHalfDegrees & 1; _.Temp = static_cast(nrHalfDegrees / 2 - kMitsubishiAcMinTemp); } /// Get the current temperature setting. /// @return The current setting for temp. in degrees celsius. /// @note The temperature resolution is 0.5 of a degree. float IRMitsubishiAC::getTemp(void) const { return _.Temp + kMitsubishiAcMinTemp + (_.HalfDegree ? 0.5 : 0); } /// Set the speed of the fan. /// @param[in] speed The desired setting. 0 is auto, 1-5 is speed, 6 is silent. void IRMitsubishiAC::setFan(const uint8_t speed) { uint8_t fan = speed; // Bounds check if (fan > kMitsubishiAcFanSilent) fan = kMitsubishiAcFanMax; // Set the fan to maximum if out of range. // Auto has a special bit. _.FanAuto = (fan == kMitsubishiAcFanAuto); if (fan >= kMitsubishiAcFanMax) fan--; // There is no spoon^H^H^Heed 5 (max), pretend it doesn't exist. _.Fan = fan; } /// Get the current fan speed setting. /// @return The current fan speed/mode. uint8_t IRMitsubishiAC::getFan(void) const { uint8_t fan = _.Fan; if (fan == kMitsubishiAcFanMax) return kMitsubishiAcFanSilent; return fan; } /// Get the operating mode setting of the A/C. /// @return The current operating mode setting. uint8_t IRMitsubishiAC::getMode(void) const { return _.Mode; } /// Set the operating mode of the A/C. /// @param[in] mode The desired operating mode. void IRMitsubishiAC::setMode(const uint8_t mode) { // If we get an unexpected mode, default to AUTO. switch (mode) { case kMitsubishiAcAuto: _.raw[8] = 0b00110000; break; case kMitsubishiAcCool: _.raw[8] = 0b00110110; break; case kMitsubishiAcDry: _.raw[8] = 0b00110010; break; case kMitsubishiAcHeat: _.raw[8] = 0b00110000; break; default: _.raw[8] = 0b00110000; _.Mode = kMitsubishiAcAuto; return; } _.Mode = mode; } /// Set the requested vane (Vertical Swing) operation mode of the a/c unit. /// @param[in] position The position/mode to set the vane to. void IRMitsubishiAC::setVane(const uint8_t position) { uint8_t pos = std::min(position, kMitsubishiAcVaneAutoMove); // bounds check _.VaneBit = 1; _.Vane = pos; } /// Set the requested wide-vane (Horizontal Swing) operation mode of the a/c. /// @param[in] position The position/mode to set the wide vane to. void IRMitsubishiAC::setWideVane(const uint8_t position) { _.WideVane = std::min(position, kMitsubishiAcWideVaneAuto); } /// Get the Vane (Vertical Swing) mode of the A/C. /// @return The native position/mode setting. uint8_t IRMitsubishiAC::getVane(void) const { return _.Vane; } /// Get the Wide Vane (Horizontal Swing) mode of the A/C. /// @return The native position/mode setting. uint8_t IRMitsubishiAC::getWideVane(void) const { return _.WideVane; } /// Get the clock time of the A/C unit. /// @return Nr. of 10 minute increments past midnight. /// @note 1 = 1/6 hour (10 minutes). e.g. 4pm = 48. uint8_t IRMitsubishiAC::getClock(void) const { return _.Clock; } /// Set the clock time on the A/C unit. /// @param[in] clock Nr. of 10 minute increments past midnight. /// @note 1 = 1/6 hour (10 minutes). e.g. 6am = 36. void IRMitsubishiAC::setClock(const uint8_t clock) { _.Clock = clock; } /// Get the desired start time of the A/C unit. /// @return Nr. of 10 minute increments past midnight. /// @note 1 = 1/6 hour (10 minutes). e.g. 4pm = 48. uint8_t IRMitsubishiAC::getStartClock(void) const { return _.StartClock; } /// Set the desired start time of the A/C unit. /// @param[in] clock Nr. of 10 minute increments past midnight. /// @note 1 = 1/6 hour (10 minutes). e.g. 8pm = 120. void IRMitsubishiAC::setStartClock(const uint8_t clock) { _.StartClock = clock; } /// Get the desired stop time of the A/C unit. /// @return Nr. of 10 minute increments past midnight. /// @note 1 = 1/6 hour (10 minutes). e.g. 10pm = 132. uint8_t IRMitsubishiAC::getStopClock(void) const { return _.StopClock; } /// Set the desired stop time of the A/C unit. /// @param[in] clock Nr. of 10 minute increments past midnight. /// @note 1 = 1/6 hour (10 minutes). e.g. 10pm = 132. void IRMitsubishiAC::setStopClock(const uint8_t clock) { _.StopClock = clock; } /// Get the timers active setting of the A/C. /// @return The current timers enabled. /// @note Possible values: kMitsubishiAcNoTimer, /// kMitsubishiAcStartTimer, kMitsubishiAcStopTimer, /// kMitsubishiAcStartStopTimer uint8_t IRMitsubishiAC::getTimer(void) const { return _.Timer; } /// Set the timers active setting of the A/C. /// @param[in] timer The timer code indicating which ones are active. /// @note Possible values: kMitsubishiAcNoTimer, /// kMitsubishiAcStartTimer, kMitsubishiAcStopTimer, /// kMitsubishiAcStartStopTimer void IRMitsubishiAC::setTimer(const uint8_t timer) { _.Timer = timer; } /// 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 IRMitsubishiAC::convertMode(const stdAc::opmode_t mode) { switch (mode) { case stdAc::opmode_t::kCool: return kMitsubishiAcCool; case stdAc::opmode_t::kHeat: return kMitsubishiAcHeat; case stdAc::opmode_t::kDry: return kMitsubishiAcDry; default: return kMitsubishiAcAuto; } } /// 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 IRMitsubishiAC::convertFan(const stdAc::fanspeed_t speed) { switch (speed) { case stdAc::fanspeed_t::kMin: return kMitsubishiAcFanSilent; case stdAc::fanspeed_t::kLow: return kMitsubishiAcFanRealMax - 3; case stdAc::fanspeed_t::kMedium: return kMitsubishiAcFanRealMax - 2; case stdAc::fanspeed_t::kHigh: return kMitsubishiAcFanRealMax - 1; case stdAc::fanspeed_t::kMax: return kMitsubishiAcFanRealMax; default: return kMitsubishiAcFanAuto; } } /// Convert a stdAc::swingv_t enum into it's native setting. /// @param[in] position The enum to be converted. /// @return The native equivalent of the enum. /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1399 /// @see https://github.com/crankyoldgit/IRremoteESP8266/pull/1401 uint8_t IRMitsubishiAC::convertSwingV(const stdAc::swingv_t position) { switch (position) { case stdAc::swingv_t::kHighest: return kMitsubishiAcVaneHighest; case stdAc::swingv_t::kHigh: return kMitsubishiAcVaneHigh; case stdAc::swingv_t::kMiddle: return kMitsubishiAcVaneMiddle; case stdAc::swingv_t::kLow: return kMitsubishiAcVaneLow; case stdAc::swingv_t::kLowest: return kMitsubishiAcVaneLowest; // These model Mitsubishi A/C have two automatic settings. // 1. A typical up & down oscillation. (Native Swing) // 2. The A/C determines where the best placement for the vanes, outside of // user control. (Native Auto) // Native "Swing" is what we consider "Auto" in stdAc. (Case 1) case stdAc::swingv_t::kAuto: return kMitsubishiAcVaneSwing; // Native "Auto" doesn't have a good match for this in stdAc. (Case 2) // So we repurpose stdAc's "Off" (and anything else) to be Native Auto. default: return kMitsubishiAcVaneAuto; } } /// Convert a stdAc::swingh_t enum into it's native setting. /// @param[in] position The enum to be converted. /// @return The native equivalent of the enum. uint8_t IRMitsubishiAC::convertSwingH(const stdAc::swingh_t position) { switch (position) { case stdAc::swingh_t::kLeftMax: return kMitsubishiAcWideVaneLeftMax; case stdAc::swingh_t::kLeft: return kMitsubishiAcWideVaneLeft; case stdAc::swingh_t::kMiddle: return kMitsubishiAcWideVaneMiddle; case stdAc::swingh_t::kRight: return kMitsubishiAcWideVaneRight; case stdAc::swingh_t::kRightMax: return kMitsubishiAcWideVaneRightMax; case stdAc::swingh_t::kWide: return kMitsubishiAcWideVaneWide; case stdAc::swingh_t::kAuto: return kMitsubishiAcWideVaneAuto; default: return kMitsubishiAcWideVaneMiddle; } } /// 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 IRMitsubishiAC::toCommonMode(const uint8_t mode) { switch (mode) { case kMitsubishiAcCool: return stdAc::opmode_t::kCool; case kMitsubishiAcHeat: return stdAc::opmode_t::kHeat; case kMitsubishiAcDry: return stdAc::opmode_t::kDry; 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 IRMitsubishiAC::toCommonFanSpeed(const uint8_t speed) { switch (speed) { case kMitsubishiAcFanRealMax: return stdAc::fanspeed_t::kMax; case kMitsubishiAcFanRealMax - 1: return stdAc::fanspeed_t::kHigh; case kMitsubishiAcFanRealMax - 2: return stdAc::fanspeed_t::kMedium; case kMitsubishiAcFanRealMax - 3: return stdAc::fanspeed_t::kLow; case kMitsubishiAcFanSilent: return stdAc::fanspeed_t::kMin; default: return stdAc::fanspeed_t::kAuto; } } /// Convert a native vertical swing postion to it's common equivalent. /// @param[in] pos A native position to convert. /// @return The common vertical swing position. /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1399 /// @see https://github.com/crankyoldgit/IRremoteESP8266/pull/1401 stdAc::swingv_t IRMitsubishiAC::toCommonSwingV(const uint8_t pos) { switch (pos) { case kMitsubishiAcVaneHighest: return stdAc::swingv_t::kHighest; case kMitsubishiAcVaneHigh: return stdAc::swingv_t::kHigh; case kMitsubishiAcVaneMiddle: return stdAc::swingv_t::kMiddle; case kMitsubishiAcVaneLow: return stdAc::swingv_t::kLow; case kMitsubishiAcVaneLowest: return stdAc::swingv_t::kLowest; // These model Mitsubishi A/C have two automatic settings. // 1. A typical up & down oscillation. (Native Swing) // 2. The A/C determines where the best placement for the vanes, outside of // user control. (Native Auto) // Native "Auto" doesn't have a good match for this in stdAc. (Case 2) // So we repurpose stdAc's "Off" to be Native Auto. case kMitsubishiAcVaneAuto: return stdAc::swingv_t::kOff; // Native "Swing" is what we consider "Auto" in stdAc. (Case 1) default: return stdAc::swingv_t::kAuto; } } /// Convert a native horizontal swing postion to it's common equivalent. /// @param[in] pos A native position to convert. /// @return The common horizontal swing position. stdAc::swingh_t IRMitsubishiAC::toCommonSwingH(const uint8_t pos) { switch (pos) { case kMitsubishiAcWideVaneLeftMax: return stdAc::swingh_t::kLeftMax; case kMitsubishiAcWideVaneLeft: return stdAc::swingh_t::kLeft; case kMitsubishiAcWideVaneMiddle: return stdAc::swingh_t::kMiddle; case kMitsubishiAcWideVaneRight: return stdAc::swingh_t::kRight; case kMitsubishiAcWideVaneRightMax: return stdAc::swingh_t::kRightMax; case kMitsubishiAcWideVaneWide: return stdAc::swingh_t::kWide; default: return stdAc::swingh_t::kAuto; } } /// Convert the current internal state into its stdAc::state_t equivalent. /// @return The stdAc equivalent of the native settings. stdAc::state_t IRMitsubishiAC::toCommon(void) const { stdAc::state_t result; result.protocol = decode_type_t::MITSUBISHI_AC; result.model = -1; // No models used. result.power = _.Power; result.mode = toCommonMode(_.Mode); result.celsius = true; result.degrees = getTemp(); result.fanspeed = toCommonFanSpeed(getFan()); result.swingv = toCommonSwingV(_.Vane); result.swingh = toCommonSwingH(_.WideVane); result.quiet = getFan() == kMitsubishiAcFanSilent; // Not supported. result.turbo = false; result.clean = false; result.econo = false; result.filter = false; result.light = false; result.beep = false; result.sleep = -1; result.clock = -1; return result; } /// Change the Weekly Timer Enabled setting. /// @param[in] on true, the setting is on. false, the setting is off. void IRMitsubishiAC::setWeeklyTimerEnabled(const bool on) { _.WeeklyTimer = on; } /// Get the value of the WeeklyTimer Enabled setting. /// @return true, the setting is on. false, the setting is off. bool IRMitsubishiAC::getWeeklyTimerEnabled(void) const { return _.WeeklyTimer; } /// Convert the internal state into a human readable string. /// @return A string containing the settings in human-readable form. String IRMitsubishiAC::toString(void) const { String result = ""; result.reserve(110); // Reserve some heap for the string to reduce fragging. result += addBoolToString(_.Power, kPowerStr, false); result += addModeToString(_.Mode, kMitsubishiAcAuto, kMitsubishiAcCool, kMitsubishiAcHeat, kMitsubishiAcDry, kMitsubishiAcAuto); result += addTempFloatToString(getTemp()); result += addFanToString(getFan(), kMitsubishiAcFanRealMax, kMitsubishiAcFanRealMax - 3, kMitsubishiAcFanAuto, kMitsubishiAcFanQuiet, kMitsubishiAcFanRealMax - 2); result += addSwingVToString(_.Vane, kMitsubishiAcVaneAuto, kMitsubishiAcVaneHighest, kMitsubishiAcVaneHigh, kMitsubishiAcVaneAuto, // Upper Middle unused. kMitsubishiAcVaneMiddle, kMitsubishiAcVaneAuto, // Lower Middle unused. kMitsubishiAcVaneLow, kMitsubishiAcVaneLowest, kMitsubishiAcVaneAuto, kMitsubishiAcVaneSwing, // Below are unused. kMitsubishiAcVaneAuto, kMitsubishiAcVaneAuto); result += addSwingHToString(_.WideVane, kMitsubishiAcWideVaneAuto, kMitsubishiAcWideVaneLeftMax, kMitsubishiAcWideVaneLeft, kMitsubishiAcWideVaneMiddle, kMitsubishiAcWideVaneRight, kMitsubishiAcWideVaneRightMax, kMitsubishiAcWideVaneAuto, // Unused kMitsubishiAcWideVaneAuto, // Unused kMitsubishiAcWideVaneAuto, // Unused kMitsubishiAcWideVaneAuto, // Unused kMitsubishiAcWideVaneWide); result += addLabeledString(minsToString(_.Clock * 10), kClockStr); result += addLabeledString(minsToString(_.StartClock * 10), kOnTimerStr); result += addLabeledString(minsToString(_.StopClock * 10), kOffTimerStr); result += kCommaSpaceStr; result += kTimerStr; result += kColonSpaceStr; switch (_.Timer) { case kMitsubishiAcNoTimer: result += '-'; break; case kMitsubishiAcStartTimer: result += kStartStr; break; case kMitsubishiAcStopTimer: result += kStopStr; break; case kMitsubishiAcStartStopTimer: result += kStartStr; result += '+'; result += kStopStr; break; default: result += F("? ("); result += _.Timer; result += ')'; } result += addBoolToString(_.WeeklyTimer, kWeeklyTimerStr); return result; } #if SEND_MITSUBISHI136 /// Send a Mitsubishi 136-bit A/C message. (MITSUBISHI136) /// Status: BETA / Probably working. Needs to be tested against a real device. /// @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/888 void IRsend::sendMitsubishi136(const unsigned char data[], const uint16_t nbytes, const uint16_t repeat) { if (nbytes < kMitsubishi136StateLength) return; // Not enough bytes to send a proper message. sendGeneric(kMitsubishi136HdrMark, kMitsubishi136HdrSpace, kMitsubishi136BitMark, kMitsubishi136OneSpace, kMitsubishi136BitMark, kMitsubishi136ZeroSpace, kMitsubishi136BitMark, kMitsubishi136Gap, data, nbytes, 38, false, repeat, 50); } #endif // SEND_MITSUBISHI136 #if DECODE_MITSUBISHI136 /// Decode the supplied Mitsubishi 136-bit A/C message. (MITSUBISHI136) /// Status: STABLE / Reported as 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. /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/888 bool IRrecv::decodeMitsubishi136(decode_results *results, uint16_t offset, const uint16_t nbits, const bool strict) { if (nbits % 8 != 0) return false; // Not a multiple of an 8 bit byte. if (strict) { // Do checks to see if it matches the spec. if (nbits != kMitsubishi136Bits) return false; } uint16_t used = matchGeneric(results->rawbuf + offset, results->state, results->rawlen - offset, nbits, kMitsubishi136HdrMark, kMitsubishi136HdrSpace, kMitsubishi136BitMark, kMitsubishi136OneSpace, kMitsubishi136BitMark, kMitsubishi136ZeroSpace, kMitsubishi136BitMark, kMitsubishi136Gap, true, _tolerance, 0, false); if (!used) return false; if (strict) { // Header validation: Codes start with 0x23CB26 if (results->state[0] != 0x23 || results->state[1] != 0xCB || results->state[2] != 0x26) return false; if (!IRMitsubishi136::validChecksum(results->state, nbits / 8)) return false; } results->decode_type = MITSUBISHI136; results->bits = nbits; return true; } #endif // DECODE_MITSUBISHI136 // Code to emulate Mitsubishi 136bit 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? IRMitsubishi136::IRMitsubishi136(const uint16_t pin, const bool inverted, const bool use_modulation) : _irsend(pin, inverted, use_modulation) { stateReset(); } /// Reset the state of the remote to a known good state/sequence. void IRMitsubishi136::stateReset(void) { // The state of the IR remote in IR code form. // Known good state obtained from: // https://docs.google.com/spreadsheets/d/1f8EGfIbBUo2B-CzUFdrgKQprWakoYNKM80IKZN4KXQE/edit#gid=312397579&range=A10 static const uint8_t kReset[kMitsubishi136StateLength] = { 0x23, 0xCB, 0x26, 0x21, 0x00, 0x40, 0xC2, 0xC7, 0x04}; std::memcpy(_.raw, kReset, kMitsubishi136StateLength); } /// Calculate the checksum for the current internal state of the remote. void IRMitsubishi136::checksum(void) { for (uint8_t i = 0; i < 6; i++) _.raw[kMitsubishi136PowerByte + 6 + i] = ~_.raw[kMitsubishi136PowerByte + i]; } /// Verify the checksum is valid for a given state. /// @param[in] data The array to verify the checksum of. /// @param[in] len The length of the data array. /// @return true, if the state has a valid checksum. Otherwise, false. bool IRMitsubishi136::validChecksum(const uint8_t *data, const uint16_t len) { if (len < kMitsubishi136StateLength) return false; const uint16_t half = (len - kMitsubishi136PowerByte) / 2; for (uint8_t i = 0; i < half; i++) { // This variable is needed to avoid the warning: (known compiler issue) // warning: comparison of promoted ~unsigned with unsigned [-Wsign-compare] const uint8_t inverted = ~data[kMitsubishi136PowerByte + half + i]; if (data[kMitsubishi136PowerByte + i] != inverted) return false; } return true; } /// Set up hardware to be able to send a message. void IRMitsubishi136::begin(void) { _irsend.begin(); } #if SEND_MITSUBISHI136 /// Send the current internal state as an IR message. /// @param[in] repeat Nr. of times the message will be repeated. void IRMitsubishi136::send(const uint16_t repeat) { _irsend.sendMitsubishi136(getRaw(), kMitsubishi136StateLength, repeat); } #endif // SEND_MITSUBISHI136 /// 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 *IRMitsubishi136::getRaw(void) { checksum(); return _.raw; } /// Set the internal state from a valid code for this protocol. /// @param[in] data A valid code for this protocol. void IRMitsubishi136::setRaw(const uint8_t *data) { std::memcpy(_.raw, data, kMitsubishi136StateLength); } /// Set the requested power state of the A/C to on. void IRMitsubishi136::on(void) { setPower(true); } /// Set the requested power state of the A/C to off. void IRMitsubishi136::off(void) { setPower(false); } /// Change the power setting. /// @param[in] on true, the setting is on. false, the setting is off. void IRMitsubishi136::setPower(bool on) { _.Power = on; } /// Get the value of the current power setting. /// @return true, the setting is on. false, the setting is off. bool IRMitsubishi136::getPower(void) const { return _.Power; } /// Set the temperature. /// @param[in] degrees The temperature in degrees celsius. void IRMitsubishi136::setTemp(const uint8_t degrees) { uint8_t temp = std::max((uint8_t)kMitsubishi136MinTemp, degrees); temp = std::min((uint8_t)kMitsubishi136MaxTemp, temp); _.Temp = temp - kMitsubishiAcMinTemp; } /// Get the current temperature setting. /// @return The current setting for temp. in degrees celsius. uint8_t IRMitsubishi136::getTemp(void) const { return _.Temp + kMitsubishiAcMinTemp; } /// Set the speed of the fan. /// @param[in] speed The desired setting. void IRMitsubishi136::setFan(const uint8_t speed) { _.Fan = std::min(speed, kMitsubishi136FanMax); } /// Get the current fan speed setting. /// @return The current fan speed/mode. uint8_t IRMitsubishi136::getFan(void) const { return _.Fan; } /// Get the operating mode setting of the A/C. /// @return The current operating mode setting. uint8_t IRMitsubishi136::getMode(void) const { return _.Mode; } /// Set the operating mode of the A/C. /// @param[in] mode The desired operating mode. void IRMitsubishi136::setMode(const uint8_t mode) { // If we get an unexpected mode, default to AUTO. switch (mode) { case kMitsubishi136Fan: case kMitsubishi136Cool: case kMitsubishi136Heat: case kMitsubishi136Auto: case kMitsubishi136Dry: _.Mode = mode; break; default: _.Mode = kMitsubishi136Auto; } } /// Set the Vertical Swing mode of the A/C. /// @param[in] position The position/mode to set the swing to. void IRMitsubishi136::setSwingV(const uint8_t position) { // If we get an unexpected mode, default to auto. switch (position) { case kMitsubishi136SwingVLowest: case kMitsubishi136SwingVLow: case kMitsubishi136SwingVHigh: case kMitsubishi136SwingVHighest: case kMitsubishi136SwingVAuto: _.SwingV = position; break; default: _.SwingV = kMitsubishi136SwingVAuto; } } /// Get the Vertical Swing mode of the A/C. /// @return The native position/mode setting. uint8_t IRMitsubishi136::getSwingV(void) const { return _.SwingV; } /// Set the Quiet mode of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. void IRMitsubishi136::setQuiet(bool on) { if (on) setFan(kMitsubishi136FanQuiet); else if (getQuiet()) setFan(kMitsubishi136FanLow); } /// Get the Quiet mode of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRMitsubishi136::getQuiet(void) const { return _.Fan == kMitsubishi136FanQuiet; } /// 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 IRMitsubishi136::convertMode(const stdAc::opmode_t mode) { switch (mode) { case stdAc::opmode_t::kCool: return kMitsubishi136Cool; case stdAc::opmode_t::kHeat: return kMitsubishi136Heat; case stdAc::opmode_t::kDry: return kMitsubishi136Dry; case stdAc::opmode_t::kFan: return kMitsubishi136Fan; default: return kMitsubishi136Auto; } } /// 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 IRMitsubishi136::convertFan(const stdAc::fanspeed_t speed) { switch (speed) { case stdAc::fanspeed_t::kMin: return kMitsubishi136FanMin; case stdAc::fanspeed_t::kLow: return kMitsubishi136FanLow; case stdAc::fanspeed_t::kHigh: case stdAc::fanspeed_t::kMax: return kMitsubishi136FanMax; default: return kMitsubishi136FanMed; } } /// Convert a stdAc::swingv_t enum into it's native setting. /// @param[in] position The enum to be converted. /// @return The native equivalent of the enum. uint8_t IRMitsubishi136::convertSwingV(const stdAc::swingv_t position) { switch (position) { case stdAc::swingv_t::kHighest: return kMitsubishi136SwingVHighest; case stdAc::swingv_t::kHigh: case stdAc::swingv_t::kMiddle: return kMitsubishi136SwingVHigh; case stdAc::swingv_t::kLow: return kMitsubishi136SwingVLow; case stdAc::swingv_t::kLowest: return kMitsubishi136SwingVLowest; default: return kMitsubishi136SwingVAuto; } } /// 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 IRMitsubishi136::toCommonMode(const uint8_t mode) { switch (mode) { case kMitsubishi136Cool: return stdAc::opmode_t::kCool; case kMitsubishi136Heat: return stdAc::opmode_t::kHeat; case kMitsubishi136Dry: return stdAc::opmode_t::kDry; case kMitsubishi136Fan: 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 IRMitsubishi136::toCommonFanSpeed(const uint8_t speed) { switch (speed) { case kMitsubishi136FanMax: return stdAc::fanspeed_t::kMax; case kMitsubishi136FanMed: return stdAc::fanspeed_t::kMedium; case kMitsubishi136FanLow: return stdAc::fanspeed_t::kLow; case kMitsubishi136FanMin: return stdAc::fanspeed_t::kMin; default: return stdAc::fanspeed_t::kMedium; } } /// Convert a native vertical swing postion to it's common equivalent. /// @param[in] pos A native position to convert. /// @return The common vertical swing position. stdAc::swingv_t IRMitsubishi136::toCommonSwingV(const uint8_t pos) { switch (pos) { case kMitsubishi136SwingVHighest: return stdAc::swingv_t::kHighest; case kMitsubishi136SwingVHigh: return stdAc::swingv_t::kHigh; case kMitsubishi136SwingVLow: return stdAc::swingv_t::kLow; case kMitsubishi136SwingVLowest: return stdAc::swingv_t::kLowest; default: return stdAc::swingv_t::kAuto; } } /// Convert the current internal state into its stdAc::state_t equivalent. /// @return The stdAc equivalent of the native settings. stdAc::state_t IRMitsubishi136::toCommon(void) const { stdAc::state_t result; result.protocol = decode_type_t::MITSUBISHI136; result.model = -1; // No models used. result.power = _.Power; result.mode = toCommonMode(_.Mode); result.celsius = true; result.degrees = getTemp(); result.fanspeed = toCommonFanSpeed(_.Fan); result.swingv = toCommonSwingV(_.SwingV); result.quiet = getQuiet(); // Not supported. result.swingh = stdAc::swingh_t::kOff; result.turbo = false; result.clean = false; result.econo = false; result.filter = false; result.light = false; result.beep = false; result.sleep = -1; result.clock = -1; return result; } /// Convert the internal state into a human readable string. /// @return A string containing the settings in human-readable form. String IRMitsubishi136::toString(void) const { String result = ""; result.reserve(80); // Reserve some heap for the string to reduce fragging. result += addBoolToString(_.Power, kPowerStr, false); result += addModeToString(_.Mode, kMitsubishi136Auto, kMitsubishi136Cool, kMitsubishi136Heat, kMitsubishi136Dry, kMitsubishi136Fan); result += addTempToString(getTemp()); result += addFanToString(_.Fan, kMitsubishi136FanMax, kMitsubishi136FanLow, kMitsubishi136FanMax, kMitsubishi136FanQuiet, kMitsubishi136FanMed); result += addSwingVToString(_.SwingV, kMitsubishi136SwingVAuto, kMitsubishi136SwingVHighest, kMitsubishi136SwingVHigh, kMitsubishi136SwingVAuto, // Unused kMitsubishi136SwingVAuto, // Unused kMitsubishi136SwingVAuto, // Unused kMitsubishi136SwingVLow, kMitsubishi136SwingVLow, // Below are unused. kMitsubishi136SwingVAuto, kMitsubishi136SwingVAuto, kMitsubishi136SwingVAuto, kMitsubishi136SwingVAuto); result += addBoolToString(getQuiet(), kQuietStr); return result; } #if SEND_MITSUBISHI112 /// Send a Mitsubishi 112-bit A/C formatted message. (MITSUBISHI112) /// Status: Stable / Reported as working. /// @param[in] data The message to be sent. /// @param[in] nbytes The number of bytes of message to be sent. /// @param[in] repeat The number of times the command is to be repeated. /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/947 void IRsend::sendMitsubishi112(const unsigned char data[], const uint16_t nbytes, const uint16_t repeat) { if (nbytes < kMitsubishi112StateLength) return; // Not enough bytes to send a proper message. sendGeneric(kMitsubishi112HdrMark, kMitsubishi112HdrSpace, kMitsubishi112BitMark, kMitsubishi112OneSpace, kMitsubishi112BitMark, kMitsubishi112ZeroSpace, kMitsubishi112BitMark, kMitsubishi112Gap, data, nbytes, 38, false, repeat, 50); } #endif // SEND_MITSUBISHI112 #if DECODE_MITSUBISHI112 || DECODE_TCL112AC /// Decode the supplied Mitsubishi/TCL 112-bit A/C message. /// (MITSUBISHI112, TCL112AC) /// Status: STABLE / Reported as 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. /// @note Note Mitsubishi112 & Tcl112Ac are basically the same protocol. /// The only significant difference I can see is Mitsubishi112 has a /// slightly longer header mark. We will use that to determine which /// variant it should be. The other differences require full decoding and /// only only with certain settings. /// There are some other timing differences too, but the tolerances will /// overlap. /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/619 /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/947 bool IRrecv::decodeMitsubishi112(decode_results *results, uint16_t offset, const uint16_t nbits, const bool strict) { if (results->rawlen < (2 * nbits) + kHeader + kFooter - 1 + offset) return false; if (nbits % 8 != 0) return false; // Not a multiple of an 8 bit byte. if (strict) { // Do checks to see if it matches the spec. if (nbits != kMitsubishi112Bits && nbits != kTcl112AcBits) return false; } decode_type_t typeguess = decode_type_t::UNKNOWN; uint16_t hdrspace; uint16_t bitmark; uint16_t onespace; uint16_t zerospace; uint32_t gap; uint8_t tolerance = _tolerance; // Header #if DECODE_MITSUBISHI112 if (matchMark(results->rawbuf[offset], kMitsubishi112HdrMark, kMitsubishi112HdrMarkTolerance, 0)) { typeguess = decode_type_t::MITSUBISHI112; hdrspace = kMitsubishi112HdrSpace; bitmark = kMitsubishi112BitMark; onespace = kMitsubishi112OneSpace; zerospace = kMitsubishi112ZeroSpace; gap = kMitsubishi112Gap; } #endif // DECODE_MITSUBISHI112 #if DECODE_TCL112AC if (typeguess == decode_type_t::UNKNOWN && // We didn't match Mitsubishi112 matchMark(results->rawbuf[offset], kTcl112AcHdrMark, kTcl112AcHdrMarkTolerance, 0)) { typeguess = decode_type_t::TCL112AC; hdrspace = kTcl112AcHdrSpace; bitmark = kTcl112AcBitMark; onespace = kTcl112AcOneSpace; zerospace = kTcl112AcZeroSpace; gap = kTcl112AcGap; tolerance += kTcl112AcTolerance; } #endif // DECODE_TCL112AC if (typeguess == decode_type_t::UNKNOWN) return false; // No header matched. offset++; uint16_t used = matchGeneric(results->rawbuf + offset, results->state, results->rawlen - offset, nbits, 0, // Skip the header as we matched it earlier. hdrspace, bitmark, onespace, bitmark, zerospace, bitmark, gap, true, tolerance, 0, false); if (!used) return false; if (strict) { // Header validation: Codes start with 0x23CB26 if (results->state[0] != 0x23 || results->state[1] != 0xCB || results->state[2] != 0x26) return false; // TCL112 and MITSUBISHI112 share the exact same checksum. if (!IRTcl112Ac::validChecksum(results->state, nbits / 8)) return false; } // Success results->decode_type = typeguess; 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_MITSUBISHI112 || DECODE_TCL112AC // Code to emulate Mitsubishi 112bit 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? IRMitsubishi112::IRMitsubishi112(const uint16_t pin, const bool inverted, const bool use_modulation) : _irsend(pin, inverted, use_modulation) { stateReset(); } /// Reset the state of the remote to a known good state/sequence. void IRMitsubishi112::stateReset(void) { const uint8_t kReset[kMitsubishi112StateLength] = { 0x23, 0xCB, 0x26, 0x01, 0x00, 0x24, 0x03, 0x0B, 0x10, 0x00, 0x00, 0x00, 0x30}; setRaw(kReset); } /// Calculate the checksum for the current internal state of the remote. void IRMitsubishi112::checksum(void) { _.Sum = IRTcl112Ac::calcChecksum(_.raw, kMitsubishi112StateLength); } /// Set up hardware to be able to send a message. void IRMitsubishi112::begin(void) { _irsend.begin(); } #if SEND_MITSUBISHI112 /// Send the current internal state as an IR message. /// @param[in] repeat Nr. of times the message will be repeated. void IRMitsubishi112::send(const uint16_t repeat) { _irsend.sendMitsubishi112(getRaw(), kMitsubishi112StateLength, repeat); } #endif // SEND_MITSUBISHI112 /// 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 *IRMitsubishi112::getRaw(void) { checksum(); return _.raw; } /// Set the internal state from a valid code for this protocol. /// @param[in] data A valid code for this protocol. void IRMitsubishi112::setRaw(const uint8_t *data) { std::memcpy(_.raw, data, kMitsubishi112StateLength); } /// Set the requested power state of the A/C to off. void IRMitsubishi112::on(void) { setPower(true); } /// Set the requested power state of the A/C to off. void IRMitsubishi112::off(void) { setPower(false); } /// Change the power setting. /// @param[in] on true, the setting is on. false, the setting is off. void IRMitsubishi112::setPower(bool on) { _.Power = on; } /// Get the value of the current power setting. /// @return true, the setting is on. false, the setting is off. bool IRMitsubishi112::getPower(void) const { return _.Power; } /// Set the temperature. /// @param[in] degrees The temperature in degrees celsius. void IRMitsubishi112::setTemp(const uint8_t degrees) { uint8_t temp = std::max((uint8_t)kMitsubishi112MinTemp, degrees); temp = std::min((uint8_t)kMitsubishi112MaxTemp, temp); _.Temp = kMitsubishiAcMaxTemp - temp; } /// Get the current temperature setting. /// @return The current setting for temp. in degrees celsius. uint8_t IRMitsubishi112::getTemp(void) const { return kMitsubishiAcMaxTemp - _.Temp; } /// Set the speed of the fan. /// @param[in] speed The desired setting. void IRMitsubishi112::setFan(const uint8_t speed) { switch (speed) { case kMitsubishi112FanMin: case kMitsubishi112FanLow: case kMitsubishi112FanMed: case kMitsubishi112FanMax: _.Fan = speed; break; default: _.Fan = kMitsubishi112FanMax; } } /// Get the current fan speed setting. /// @return The current fan speed/mode. uint8_t IRMitsubishi112::getFan(void) const { return _.Fan; } /// Get the operating mode setting of the A/C. /// @return The current operating mode setting. uint8_t IRMitsubishi112::getMode(void) const { return _.Mode; } /// Set the operating mode of the A/C. /// @param[in] mode The desired operating mode. void IRMitsubishi112::setMode(const uint8_t mode) { // If we get an unexpected mode, default to AUTO. switch (mode) { // Note: No Fan Only mode. case kMitsubishi112Cool: case kMitsubishi112Heat: case kMitsubishi112Auto: case kMitsubishi112Dry: _.Mode = mode; break; default: _.Mode = kMitsubishi112Auto; } } /// Set the Vertical Swing mode of the A/C. /// @param[in] position The position/mode to set the swing to. void IRMitsubishi112::setSwingV(const uint8_t position) { // If we get an unexpected mode, default to auto. switch (position) { case kMitsubishi112SwingVLowest: case kMitsubishi112SwingVLow: case kMitsubishi112SwingVMiddle: case kMitsubishi112SwingVHigh: case kMitsubishi112SwingVHighest: case kMitsubishi112SwingVAuto: _.SwingV = position; break; default: _.SwingV = kMitsubishi112SwingVAuto; } } /// Get the Vertical Swing mode of the A/C. /// @return The native position/mode setting. uint8_t IRMitsubishi112::getSwingV(void) const { return _.SwingV; } /// Set the Horizontal Swing mode of the A/C. /// @param[in] position The position/mode to set the swing to. void IRMitsubishi112::setSwingH(const uint8_t position) { // If we get an unexpected mode, default to auto. switch (position) { case kMitsubishi112SwingHLeftMax: case kMitsubishi112SwingHLeft: case kMitsubishi112SwingHMiddle: case kMitsubishi112SwingHRight: case kMitsubishi112SwingHRightMax: case kMitsubishi112SwingHWide: case kMitsubishi112SwingHAuto: _.SwingH = position; break; default: _.SwingH = kMitsubishi112SwingHAuto; } } /// Get the Horizontal Swing mode of the A/C. /// @return The native position/mode setting. uint8_t IRMitsubishi112::getSwingH(void) const { return _.SwingH; } /// Set the Quiet mode of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. /// @note There is no true quiet setting on this A/C. void IRMitsubishi112::setQuiet(bool on) { if (on) setFan(kMitsubishi112FanQuiet); else if (getQuiet()) setFan(kMitsubishi112FanLow); } /// Get the Quiet mode of the A/C. /// @return true, the setting is on. false, the setting is off. /// @note There is no true quiet setting on this A/C. bool IRMitsubishi112::getQuiet(void) const { return _.Fan == kMitsubishi112FanQuiet; } /// 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 IRMitsubishi112::convertMode(const stdAc::opmode_t mode) { switch (mode) { case stdAc::opmode_t::kCool: return kMitsubishi112Cool; case stdAc::opmode_t::kHeat: return kMitsubishi112Heat; case stdAc::opmode_t::kDry: return kMitsubishi112Dry; // Note: No Fan Only mode. default: return kMitsubishi112Auto; } } /// 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 IRMitsubishi112::convertFan(const stdAc::fanspeed_t speed) { switch (speed) { case stdAc::fanspeed_t::kMin: return kMitsubishi112FanMin; case stdAc::fanspeed_t::kLow: return kMitsubishi112FanLow; case stdAc::fanspeed_t::kMedium: return kMitsubishi112FanMed; case stdAc::fanspeed_t::kHigh: case stdAc::fanspeed_t::kMax: return kMitsubishi112FanMax; default: return kMitsubishi112FanMed; } } /// Convert a stdAc::swingv_t enum into it's native setting. /// @param[in] position The enum to be converted. /// @return The native equivalent of the enum. uint8_t IRMitsubishi112::convertSwingV(const stdAc::swingv_t position) { switch (position) { case stdAc::swingv_t::kHighest: return kMitsubishi112SwingVHighest; case stdAc::swingv_t::kHigh: return kMitsubishi112SwingVHigh; case stdAc::swingv_t::kMiddle: return kMitsubishi112SwingVMiddle; case stdAc::swingv_t::kLow: return kMitsubishi112SwingVLow; case stdAc::swingv_t::kLowest: return kMitsubishi112SwingVLowest; default: return kMitsubishi112SwingVAuto; } } /// Convert a stdAc::swingh_t enum into it's native setting. /// @param[in] position The enum to be converted. /// @return The native equivalent of the enum. uint8_t IRMitsubishi112::convertSwingH(const stdAc::swingh_t position) { switch (position) { case stdAc::swingh_t::kLeftMax: return kMitsubishi112SwingHLeftMax; case stdAc::swingh_t::kLeft: return kMitsubishi112SwingHLeft; case stdAc::swingh_t::kMiddle: return kMitsubishi112SwingHMiddle; case stdAc::swingh_t::kRight: return kMitsubishi112SwingHRight; case stdAc::swingh_t::kRightMax: return kMitsubishi112SwingHRightMax; case stdAc::swingh_t::kWide: return kMitsubishi112SwingHWide; case stdAc::swingh_t::kAuto: return kMitsubishi112SwingHAuto; default: return kMitsubishi112SwingHAuto; } } /// 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 IRMitsubishi112::toCommonMode(const uint8_t mode) { switch (mode) { case kMitsubishi112Cool: return stdAc::opmode_t::kCool; case kMitsubishi112Heat: return stdAc::opmode_t::kHeat; case kMitsubishi112Dry: return stdAc::opmode_t::kDry; 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 IRMitsubishi112::toCommonFanSpeed(const uint8_t speed) { switch (speed) { case kMitsubishi112FanMax: return stdAc::fanspeed_t::kMax; case kMitsubishi112FanMed: return stdAc::fanspeed_t::kMedium; case kMitsubishi112FanLow: return stdAc::fanspeed_t::kLow; case kMitsubishi112FanMin: return stdAc::fanspeed_t::kMin; default: return stdAc::fanspeed_t::kMedium; } } /// Convert a native vertical swing postion to it's common equivalent. /// @param[in] pos A native position to convert. /// @return The common vertical swing position. stdAc::swingv_t IRMitsubishi112::toCommonSwingV(const uint8_t pos) { switch (pos) { case kMitsubishi112SwingVHighest: return stdAc::swingv_t::kHighest; case kMitsubishi112SwingVHigh: return stdAc::swingv_t::kHigh; case kMitsubishi112SwingVMiddle: return stdAc::swingv_t::kMiddle; case kMitsubishi112SwingVLow: return stdAc::swingv_t::kLow; case kMitsubishi112SwingVLowest: return stdAc::swingv_t::kLowest; default: return stdAc::swingv_t::kAuto; } } /// Convert a native horizontal swing postion to it's common equivalent. /// @param[in] pos A native position to convert. /// @return The common horizontal swing position. stdAc::swingh_t IRMitsubishi112::toCommonSwingH(const uint8_t pos) { switch (pos) { case kMitsubishi112SwingHLeftMax: return stdAc::swingh_t::kLeftMax; case kMitsubishi112SwingHLeft: return stdAc::swingh_t::kLeft; case kMitsubishi112SwingHMiddle: return stdAc::swingh_t::kMiddle; case kMitsubishi112SwingHRight: return stdAc::swingh_t::kRight; case kMitsubishi112SwingHRightMax: return stdAc::swingh_t::kRightMax; case kMitsubishi112SwingHWide: return stdAc::swingh_t::kWide; default: return stdAc::swingh_t::kAuto; } } /// Convert the current internal state into its stdAc::state_t equivalent. /// @return The stdAc equivalent of the native settings. stdAc::state_t IRMitsubishi112::toCommon(void) const { stdAc::state_t result; result.protocol = decode_type_t::MITSUBISHI112; result.model = -1; // No models used. result.power = _.Power; result.mode = toCommonMode(_.Mode); result.celsius = true; result.degrees = getTemp(); result.fanspeed = toCommonFanSpeed(_.Fan); result.swingv = toCommonSwingV(_.SwingV); result.swingh = toCommonSwingH(_.SwingH);; result.quiet = getQuiet(); // Not supported. result.econo = false; // Need to figure this part from stdAc result.clock = -1; result.sleep = -1; result.turbo = false; result.clean = false; result.filter = false; result.light = false; result.beep = false; return result; } /// Convert the internal state into a human readable string. /// @return A string containing the settings in human-readable form. String IRMitsubishi112::toString(void) const { String result = ""; result.reserve(80); // Reserve some heap for the string to reduce fragging. result += addBoolToString(_.Power, kPowerStr, false); result += addModeToString(_.Mode, kMitsubishi112Auto, kMitsubishi112Cool, kMitsubishi112Heat, kMitsubishi112Dry, kMitsubishi112Auto); result += addTempToString(getTemp()); result += addFanToString(_.Fan, kMitsubishi112FanMax, kMitsubishi112FanLow, kMitsubishi112FanMax, kMitsubishi112FanQuiet, kMitsubishi112FanMed); result += addSwingVToString(_.SwingV, kMitsubishi112SwingVAuto, kMitsubishi112SwingVHighest, kMitsubishi112SwingVHigh, kMitsubishi112SwingVAuto, // Upper Middle unused. kMitsubishi112SwingVMiddle, kMitsubishi112SwingVAuto, // Lower Middle unused. kMitsubishi112SwingVLow, kMitsubishi112SwingVLowest, // Below are unused. kMitsubishi112SwingVAuto, kMitsubishi112SwingVAuto, kMitsubishi112SwingVAuto, kMitsubishi112SwingVAuto); result += addSwingHToString(_.SwingH, kMitsubishi112SwingHAuto, kMitsubishi112SwingHLeftMax, kMitsubishi112SwingHLeft, kMitsubishi112SwingHMiddle, kMitsubishi112SwingHRight, kMitsubishi112SwingHRightMax, kMitsubishi112SwingHAuto, // Unused kMitsubishi112SwingHAuto, // Unused kMitsubishi112SwingHAuto, // Unused kMitsubishi112SwingHAuto, // Unused kMitsubishi112SwingHWide); result += addBoolToString(getQuiet(), kQuietStr); return result; }