// Copyright 2019 David Conran /// @file /// @brief Support for Mitsubishi Heavy Industry protocols. /// Code to emulate Mitsubishi Heavy Industries A/C IR remote control units. /// @note This code was *heavily* influenced by ToniA's great work & code, /// but it has been written from scratch. /// Nothing was copied other than constants and message analysis. /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/660 /// @see https://github.com/ToniA/Raw-IR-decoder-for-Arduino/blob/master/MitsubishiHeavy.cpp /// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/MitsubishiHeavyHeatpumpIR.cpp #include "ir_MitsubishiHeavy.h" #include #include #include "IRremoteESP8266.h" #include "IRtext.h" #include "IRutils.h" #ifndef ARDUINO #include #endif // Constants const uint16_t kMitsubishiHeavyHdrMark = 3140; const uint16_t kMitsubishiHeavyHdrSpace = 1630; const uint16_t kMitsubishiHeavyBitMark = 370; const uint16_t kMitsubishiHeavyOneSpace = 420; const uint16_t kMitsubishiHeavyZeroSpace = 1220; const uint32_t kMitsubishiHeavyGap = kDefaultMessageGap; // Just a guess. using irutils::addBoolToString; using irutils::addIntToString; using irutils::addLabeledString; using irutils::addModeToString; using irutils::addSwingHToString; using irutils::addSwingVToString; using irutils::addTempToString; using irutils::checkInvertedBytePairs; using irutils::invertBytePairs; #if SEND_MITSUBISHIHEAVY /// Send a MitsubishiHeavy 88-bit A/C message. /// Status: BETA / Appears to be working. Needs testing 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. void IRsend::sendMitsubishiHeavy88(const unsigned char data[], const uint16_t nbytes, const uint16_t repeat) { if (nbytes < kMitsubishiHeavy88StateLength) return; // Not enough bytes to send a proper message. sendGeneric(kMitsubishiHeavyHdrMark, kMitsubishiHeavyHdrSpace, kMitsubishiHeavyBitMark, kMitsubishiHeavyOneSpace, kMitsubishiHeavyBitMark, kMitsubishiHeavyZeroSpace, kMitsubishiHeavyBitMark, kMitsubishiHeavyGap, data, nbytes, 38000, false, repeat, kDutyDefault); } /// Send a MitsubishiHeavy 152-bit A/C message. /// Status: BETA / Appears to be working. Needs testing 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. void IRsend::sendMitsubishiHeavy152(const unsigned char data[], const uint16_t nbytes, const uint16_t repeat) { if (nbytes < kMitsubishiHeavy152StateLength) return; // Not enough bytes to send a proper message. sendMitsubishiHeavy88(data, nbytes, repeat); } #endif // SEND_MITSUBISHIHEAVY // Class for decoding and constructing MitsubishiHeavy152 AC messages. /// 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? IRMitsubishiHeavy152Ac::IRMitsubishiHeavy152Ac(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 IRMitsubishiHeavy152Ac::begin(void) { _irsend.begin(); } #if SEND_MITSUBISHIHEAVY /// Send the current internal state as an IR message. /// @param[in] repeat Nr. of times the message will be repeated. void IRMitsubishiHeavy152Ac::send(const uint16_t repeat) { _irsend.sendMitsubishiHeavy152(getRaw(), kMitsubishiHeavy152StateLength, repeat); } #endif // SEND_MITSUBISHIHEAVY /// Reset the state of the remote to a known good state/sequence. void IRMitsubishiHeavy152Ac::stateReset(void) { std::memcpy(_.raw, kMitsubishiHeavyZmsSig, kMitsubishiHeavySigLength); for (uint8_t i = kMitsubishiHeavySigLength; i < kMitsubishiHeavy152StateLength - 3; i += 2) _.raw[i] = 0; _.raw[17] = 0x80; } /// 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 *IRMitsubishiHeavy152Ac::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 IRMitsubishiHeavy152Ac::setRaw(const uint8_t *data) { std::memcpy(_.raw, data, kMitsubishiHeavy152StateLength); } /// Set the requested power state of the A/C to on. void IRMitsubishiHeavy152Ac::on(void) { setPower(true); } /// Set the requested power state of the A/C to off. void IRMitsubishiHeavy152Ac::off(void) { setPower(false); } /// Change the power setting. /// @param[in] on true, the setting is on. false, the setting is off. void IRMitsubishiHeavy152Ac::setPower(const bool on) { _.Power = on; } /// Get the value of the current power setting. /// @return true, the setting is on. false, the setting is off. bool IRMitsubishiHeavy152Ac::getPower(void) const { return _.Power; } /// Set the temperature. /// @param[in] temp The temperature in degrees celsius. void IRMitsubishiHeavy152Ac::setTemp(const uint8_t temp) { uint8_t newtemp = temp; newtemp = std::min(newtemp, kMitsubishiHeavyMaxTemp); newtemp = std::max(newtemp, kMitsubishiHeavyMinTemp); _.Temp = newtemp - kMitsubishiHeavyMinTemp; } /// Get the current temperature setting. /// @return The current setting for temp. in degrees celsius. uint8_t IRMitsubishiHeavy152Ac::getTemp(void) const { return _.Temp + kMitsubishiHeavyMinTemp; } /// Set the speed of the fan. /// @param[in] speed The desired setting. void IRMitsubishiHeavy152Ac::setFan(const uint8_t speed) { uint8_t newspeed = speed; switch (speed) { case kMitsubishiHeavy152FanLow: case kMitsubishiHeavy152FanMed: case kMitsubishiHeavy152FanHigh: case kMitsubishiHeavy152FanMax: case kMitsubishiHeavy152FanEcono: case kMitsubishiHeavy152FanTurbo: break; default: newspeed = kMitsubishiHeavy152FanAuto; } _.Fan = newspeed; } /// Get the current fan speed setting. /// @return The current fan speed/mode. uint8_t IRMitsubishiHeavy152Ac::getFan(void) const { return _.Fan; } /// Set the operating mode of the A/C. /// @param[in] mode The desired operating mode. void IRMitsubishiHeavy152Ac::setMode(const uint8_t mode) { uint8_t newmode = mode; switch (mode) { case kMitsubishiHeavyCool: case kMitsubishiHeavyDry: case kMitsubishiHeavyFan: case kMitsubishiHeavyHeat: break; default: newmode = kMitsubishiHeavyAuto; } _.Mode = newmode; } /// Get the operating mode setting of the A/C. /// @return The current operating mode setting. uint8_t IRMitsubishiHeavy152Ac::getMode(void) const { return _.Mode; } /// Set the Vertical Swing mode of the A/C. /// @param[in] pos The position/mode to set the swing to. void IRMitsubishiHeavy152Ac::setSwingVertical(const uint8_t pos) { _.SwingV = std::min(pos, kMitsubishiHeavy152SwingVOff); } /// Get the Vertical Swing mode of the A/C. /// @return The native position/mode setting. uint8_t IRMitsubishiHeavy152Ac::getSwingVertical(void) const { return _.SwingV; } /// Set the Horizontal Swing mode of the A/C. /// @param[in] pos The position/mode to set the swing to. void IRMitsubishiHeavy152Ac::setSwingHorizontal(const uint8_t pos) { _.SwingH = std::min(pos, kMitsubishiHeavy152SwingHOff); } /// Get the Horizontal Swing mode of the A/C. /// @return The native position/mode setting. uint8_t IRMitsubishiHeavy152Ac::getSwingHorizontal(void) const { return _.SwingH; } /// Set the Night (Sleep) mode of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. void IRMitsubishiHeavy152Ac::setNight(const bool on) { _.Night = on; } /// Get the Night (Sleep) mode of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRMitsubishiHeavy152Ac::getNight(void) const { return _.Night; } /// Set the 3D mode of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. void IRMitsubishiHeavy152Ac::set3D(const bool on) { if (on) { _.Three = 1; _.D = 1; } else { _.Three = 0; _.D = 0; } } /// Get the 3D mode of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRMitsubishiHeavy152Ac::get3D(void) const { return _.Three && _.D; } /// Set the Silent (Quiet) mode of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. void IRMitsubishiHeavy152Ac::setSilent(const bool on) { _.Silent = on; } /// Get the Silent (Quiet) mode of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRMitsubishiHeavy152Ac::getSilent(void) const { return _.Silent; } /// Set the Filter mode of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. void IRMitsubishiHeavy152Ac::setFilter(const bool on) { _.Filter = on; } /// Get the Filter mode of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRMitsubishiHeavy152Ac::getFilter(void) const { return _.Filter; } /// Set the Clean mode of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. void IRMitsubishiHeavy152Ac::setClean(const bool on) { _.Filter = on; _.Clean = on; } /// Get the Clean mode of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRMitsubishiHeavy152Ac::getClean(void) const { return _.Clean && _.Filter; } /// Set the Turbo mode of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. void IRMitsubishiHeavy152Ac::setTurbo(const bool on) { if (on) setFan(kMitsubishiHeavy152FanTurbo); else if (getTurbo()) setFan(kMitsubishiHeavy152FanAuto); } /// Get the Turbo mode of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRMitsubishiHeavy152Ac::getTurbo(void) const { return _.Fan == kMitsubishiHeavy152FanTurbo; } /// Set the Economical mode of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. void IRMitsubishiHeavy152Ac::setEcono(const bool on) { if (on) setFan(kMitsubishiHeavy152FanEcono); else if (getEcono()) setFan(kMitsubishiHeavy152FanAuto); } /// Get the Economical mode of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRMitsubishiHeavy152Ac::getEcono(void) const { return _.Fan == kMitsubishiHeavy152FanEcono; } /// Verify the given state has a ZM-S signature. /// @param[in] state A ptr to a state to be checked. /// @return true, the check passed. Otherwise, false. bool IRMitsubishiHeavy152Ac::checkZmsSig(const uint8_t *state) { for (uint8_t i = 0; i < kMitsubishiHeavySigLength; i++) if (state[i] != kMitsubishiHeavyZmsSig[i]) return false; return true; } /// Calculate the checksum for the current internal state of the remote. /// Note: Technically it has no checksum, but does have inverted byte pairs. void IRMitsubishiHeavy152Ac::checksum(void) { const uint8_t kOffset = kMitsubishiHeavySigLength - 2; invertBytePairs(_.raw + kOffset, kMitsubishiHeavy152StateLength - kOffset); } /// 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 state array. /// @return true, if the state has a valid checksum. Otherwise, false. /// Note: Technically it has no checksum, but does have inverted byte pairs. bool IRMitsubishiHeavy152Ac::validChecksum(const uint8_t *state, const uint16_t length) { // Assume anything too short is fine. if (length < kMitsubishiHeavySigLength) return true; const uint8_t kOffset = kMitsubishiHeavySigLength - 2; return checkInvertedBytePairs(state + kOffset, length - kOffset); } /// 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 IRMitsubishiHeavy152Ac::convertMode(const stdAc::opmode_t mode) { switch (mode) { case stdAc::opmode_t::kCool: return kMitsubishiHeavyCool; case stdAc::opmode_t::kHeat: return kMitsubishiHeavyHeat; case stdAc::opmode_t::kDry: return kMitsubishiHeavyDry; case stdAc::opmode_t::kFan: return kMitsubishiHeavyFan; default: return kMitsubishiHeavyAuto; } } /// 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 IRMitsubishiHeavy152Ac::convertFan(const stdAc::fanspeed_t speed) { switch (speed) { // Assumes Econo is slower than Low. case stdAc::fanspeed_t::kMin: return kMitsubishiHeavy152FanEcono; case stdAc::fanspeed_t::kLow: return kMitsubishiHeavy152FanLow; case stdAc::fanspeed_t::kMedium: return kMitsubishiHeavy152FanMed; case stdAc::fanspeed_t::kHigh: return kMitsubishiHeavy152FanHigh; case stdAc::fanspeed_t::kMax: return kMitsubishiHeavy152FanMax; default: return kMitsubishiHeavy152FanAuto; } } /// 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 IRMitsubishiHeavy152Ac::convertSwingV(const stdAc::swingv_t position) { switch (position) { case stdAc::swingv_t::kAuto: return kMitsubishiHeavy152SwingVAuto; case stdAc::swingv_t::kHighest: return kMitsubishiHeavy152SwingVHighest; case stdAc::swingv_t::kHigh: return kMitsubishiHeavy152SwingVHigh; case stdAc::swingv_t::kMiddle: return kMitsubishiHeavy152SwingVMiddle; case stdAc::swingv_t::kLow: return kMitsubishiHeavy152SwingVLow; case stdAc::swingv_t::kLowest: return kMitsubishiHeavy152SwingVLowest; default: return kMitsubishiHeavy152SwingVOff; } } /// 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 IRMitsubishiHeavy152Ac::convertSwingH(const stdAc::swingh_t position) { switch (position) { case stdAc::swingh_t::kAuto: return kMitsubishiHeavy152SwingHAuto; case stdAc::swingh_t::kLeftMax: return kMitsubishiHeavy152SwingHLeftMax; case stdAc::swingh_t::kLeft: return kMitsubishiHeavy152SwingHLeft; case stdAc::swingh_t::kMiddle: return kMitsubishiHeavy152SwingHMiddle; case stdAc::swingh_t::kRight: return kMitsubishiHeavy152SwingHRight; case stdAc::swingh_t::kRightMax: return kMitsubishiHeavy152SwingHRightMax; default: return kMitsubishiHeavy152SwingHOff; } } /// 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 IRMitsubishiHeavy152Ac::toCommonMode(const uint8_t mode) { switch (mode) { case kMitsubishiHeavyCool: return stdAc::opmode_t::kCool; case kMitsubishiHeavyHeat: return stdAc::opmode_t::kHeat; case kMitsubishiHeavyDry: return stdAc::opmode_t::kDry; case kMitsubishiHeavyFan: return stdAc::opmode_t::kFan; default: return stdAc::opmode_t::kAuto; } } /// Convert a native fan speed into its stdAc equivalent. /// @param[in] spd The native setting to be converted. /// @return The stdAc equivalent of the native setting. stdAc::fanspeed_t IRMitsubishiHeavy152Ac::toCommonFanSpeed(const uint8_t spd) { switch (spd) { case kMitsubishiHeavy152FanMax: return stdAc::fanspeed_t::kMax; case kMitsubishiHeavy152FanHigh: return stdAc::fanspeed_t::kHigh; case kMitsubishiHeavy152FanMed: return stdAc::fanspeed_t::kMedium; case kMitsubishiHeavy152FanLow: return stdAc::fanspeed_t::kLow; case kMitsubishiHeavy152FanEcono: return stdAc::fanspeed_t::kMin; default: return stdAc::fanspeed_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 IRMitsubishiHeavy152Ac::toCommonSwingH(const uint8_t pos) { switch (pos) { case kMitsubishiHeavy152SwingHLeftMax: return stdAc::swingh_t::kLeftMax; case kMitsubishiHeavy152SwingHLeft: return stdAc::swingh_t::kLeft; case kMitsubishiHeavy152SwingHMiddle: return stdAc::swingh_t::kMiddle; case kMitsubishiHeavy152SwingHRight: return stdAc::swingh_t::kRight; case kMitsubishiHeavy152SwingHRightMax: return stdAc::swingh_t::kRightMax; case kMitsubishiHeavy152SwingHOff: return stdAc::swingh_t::kOff; default: return stdAc::swingh_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. stdAc::swingv_t IRMitsubishiHeavy152Ac::toCommonSwingV(const uint8_t pos) { switch (pos) { case kMitsubishiHeavy152SwingVHighest: return stdAc::swingv_t::kHighest; case kMitsubishiHeavy152SwingVHigh: return stdAc::swingv_t::kHigh; case kMitsubishiHeavy152SwingVMiddle: return stdAc::swingv_t::kMiddle; case kMitsubishiHeavy152SwingVLow: return stdAc::swingv_t::kLow; case kMitsubishiHeavy152SwingVLowest: return stdAc::swingv_t::kLowest; case kMitsubishiHeavy152SwingVOff: return stdAc::swingv_t::kOff; 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 IRMitsubishiHeavy152Ac::toCommon(void) const { stdAc::state_t result; result.protocol = decode_type_t::MITSUBISHI_HEAVY_152; 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.turbo = getTurbo(); result.econo = getEcono(); result.clean = getClean(); result.quiet = _.Silent; result.filter = _.Filter; result.sleep = _.Night ? 0 : -1; // Not supported. result.light = false; result.beep = false; 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 IRMitsubishiHeavy152Ac::toString(void) const { String result = ""; result.reserve(180); // Reserve some heap for the string to reduce fragging. result += addBoolToString(_.Power, kPowerStr, false); result += addModeToString(_.Mode, kMitsubishiHeavyAuto, kMitsubishiHeavyCool, kMitsubishiHeavyHeat, kMitsubishiHeavyDry, kMitsubishiHeavyFan); result += addTempToString(getTemp()); result += addIntToString(_.Fan, kFanStr); result += kSpaceLBraceStr; switch (_.Fan) { case kMitsubishiHeavy152FanAuto: result += kAutoStr; break; case kMitsubishiHeavy152FanHigh: result += kHighStr; break; case kMitsubishiHeavy152FanLow: result += kLowStr; break; case kMitsubishiHeavy152FanMed: result += kMedStr; break; case kMitsubishiHeavy152FanMax: result += kMaxStr; break; case kMitsubishiHeavy152FanEcono: result += kEconoStr; break; case kMitsubishiHeavy152FanTurbo: result += kTurboStr; break; default: result += kUnknownStr; } result += ')'; result += addSwingVToString(_.SwingV, kMitsubishiHeavy152SwingVAuto, kMitsubishiHeavy152SwingVHighest, kMitsubishiHeavy152SwingVHigh, kMitsubishiHeavy152SwingVAuto, // UpperMid unused kMitsubishiHeavy152SwingVMiddle, kMitsubishiHeavy152SwingVAuto, // LowerMid unused kMitsubishiHeavy152SwingVLow, kMitsubishiHeavy152SwingVLowest, kMitsubishiHeavy152SwingVOff, // Below are unused. kMitsubishiHeavy152SwingVAuto, kMitsubishiHeavy152SwingVAuto, kMitsubishiHeavy152SwingVAuto); result += addSwingHToString(_.SwingH, kMitsubishiHeavy152SwingHAuto, kMitsubishiHeavy152SwingHLeftMax, kMitsubishiHeavy152SwingHLeft, kMitsubishiHeavy152SwingHMiddle, kMitsubishiHeavy152SwingHRight, kMitsubishiHeavy152SwingHRightMax, kMitsubishiHeavy152SwingHOff, kMitsubishiHeavy152SwingHLeftRight, kMitsubishiHeavy152SwingHRightLeft, // Below are unused. kMitsubishiHeavy152SwingHAuto, kMitsubishiHeavy152SwingHAuto); result += addBoolToString(_.Silent, kSilentStr); result += addBoolToString(getTurbo(), kTurboStr); result += addBoolToString(getEcono(), kEconoStr); result += addBoolToString(_.Night, kNightStr); result += addBoolToString(_.Filter, kFilterStr); result += addBoolToString(get3D(), k3DStr); result += addBoolToString(getClean(), kCleanStr); return result; } // Class for decoding and constructing MitsubishiHeavy88 AC messages. /// 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? IRMitsubishiHeavy88Ac::IRMitsubishiHeavy88Ac(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 IRMitsubishiHeavy88Ac::begin(void) { _irsend.begin(); } #if SEND_MITSUBISHIHEAVY /// Send the current internal state as an IR message. /// @param[in] repeat Nr. of times the message will be repeated. void IRMitsubishiHeavy88Ac::send(const uint16_t repeat) { _irsend.sendMitsubishiHeavy88(getRaw(), kMitsubishiHeavy88StateLength, repeat); } #endif // SEND_MITSUBISHIHEAVY /// Reset the state of the remote to a known good state/sequence. void IRMitsubishiHeavy88Ac::stateReset(void) { std::memcpy(_.raw, kMitsubishiHeavyZjsSig, kMitsubishiHeavySigLength); for (uint8_t i = kMitsubishiHeavySigLength; i < kMitsubishiHeavy88StateLength; i++) _.raw[i] = 0; } /// 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 *IRMitsubishiHeavy88Ac::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 IRMitsubishiHeavy88Ac::setRaw(const uint8_t *data) { std::memcpy(_.raw, data, kMitsubishiHeavy88StateLength); } /// Set the requested power state of the A/C to on. void IRMitsubishiHeavy88Ac::on(void) { setPower(true); } /// Set the requested power state of the A/C to off. void IRMitsubishiHeavy88Ac::off(void) { setPower(false); } /// Change the power setting. /// @param[in] on true, the setting is on. false, the setting is off. void IRMitsubishiHeavy88Ac::setPower(const bool on) { _.Power = on; } /// Get the value of the current power setting. /// @return true, the setting is on. false, the setting is off. bool IRMitsubishiHeavy88Ac::getPower(void) const { return _.Power; } /// Set the temperature. /// @param[in] temp The temperature in degrees celsius. void IRMitsubishiHeavy88Ac::setTemp(const uint8_t temp) { uint8_t newtemp = temp; newtemp = std::min(newtemp, kMitsubishiHeavyMaxTemp); newtemp = std::max(newtemp, kMitsubishiHeavyMinTemp); _.Temp = newtemp - kMitsubishiHeavyMinTemp; } /// Get the current temperature setting. /// @return The current setting for temp. in degrees celsius. uint8_t IRMitsubishiHeavy88Ac::getTemp(void) const { return _.Temp + kMitsubishiHeavyMinTemp; } /// Set the speed of the fan. /// @param[in] speed The desired setting. void IRMitsubishiHeavy88Ac::setFan(const uint8_t speed) { uint8_t newspeed = speed; switch (speed) { case kMitsubishiHeavy88FanLow: case kMitsubishiHeavy88FanMed: case kMitsubishiHeavy88FanHigh: case kMitsubishiHeavy88FanTurbo: case kMitsubishiHeavy88FanEcono: break; default: newspeed = kMitsubishiHeavy88FanAuto; } _.Fan = newspeed; } /// Get the current fan speed setting. /// @return The current fan speed/mode. uint8_t IRMitsubishiHeavy88Ac::getFan(void) const { return _.Fan; } /// Set the operating mode of the A/C. /// @param[in] mode The desired operating mode. void IRMitsubishiHeavy88Ac::setMode(const uint8_t mode) { uint8_t newmode = mode; switch (mode) { case kMitsubishiHeavyCool: case kMitsubishiHeavyDry: case kMitsubishiHeavyFan: case kMitsubishiHeavyHeat: break; default: newmode = kMitsubishiHeavyAuto; } _.Mode = newmode; } /// Get the operating mode setting of the A/C. /// @return The current operating mode setting. uint8_t IRMitsubishiHeavy88Ac::getMode(void) const { return _.Mode; } /// Set the Vertical Swing mode of the A/C. /// @param[in] pos The position/mode to set the swing to. void IRMitsubishiHeavy88Ac::setSwingVertical(const uint8_t pos) { uint8_t newpos; switch (pos) { case kMitsubishiHeavy88SwingVAuto: case kMitsubishiHeavy88SwingVHighest: case kMitsubishiHeavy88SwingVHigh: case kMitsubishiHeavy88SwingVMiddle: case kMitsubishiHeavy88SwingVLow: case kMitsubishiHeavy88SwingVLowest: newpos = pos; break; default: newpos = kMitsubishiHeavy88SwingVOff; } _.SwingV5 = newpos; _.SwingV7 = (newpos >> kMitsubishiHeavy88SwingVByte5Size); } /// Get the Vertical Swing mode of the A/C. /// @return The native position/mode setting. uint8_t IRMitsubishiHeavy88Ac::getSwingVertical(void) const { return _.SwingV5 | (_.SwingV7 << kMitsubishiHeavy88SwingVByte5Size); } /// Set the Horizontal Swing mode of the A/C. /// @param[in] pos The position/mode to set the swing to. void IRMitsubishiHeavy88Ac::setSwingHorizontal(const uint8_t pos) { uint8_t newpos; switch (pos) { case kMitsubishiHeavy88SwingHAuto: case kMitsubishiHeavy88SwingHLeftMax: case kMitsubishiHeavy88SwingHLeft: case kMitsubishiHeavy88SwingHMiddle: case kMitsubishiHeavy88SwingHRight: case kMitsubishiHeavy88SwingHRightMax: case kMitsubishiHeavy88SwingHLeftRight: case kMitsubishiHeavy88SwingHRightLeft: case kMitsubishiHeavy88SwingH3D: newpos = pos; break; default: newpos = kMitsubishiHeavy88SwingHOff; } _.SwingH1 = newpos; _.SwingH2 = (newpos >> kMitsubishiHeavy88SwingHSize); } /// Get the Horizontal Swing mode of the A/C. /// @return The native position/mode setting. uint8_t IRMitsubishiHeavy88Ac::getSwingHorizontal(void) const { return _.SwingH1 | (_.SwingH2 << kMitsubishiHeavy88SwingHSize); } /// Set the Turbo mode of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. void IRMitsubishiHeavy88Ac::setTurbo(const bool on) { if (on) setFan(kMitsubishiHeavy88FanTurbo); else if (getTurbo()) setFan(kMitsubishiHeavy88FanAuto); } /// Get the Turbo mode of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRMitsubishiHeavy88Ac::getTurbo(void) const { return _.Fan == kMitsubishiHeavy88FanTurbo; } /// Set the Economical mode of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. void IRMitsubishiHeavy88Ac::setEcono(const bool on) { if (on) setFan(kMitsubishiHeavy88FanEcono); else if (getEcono()) setFan(kMitsubishiHeavy88FanAuto); } /// Get the Economical mode of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRMitsubishiHeavy88Ac::getEcono(void) const { return _.Fan == kMitsubishiHeavy88FanEcono; } /// Set the 3D mode of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. void IRMitsubishiHeavy88Ac::set3D(const bool on) { if (on) setSwingHorizontal(kMitsubishiHeavy88SwingH3D); else if (get3D()) setSwingHorizontal(kMitsubishiHeavy88SwingHOff); } /// Get the 3D mode of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRMitsubishiHeavy88Ac::get3D(void) const { return getSwingHorizontal() == kMitsubishiHeavy88SwingH3D; } /// Set the Clean mode of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. void IRMitsubishiHeavy88Ac::setClean(const bool on) { _.Clean = on; } /// Get the Clean mode of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRMitsubishiHeavy88Ac::getClean(void) const { return _.Clean; } /// Verify the given state has a ZJ-S signature. /// @param[in] state A ptr to a state to be checked. /// @return true, the check passed. Otherwise, false. bool IRMitsubishiHeavy88Ac::checkZjsSig(const uint8_t *state) { for (uint8_t i = 0; i < kMitsubishiHeavySigLength; i++) if (state[i] != kMitsubishiHeavyZjsSig[i]) return false; return true; } /// Calculate the checksum for the current internal state of the remote. /// Note: Technically it has no checksum, but does have inverted byte pairs. void IRMitsubishiHeavy88Ac::checksum(void) { const uint8_t kOffset = kMitsubishiHeavySigLength - 2; invertBytePairs(_.raw + kOffset, kMitsubishiHeavy88StateLength - kOffset); } /// 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 state array. /// @return true, if the state has a valid checksum. Otherwise, false. /// Note: Technically it has no checksum, but does have inverted byte pairs. bool IRMitsubishiHeavy88Ac::validChecksum(const uint8_t *state, const uint16_t length) { return IRMitsubishiHeavy152Ac::validChecksum(state, length); } /// 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 IRMitsubishiHeavy88Ac::convertMode(const stdAc::opmode_t mode) { return IRMitsubishiHeavy152Ac::convertMode(mode); } /// 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 IRMitsubishiHeavy88Ac::convertFan(const stdAc::fanspeed_t speed) { switch (speed) { // Assumes Econo is slower than Low. case stdAc::fanspeed_t::kMin: return kMitsubishiHeavy88FanEcono; case stdAc::fanspeed_t::kLow: return kMitsubishiHeavy88FanLow; case stdAc::fanspeed_t::kMedium: return kMitsubishiHeavy88FanMed; case stdAc::fanspeed_t::kHigh: return kMitsubishiHeavy88FanHigh; case stdAc::fanspeed_t::kMax: return kMitsubishiHeavy88FanTurbo; default: return kMitsubishiHeavy88FanAuto; } } /// 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 IRMitsubishiHeavy88Ac::convertSwingV(const stdAc::swingv_t position) { switch (position) { case stdAc::swingv_t::kAuto: return kMitsubishiHeavy88SwingVAuto; case stdAc::swingv_t::kHighest: return kMitsubishiHeavy88SwingVHighest; case stdAc::swingv_t::kHigh: return kMitsubishiHeavy88SwingVHigh; case stdAc::swingv_t::kMiddle: return kMitsubishiHeavy88SwingVMiddle; case stdAc::swingv_t::kLow: return kMitsubishiHeavy88SwingVLow; case stdAc::swingv_t::kLowest: return kMitsubishiHeavy88SwingVLowest; default: return kMitsubishiHeavy88SwingVOff; } } /// 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 IRMitsubishiHeavy88Ac::convertSwingH(const stdAc::swingh_t position) { switch (position) { case stdAc::swingh_t::kAuto: return kMitsubishiHeavy88SwingHAuto; case stdAc::swingh_t::kLeftMax: return kMitsubishiHeavy88SwingHLeftMax; case stdAc::swingh_t::kLeft: return kMitsubishiHeavy88SwingHLeft; case stdAc::swingh_t::kMiddle: return kMitsubishiHeavy88SwingHMiddle; case stdAc::swingh_t::kRight: return kMitsubishiHeavy88SwingHRight; case stdAc::swingh_t::kRightMax: return kMitsubishiHeavy88SwingHRightMax; default: return kMitsubishiHeavy88SwingHOff; } } /// 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 IRMitsubishiHeavy88Ac::toCommonFanSpeed(const uint8_t speed) { switch (speed) { case kMitsubishiHeavy88FanTurbo: return stdAc::fanspeed_t::kMax; case kMitsubishiHeavy88FanHigh: return stdAc::fanspeed_t::kHigh; case kMitsubishiHeavy88FanMed: return stdAc::fanspeed_t::kMedium; case kMitsubishiHeavy88FanLow: return stdAc::fanspeed_t::kLow; case kMitsubishiHeavy88FanEcono: return stdAc::fanspeed_t::kMin; default: return stdAc::fanspeed_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 IRMitsubishiHeavy88Ac::toCommonSwingH(const uint8_t pos) { switch (pos) { case kMitsubishiHeavy88SwingHLeftMax: return stdAc::swingh_t::kLeftMax; case kMitsubishiHeavy88SwingHLeft: return stdAc::swingh_t::kLeft; case kMitsubishiHeavy88SwingHMiddle: return stdAc::swingh_t::kMiddle; case kMitsubishiHeavy88SwingHRight: return stdAc::swingh_t::kRight; case kMitsubishiHeavy88SwingHRightMax: return stdAc::swingh_t::kRightMax; case kMitsubishiHeavy88SwingHOff: return stdAc::swingh_t::kOff; default: return stdAc::swingh_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. stdAc::swingv_t IRMitsubishiHeavy88Ac::toCommonSwingV(const uint8_t pos) { switch (pos) { case kMitsubishiHeavy88SwingVHighest: return stdAc::swingv_t::kHighest; case kMitsubishiHeavy88SwingVHigh: return stdAc::swingv_t::kHigh; case kMitsubishiHeavy88SwingVMiddle: return stdAc::swingv_t::kMiddle; case kMitsubishiHeavy88SwingVLow: return stdAc::swingv_t::kLow; case kMitsubishiHeavy88SwingVLowest: return stdAc::swingv_t::kLowest; case kMitsubishiHeavy88SwingVOff: return stdAc::swingv_t::kOff; 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 IRMitsubishiHeavy88Ac::toCommon(void) const { stdAc::state_t result; result.protocol = decode_type_t::MITSUBISHI_HEAVY_88; result.model = -1; // No models used. result.power = _.Power; result.mode = IRMitsubishiHeavy152Ac::toCommonMode(_.Mode); result.celsius = true; result.degrees = getTemp(); result.fanspeed = toCommonFanSpeed(_.Fan); result.swingv = toCommonSwingV(getSwingVertical()); result.swingh = toCommonSwingH(getSwingHorizontal()); result.turbo = getTurbo(); result.econo = getEcono(); result.clean = _.Clean; // Not supported. result.quiet = 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 IRMitsubishiHeavy88Ac::toString(void) const { String result = ""; result.reserve(140); // Reserve some heap for the string to reduce fragging. result += addBoolToString(_.Power, kPowerStr, false); result += addModeToString(_.Mode, kMitsubishiHeavyAuto, kMitsubishiHeavyCool, kMitsubishiHeavyHeat, kMitsubishiHeavyDry, kMitsubishiHeavyFan); result += addTempToString(getTemp()); result += addIntToString(_.Fan, kFanStr); result += kSpaceLBraceStr; switch (_.Fan) { case kMitsubishiHeavy88FanAuto: result += kAutoStr; break; case kMitsubishiHeavy88FanHigh: result += kHighStr; break; case kMitsubishiHeavy88FanLow: result += kLowStr; break; case kMitsubishiHeavy88FanMed: result += kMedStr; break; case kMitsubishiHeavy88FanEcono: result += kEconoStr; break; case kMitsubishiHeavy88FanTurbo: result += kTurboStr; break; default: result += kUnknownStr; } result += ')'; result += addSwingVToString(getSwingVertical(), kMitsubishiHeavy88SwingVAuto, kMitsubishiHeavy88SwingVHighest, kMitsubishiHeavy88SwingVHigh, kMitsubishiHeavy88SwingVAuto, // UpperMid unused kMitsubishiHeavy88SwingVMiddle, kMitsubishiHeavy88SwingVAuto, // LowerMid unused kMitsubishiHeavy88SwingVLow, kMitsubishiHeavy88SwingVLowest, kMitsubishiHeavy88SwingVOff, // Below are unused. kMitsubishiHeavy88SwingVAuto, kMitsubishiHeavy88SwingVAuto, kMitsubishiHeavy88SwingVAuto); result += addSwingHToString(getSwingHorizontal(), kMitsubishiHeavy88SwingHAuto, kMitsubishiHeavy88SwingHLeftMax, kMitsubishiHeavy88SwingHLeft, kMitsubishiHeavy88SwingHMiddle, kMitsubishiHeavy88SwingHRight, kMitsubishiHeavy88SwingHRightMax, kMitsubishiHeavy88SwingHOff, kMitsubishiHeavy88SwingHLeftRight, kMitsubishiHeavy88SwingHRightLeft, kMitsubishiHeavy88SwingH3D, // Below are unused. kMitsubishiHeavy88SwingHAuto); result += addBoolToString(getTurbo(), kTurboStr); result += addBoolToString(getEcono(), kEconoStr); result += addBoolToString(get3D(), k3DStr); result += addBoolToString(_.Clean, kCleanStr); return result; } #if DECODE_MITSUBISHIHEAVY /// Decode the supplied Mitsubishi Heavy Industries A/C message. /// Status: BETA / Appears to be working. Needs testing against a real device. /// @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. /// Typically kMitsubishiHeavy88Bits or kMitsubishiHeavy152Bits (def). /// @param[in] strict Flag indicating if we should perform strict matching. /// @return True if it can decode it, false if it can't. bool IRrecv::decodeMitsubishiHeavy(decode_results* results, uint16_t offset, const uint16_t nbits, const bool strict) { if (strict) { switch (nbits) { case kMitsubishiHeavy88Bits: case kMitsubishiHeavy152Bits: break; default: return false; // Not what is expected } } uint16_t used; used = matchGeneric(results->rawbuf + offset, results->state, results->rawlen - offset, nbits, kMitsubishiHeavyHdrMark, kMitsubishiHeavyHdrSpace, kMitsubishiHeavyBitMark, kMitsubishiHeavyOneSpace, kMitsubishiHeavyBitMark, kMitsubishiHeavyZeroSpace, kMitsubishiHeavyBitMark, kMitsubishiHeavyGap, true, _tolerance, 0, false); if (used == 0) return false; offset += used; // Compliance switch (nbits) { case kMitsubishiHeavy88Bits: if (strict && !(IRMitsubishiHeavy88Ac::checkZjsSig(results->state) && IRMitsubishiHeavy88Ac::validChecksum(results->state))) return false; results->decode_type = MITSUBISHI_HEAVY_88; break; case kMitsubishiHeavy152Bits: if (strict && !(IRMitsubishiHeavy152Ac::checkZmsSig(results->state) && IRMitsubishiHeavy152Ac::validChecksum(results->state))) return false; results->decode_type = MITSUBISHI_HEAVY_152; break; default: return false; } // Success 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_MITSUBISHIHEAVY