// Copyright 2015 Kristian Lauszus // Copyright 2017, 2018 David Conran // Panasonic devices #include "ir_Panasonic.h" #include #ifndef ARDUINO #include #endif #include "IRrecv.h" #include "IRsend.h" #include "IRutils.h" // Panasonic protocol originally added by Kristian Lauszus from: // https://github.com/z3t0/Arduino-IRremote // (Thanks to zenwheel and other people at the original blog post) // // Panasonic A/C support add by crankyoldgit but heavily influenced by: // https://github.com/ToniA/ESPEasy/blob/HeatpumpIR/lib/HeatpumpIR/PanasonicHeatpumpIR.cpp // Panasonic A/C Clock & Timer support: // Reverse Engineering by MikkelTb // Code by crankyoldgit // Panasonic A/C models supported: // A/C Series/models: // JKE, LKE, DKE, CKP, RKR, & NKE series. (In theory) // CS-YW9MKD, CS-Z9RKR (confirmed) // CS-ME14CKPG / CS-ME12CKPG / CS-ME10CKPG // A/C Remotes: // A75C3747 (confirmed) // A75C3704 // A75C2311 (CKP) // Constants // Ref: // http://www.remotecentral.com/cgi-bin/mboard/rc-pronto/thread.cgi?26152 const uint16_t kPanasonicTick = 432; const uint16_t kPanasonicHdrMarkTicks = 8; const uint16_t kPanasonicHdrMark = kPanasonicHdrMarkTicks * kPanasonicTick; const uint16_t kPanasonicHdrSpaceTicks = 4; const uint16_t kPanasonicHdrSpace = kPanasonicHdrSpaceTicks * kPanasonicTick; const uint16_t kPanasonicBitMarkTicks = 1; const uint16_t kPanasonicBitMark = kPanasonicBitMarkTicks * kPanasonicTick; const uint16_t kPanasonicOneSpaceTicks = 3; const uint16_t kPanasonicOneSpace = kPanasonicOneSpaceTicks * kPanasonicTick; const uint16_t kPanasonicZeroSpaceTicks = 1; const uint16_t kPanasonicZeroSpace = kPanasonicZeroSpaceTicks * kPanasonicTick; const uint16_t kPanasonicMinCommandLengthTicks = 378; const uint32_t kPanasonicMinCommandLength = kPanasonicMinCommandLengthTicks * kPanasonicTick; const uint16_t kPanasonicEndGap = 5000; // See issue #245 const uint16_t kPanasonicMinGapTicks = kPanasonicMinCommandLengthTicks - (kPanasonicHdrMarkTicks + kPanasonicHdrSpaceTicks + kPanasonicBits * (kPanasonicBitMarkTicks + kPanasonicOneSpaceTicks) + kPanasonicBitMarkTicks); const uint32_t kPanasonicMinGap = kPanasonicMinGapTicks * kPanasonicTick; const uint16_t kPanasonicAcSectionGap = 10000; const uint16_t kPanasonicAcSection1Length = 8; const uint32_t kPanasonicAcMessageGap = kDefaultMessageGap; // Just a guess. using irutils::addBoolToString; using irutils::addFanToString; using irutils::addIntToString; using irutils::addLabeledString; using irutils::addModeToString; using irutils::addTempToString; using irutils::minsToString; #if (SEND_PANASONIC || SEND_DENON) // Send a Panasonic formatted message. // // Args: // data: The message to be sent. // nbits: The number of bits of the message to be sent. (kPanasonicBits). // repeat: The number of times the command is to be repeated. // // Status: BETA / Should be working. // // Note: // This protocol is a modified version of Kaseikyo. void IRsend::sendPanasonic64(const uint64_t data, const uint16_t nbits, const uint16_t repeat) { sendGeneric(kPanasonicHdrMark, kPanasonicHdrSpace, kPanasonicBitMark, kPanasonicOneSpace, kPanasonicBitMark, kPanasonicZeroSpace, kPanasonicBitMark, kPanasonicMinGap, kPanasonicMinCommandLength, data, nbits, kPanasonicFreq, true, repeat, 50); } // Send a Panasonic formatted message. // // Args: // address: The manufacturer code. // data: The data portion to be sent. // nbits: The number of bits of the message to be sent. (kPanasonicBits). // repeat: The number of times the command is to be repeated. // // Status: STABLE. // // Note: // This protocol is a modified version of Kaseikyo. void IRsend::sendPanasonic(const uint16_t address, const uint32_t data, const uint16_t nbits, const uint16_t repeat) { sendPanasonic64(((uint64_t)address << 32) | (uint64_t)data, nbits, repeat); } // Calculate the raw Panasonic data based on device, subdevice, & function. // // Args: // manufacturer: A 16-bit manufacturer code. e.g. 0x4004 is Panasonic. // device: An 8-bit code. // subdevice: An 8-bit code. // function: An 8-bit code. // Returns: // A raw uint64_t Panasonic message. // // Status: BETA / Should be working.. // // Note: // Panasonic 48-bit protocol is a modified version of Kaseikyo. // Ref: // http://www.remotecentral.com/cgi-bin/mboard/rc-pronto/thread.cgi?2615 uint64_t IRsend::encodePanasonic(const uint16_t manufacturer, const uint8_t device, const uint8_t subdevice, const uint8_t function) { uint8_t checksum = device ^ subdevice ^ function; return (((uint64_t)manufacturer << 32) | ((uint64_t)device << 24) | ((uint64_t)subdevice << 16) | ((uint64_t)function << 8) | checksum); } #endif // (SEND_PANASONIC || SEND_DENON) #if (DECODE_PANASONIC || DECODE_DENON) // Decode the supplied Panasonic message. // // Args: // results: Ptr to the data to decode and where to store the decode result. // nbits: Nr. of data bits to expect. // strict: Flag indicating if we should perform strict matching. // Returns: // boolean: True if it can decode it, false if it can't. // // Status: BETA / Should be working. // Note: // Panasonic 48-bit protocol is a modified version of Kaseikyo. // Ref: // http://www.remotecentral.com/cgi-bin/mboard/rc-pronto/thread.cgi?26152 // http://www.hifi-remote.com/wiki/index.php?title=Panasonic bool IRrecv::decodePanasonic(decode_results *results, const uint16_t nbits, const bool strict, const uint32_t manufacturer) { if (strict && nbits != kPanasonicBits) return false; // Request is out of spec. uint64_t data = 0; uint16_t offset = kStartOffset; // Match Header + Data + Footer if (!matchGeneric(results->rawbuf + offset, &data, results->rawlen - offset, nbits, kPanasonicHdrMark, kPanasonicHdrSpace, kPanasonicBitMark, kPanasonicOneSpace, kPanasonicBitMark, kPanasonicZeroSpace, kPanasonicBitMark, kPanasonicEndGap, true)) return false; // Compliance uint32_t address = data >> 32; uint32_t command = data & 0xFFFFFFFF; if (strict) { if (address != manufacturer) // Verify the Manufacturer code. return false; // Verify the checksum. uint8_t checksumOrig = data & 0xFF; uint8_t checksumCalc = ((data >> 24) ^ (data >> 16) ^ (data >> 8)) & 0xFF; if (checksumOrig != checksumCalc) return false; } // Success results->value = data; results->address = address; results->command = command; results->decode_type = PANASONIC; results->bits = nbits; return true; } #endif // (DECODE_PANASONIC || DECODE_DENON) #if SEND_PANASONIC_AC // Send a Panasonic A/C message. // // Args: // data: Contents of the message to be sent. (Guessing MSBF order) // nbits: Nr. of bits of data to be sent. Typically kPanasonicAcBits. // repeat: Nr. of additional times the message is to be sent. // // Status: Beta / Appears to work with real device(s). //: // Panasonic A/C models supported: // A/C Series/models: // JKE, LKE, DKE, CKP, RKR, & NKE series. // CS-YW9MKD // A/C Remotes: // A75C3747 // A75C3704 // void IRsend::sendPanasonicAC(const uint8_t data[], const uint16_t nbytes, const uint16_t repeat) { if (nbytes < kPanasonicAcSection1Length) return; for (uint16_t r = 0; r <= repeat; r++) { // First section. (8 bytes) sendGeneric(kPanasonicHdrMark, kPanasonicHdrSpace, kPanasonicBitMark, kPanasonicOneSpace, kPanasonicBitMark, kPanasonicZeroSpace, kPanasonicBitMark, kPanasonicAcSectionGap, data, kPanasonicAcSection1Length, kPanasonicFreq, false, 0, 50); // First section. (The rest of the data bytes) sendGeneric(kPanasonicHdrMark, kPanasonicHdrSpace, kPanasonicBitMark, kPanasonicOneSpace, kPanasonicBitMark, kPanasonicZeroSpace, kPanasonicBitMark, kPanasonicAcMessageGap, data + kPanasonicAcSection1Length, nbytes - kPanasonicAcSection1Length, kPanasonicFreq, false, 0, 50); } } #endif // SEND_PANASONIC_AC IRPanasonicAc::IRPanasonicAc(const uint16_t pin, const bool inverted, const bool use_modulation) : _irsend(pin, inverted, use_modulation) { this->stateReset(); } void IRPanasonicAc::stateReset(void) { for (uint8_t i = 0; i < kPanasonicAcStateLength; i++) remote_state[i] = kPanasonicKnownGoodState[i]; _temp = 25; // An initial saved desired temp. Completely made up. _swingh = kPanasonicAcSwingHMiddle; // A similar made up value for H Swing. } void IRPanasonicAc::begin(void) { _irsend.begin(); } // Verify the checksum is valid for a given state. // Args: // state: The array to verify the checksum of. // length: The size of the state. // Returns: // A boolean. bool IRPanasonicAc::validChecksum(uint8_t state[], const uint16_t length) { if (length < 2) return false; // 1 byte of data can't have a checksum. return (state[length - 1] == sumBytes(state, length - 1, kPanasonicAcChecksumInit)); } uint8_t IRPanasonicAc::calcChecksum(uint8_t state[], const uint16_t length) { return sumBytes(state, length - 1, kPanasonicAcChecksumInit); } void IRPanasonicAc::fixChecksum(const uint16_t length) { remote_state[length - 1] = this->calcChecksum(remote_state, length); } #if SEND_PANASONIC_AC void IRPanasonicAc::send(const uint16_t repeat) { this->fixChecksum(); _irsend.sendPanasonicAC(remote_state, kPanasonicAcStateLength, repeat); } #endif // SEND_PANASONIC_AC void IRPanasonicAc::setModel(const panasonic_ac_remote_model_t model) { switch (model) { case kPanasonicDke: case kPanasonicJke: case kPanasonicLke: case kPanasonicNke: case kPanasonicCkp: case kPanasonicRkr: break; default: // Only proceed if we know what to do. return; } // clear & set the various bits and bytes. remote_state[13] &= 0xF0; remote_state[17] = 0x00; remote_state[21] &= 0b11101111; remote_state[23] = 0x81; remote_state[25] = 0x00; switch (model) { case kPanasonicLke: remote_state[13] |= 0x02; remote_state[17] = 0x06; break; case kPanasonicDke: remote_state[23] = 0x01; remote_state[25] = 0x06; // Has to be done last as setSwingHorizontal has model check built-in this->setSwingHorizontal(_swingh); break; case kPanasonicNke: remote_state[17] = 0x06; break; case kPanasonicJke: break; case kPanasonicCkp: remote_state[21] |= 0x10; remote_state[23] = 0x01; break; case kPanasonicRkr: remote_state[13] |= 0x08; remote_state[23] = 0x89; default: break; } } panasonic_ac_remote_model_t IRPanasonicAc::getModel(void) { if (remote_state[23] == 0x89) return kPanasonicRkr; if (remote_state[17] == 0x00) { if ((remote_state[21] & 0x10) && (remote_state[23] & 0x01)) return kPanasonicCkp; if (remote_state[23] & 0x80) return kPanasonicJke; } if (remote_state[17] == 0x06 && (remote_state[13] & 0x0F) == 0x02) return kPanasonicLke; if (remote_state[23] == 0x01) return kPanasonicDke; if (remote_state[17] == 0x06) return kPanasonicNke; return kPanasonicUnknown; } uint8_t *IRPanasonicAc::getRaw(void) { this->fixChecksum(); return remote_state; } void IRPanasonicAc::setRaw(const uint8_t state[]) { for (uint8_t i = 0; i < kPanasonicAcStateLength; i++) { remote_state[i] = state[i]; } } // Control the power state of the A/C unit. // // For CKP models, the remote has no memory of the power state the A/C unit // should be in. For those models setting this on/true will toggle the power // state of the Panasonic A/C unit with the next meessage. // e.g. If the A/C unit is already on, setPower(true) will turn it off. // If the A/C unit is already off, setPower(true) will turn it on. // setPower(false) will leave the A/C power state as it was. // // For all other models, setPower(true) should set the internal state to // turn it on, and setPower(false) should turn it off. void IRPanasonicAc::setPower(const bool on) { if (on) this->on(); else this->off(); } // Return the A/C power state of the remote. // Except for CKP models, where it returns if the power state will be toggled // on the A/C unit when the next message is sent. bool IRPanasonicAc::getPower(void) { return (remote_state[13] & kPanasonicAcPower) == kPanasonicAcPower; } void IRPanasonicAc::on(void) { remote_state[13] |= kPanasonicAcPower; } void IRPanasonicAc::off(void) { remote_state[13] &= ~kPanasonicAcPower; } uint8_t IRPanasonicAc::getMode(void) { return remote_state[13] >> 4; } void IRPanasonicAc::setMode(const uint8_t desired) { uint8_t mode = kPanasonicAcAuto; // Default to Auto mode. switch (desired) { case kPanasonicAcFan: // Allegedly Fan mode has a temperature of 27. this->setTemp(kPanasonicAcFanModeTemp, false); mode = desired; break; case kPanasonicAcAuto: case kPanasonicAcCool: case kPanasonicAcHeat: case kPanasonicAcDry: mode = desired; // Set the temp to the saved temp, just incase our previous mode was Fan. this->setTemp(_temp); break; } remote_state[13] &= 0x0F; // Clear the previous mode bits. remote_state[13] |= mode << 4; } uint8_t IRPanasonicAc::getTemp(void) { return remote_state[14] >> 1; } // Set the desitred temperature in Celsius. // Args: // celsius: The temperature to set the A/C unit to. // remember: A boolean flag for the class to remember the temperature. // // Automatically safely limits the temp to the operating range supported. void IRPanasonicAc::setTemp(const uint8_t celsius, const bool remember) { uint8_t temperature; temperature = std::max(celsius, kPanasonicAcMinTemp); temperature = std::min(temperature, kPanasonicAcMaxTemp); remote_state[14] = temperature << 1; if (remember) _temp = temperature; } uint8_t IRPanasonicAc::getSwingVertical(void) { return remote_state[16] & 0x0F; } void IRPanasonicAc::setSwingVertical(const uint8_t desired_elevation) { uint8_t elevation = desired_elevation; if (elevation != kPanasonicAcSwingVAuto) { elevation = std::max(elevation, kPanasonicAcSwingVUp); elevation = std::min(elevation, kPanasonicAcSwingVDown); } remote_state[16] &= 0xF0; remote_state[16] |= elevation; } uint8_t IRPanasonicAc::getSwingHorizontal(void) { return remote_state[17]; } void IRPanasonicAc::setSwingHorizontal(const uint8_t desired_direction) { switch (desired_direction) { case kPanasonicAcSwingHAuto: case kPanasonicAcSwingHMiddle: case kPanasonicAcSwingHFullLeft: case kPanasonicAcSwingHLeft: case kPanasonicAcSwingHRight: case kPanasonicAcSwingHFullRight: break; default: // Ignore anything that isn't valid. return; } _swingh = desired_direction; // Store the direction for later. uint8_t direction = desired_direction; switch (this->getModel()) { case kPanasonicDke: case kPanasonicRkr: break; case kPanasonicNke: case kPanasonicLke: direction = kPanasonicAcSwingHMiddle; break; default: // Ignore everything else. return; } remote_state[17] = direction; } void IRPanasonicAc::setFan(const uint8_t speed) { if (speed <= kPanasonicAcFanMax || speed == kPanasonicAcFanAuto) remote_state[16] = (remote_state[16] & 0x0F) | ((speed + kPanasonicAcFanOffset) << 4); } uint8_t IRPanasonicAc::getFan(void) { return (remote_state[16] >> 4) - kPanasonicAcFanOffset; } bool IRPanasonicAc::getQuiet(void) { switch (this->getModel()) { case kPanasonicRkr: case kPanasonicCkp: return remote_state[21] & kPanasonicAcQuietCkp; default: return remote_state[21] & kPanasonicAcQuiet; } } void IRPanasonicAc::setQuiet(const bool on) { uint8_t quiet; switch (this->getModel()) { case kPanasonicRkr: case kPanasonicCkp: quiet = kPanasonicAcQuietCkp; break; default: quiet = kPanasonicAcQuiet; } if (on) { this->setPowerful(false); // Powerful is mutually exclusive. remote_state[21] |= quiet; } else { remote_state[21] &= ~quiet; } } bool IRPanasonicAc::getPowerful(void) { switch (this->getModel()) { case kPanasonicRkr: case kPanasonicCkp: return remote_state[21] & kPanasonicAcPowerfulCkp; default: return remote_state[21] & kPanasonicAcPowerful; } } void IRPanasonicAc::setPowerful(const bool on) { uint8_t powerful; switch (this->getModel()) { case kPanasonicRkr: case kPanasonicCkp: powerful = kPanasonicAcPowerfulCkp; break; default: powerful = kPanasonicAcPowerful; } if (on) { this->setQuiet(false); // Quiet is mutually exclusive. remote_state[21] |= powerful; } else { remote_state[21] &= ~powerful; } } uint16_t IRPanasonicAc::encodeTime(const uint8_t hours, const uint8_t mins) { return std::min(hours, (uint8_t)23) * 60 + std::min(mins, (uint8_t)59); } uint16_t IRPanasonicAc::getClock(void) { uint16_t result = ((remote_state[25] & 0b00000111) << 8) + remote_state[24]; if (result == kPanasonicAcTimeSpecial) return 0; return result; } void IRPanasonicAc::setClock(const uint16_t mins_since_midnight) { uint16_t corrected = std::min(mins_since_midnight, kPanasonicAcTimeMax); if (mins_since_midnight == kPanasonicAcTimeSpecial) corrected = kPanasonicAcTimeSpecial; remote_state[24] = corrected & 0xFF; remote_state[25] &= 0b11111000; remote_state[25] |= (corrected >> 8); } uint16_t IRPanasonicAc::getOnTimer(void) { uint16_t result = ((remote_state[19] & 0b00000111) << 8) + remote_state[18]; if (result == kPanasonicAcTimeSpecial) return 0; return result; } void IRPanasonicAc::setOnTimer(const uint16_t mins_since_midnight, const bool enable) { // Ensure it's on a 10 minute boundary and no overflow. uint16_t corrected = std::min(mins_since_midnight, kPanasonicAcTimeMax); corrected -= corrected % 10; if (mins_since_midnight == kPanasonicAcTimeSpecial) corrected = kPanasonicAcTimeSpecial; if (enable) remote_state[13] |= kPanasonicAcOnTimer; // Set the Ontimer flag. else remote_state[13] &= ~kPanasonicAcOnTimer; // Clear the Ontimer flag. // Store the time. remote_state[18] = corrected & 0xFF; remote_state[19] &= 0b11111000; remote_state[19] |= (corrected >> 8); } void IRPanasonicAc::cancelOnTimer(void) { this->setOnTimer(0, false); } bool IRPanasonicAc::isOnTimerEnabled(void) { return remote_state[13] & kPanasonicAcOnTimer; } uint16_t IRPanasonicAc::getOffTimer(void) { uint16_t result = ((remote_state[20] & 0b01111111) << 4) + (remote_state[19] >> 4); if (result == kPanasonicAcTimeSpecial) return 0; return result; } void IRPanasonicAc::setOffTimer(const uint16_t mins_since_midnight, const bool enable) { // Ensure its on a 10 minute boundary and no overflow. uint16_t corrected = std::min(mins_since_midnight, kPanasonicAcTimeMax); corrected -= corrected % 10; if (mins_since_midnight == kPanasonicAcTimeSpecial) corrected = kPanasonicAcTimeSpecial; if (enable) remote_state[13] |= kPanasonicAcOffTimer; // Set the OffTimer flag. else remote_state[13] &= ~kPanasonicAcOffTimer; // Clear the OffTimer flag. // Store the time. remote_state[19] &= 0b00001111; remote_state[19] |= (corrected & 0b00001111) << 4; remote_state[20] &= 0b10000000; remote_state[20] |= corrected >> 4; } void IRPanasonicAc::cancelOffTimer(void) { this->setOffTimer(0, false); } bool IRPanasonicAc::isOffTimerEnabled(void) { return remote_state[13] & kPanasonicAcOffTimer; } // Convert a standard A/C mode into its native mode. uint8_t IRPanasonicAc::convertMode(const stdAc::opmode_t mode) { switch (mode) { case stdAc::opmode_t::kCool: return kPanasonicAcCool; case stdAc::opmode_t::kHeat: return kPanasonicAcHeat; case stdAc::opmode_t::kDry: return kPanasonicAcDry; case stdAc::opmode_t::kFan: return kPanasonicAcFan; default: return kPanasonicAcAuto; } } // Convert a standard A/C Fan speed into its native fan speed. uint8_t IRPanasonicAc::convertFan(const stdAc::fanspeed_t speed) { switch (speed) { case stdAc::fanspeed_t::kMin: return kPanasonicAcFanMin; case stdAc::fanspeed_t::kLow: return kPanasonicAcFanMin + 1; case stdAc::fanspeed_t::kMedium: return kPanasonicAcFanMin + 2; case stdAc::fanspeed_t::kHigh: return kPanasonicAcFanMin + 3; case stdAc::fanspeed_t::kMax: return kPanasonicAcFanMax; default: return kPanasonicAcFanAuto; } } // Convert a standard A/C vertical swing into its native setting. uint8_t IRPanasonicAc::convertSwingV(const stdAc::swingv_t position) { switch (position) { case stdAc::swingv_t::kHighest: case stdAc::swingv_t::kHigh: case stdAc::swingv_t::kMiddle: return kPanasonicAcSwingVUp; case stdAc::swingv_t::kLow: case stdAc::swingv_t::kLowest: return kPanasonicAcSwingVDown; default: return kPanasonicAcSwingVAuto; } } // Convert a standard A/C horizontal swing into its native setting. uint8_t IRPanasonicAc::convertSwingH(const stdAc::swingh_t position) { switch (position) { case stdAc::swingh_t::kLeftMax: return kPanasonicAcSwingHFullLeft; case stdAc::swingh_t::kLeft: return kPanasonicAcSwingHLeft; case stdAc::swingh_t::kMiddle: return kPanasonicAcSwingHMiddle; case stdAc::swingh_t::kRight: return kPanasonicAcSwingHRight; case stdAc::swingh_t::kRightMax: return kPanasonicAcSwingHFullRight; default: return kPanasonicAcSwingHAuto; } } // Convert a native mode to it's common equivalent. stdAc::opmode_t IRPanasonicAc::toCommonMode(const uint8_t mode) { switch (mode) { case kPanasonicAcCool: return stdAc::opmode_t::kCool; case kPanasonicAcHeat: return stdAc::opmode_t::kHeat; case kPanasonicAcDry: return stdAc::opmode_t::kDry; case kPanasonicAcFan: return stdAc::opmode_t::kFan; default: return stdAc::opmode_t::kAuto; } } // Convert a native fan speed to it's common equivalent. stdAc::fanspeed_t IRPanasonicAc::toCommonFanSpeed(const uint8_t spd) { switch (spd) { case kPanasonicAcFanMax: return stdAc::fanspeed_t::kMax; case kPanasonicAcFanMin + 3: return stdAc::fanspeed_t::kHigh; case kPanasonicAcFanMin + 2: return stdAc::fanspeed_t::kMedium; case kPanasonicAcFanMin + 1: return stdAc::fanspeed_t::kLow; case kPanasonicAcFanMin: return stdAc::fanspeed_t::kMin; default: return stdAc::fanspeed_t::kAuto; } } // Convert a native vertical swing to it's common equivalent. stdAc::swingh_t IRPanasonicAc::toCommonSwingH(const uint8_t pos) { switch (pos) { case kPanasonicAcSwingHFullLeft: return stdAc::swingh_t::kLeftMax; case kPanasonicAcSwingHLeft: return stdAc::swingh_t::kLeft; case kPanasonicAcSwingHMiddle: return stdAc::swingh_t::kMiddle; case kPanasonicAcSwingHRight: return stdAc::swingh_t::kRight; case kPanasonicAcSwingHFullRight: return stdAc::swingh_t::kRightMax; default: return stdAc::swingh_t::kAuto; } } // Convert a native vertical swing to it's common equivalent. stdAc::swingv_t IRPanasonicAc::toCommonSwingV(const uint8_t pos) { switch (pos) { case kPanasonicAcSwingVUp: return stdAc::swingv_t::kHighest; case kPanasonicAcSwingVDown: return stdAc::swingv_t::kLowest; default: return stdAc::swingv_t::kAuto; } } // Convert the A/C state to it's common equivalent. stdAc::state_t IRPanasonicAc::toCommon(void) { stdAc::state_t result; result.protocol = decode_type_t::PANASONIC_AC; result.model = this->getModel(); result.power = this->getPower(); result.mode = this->toCommonMode(this->getMode()); result.celsius = true; result.degrees = this->getTemp(); result.fanspeed = this->toCommonFanSpeed(this->getFan()); result.swingv = this->toCommonSwingV(this->getSwingVertical()); result.swingh = this->toCommonSwingH(this->getSwingHorizontal()); result.quiet = this->getQuiet(); result.turbo = this->getPowerful(); // Not supported. result.econo = false; result.clean = 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. String IRPanasonicAc::toString(void) { String result = ""; result.reserve(180); // Reserve some heap for the string to reduce fragging. result += F("Model: "); result += uint64ToString(getModel()); switch (getModel()) { case kPanasonicDke: result += F(" (DKE)"); break; case kPanasonicJke: result += F(" (JKE)"); break; case kPanasonicNke: result += F(" (NKE)"); break; case kPanasonicLke: result += F(" (LKE)"); break; case kPanasonicCkp: result += F(" (CKP)"); break; case kPanasonicRkr: result += F(" (RKR)"); break; default: result += F(" (UNKNOWN)"); } result += addBoolToString(getPower(), F("Power")); result += addModeToString(getMode(), kPanasonicAcAuto, kPanasonicAcCool, kPanasonicAcHeat, kPanasonicAcDry, kPanasonicAcFan); result += addTempToString(getTemp()); result += addFanToString(getFan(), kPanasonicAcFanMax, kPanasonicAcFanMin, kPanasonicAcFanAuto, kPanasonicAcFanAuto, kPanasonicAcFanMed); result += addIntToString(getSwingVertical(), F("Swing (Vertical)")); switch (getSwingVertical()) { case kPanasonicAcSwingVAuto: result += F(" (AUTO)"); break; case kPanasonicAcSwingVUp: result += F(" (Full Up)"); break; case kPanasonicAcSwingVDown: result += F(" (Full Down)"); break; case 2: case 3: case 4: break; default: result += F(" (UNKNOWN)"); break; } switch (getModel()) { case kPanasonicJke: case kPanasonicCkp: break; // No Horizontal Swing support. default: result += addIntToString(getSwingHorizontal(), F("Swing (Horizontal)")); switch (getSwingHorizontal()) { case kPanasonicAcSwingHAuto: result += F(" (AUTO)"); break; case kPanasonicAcSwingHFullLeft: result += F(" (Full Left)"); break; case kPanasonicAcSwingHLeft: result += F(" (Left)"); break; case kPanasonicAcSwingHMiddle: result += F(" (Middle)"); break; case kPanasonicAcSwingHFullRight: result += F(" (Full Right)"); break; case kPanasonicAcSwingHRight: result += F(" (Right)"); break; default: result += F(" (UNKNOWN)"); break; } } result += addBoolToString(getQuiet(), F("Quiet")); result += addBoolToString(getPowerful(), F("Powerful")); result += addLabeledString(minsToString(getClock()), F("Clock")); result += addLabeledString( isOnTimerEnabled() ? minsToString(getOnTimer()) : F("Off"), F("On Timer")); result += addLabeledString( isOffTimerEnabled() ? minsToString(getOffTimer()) : F("Off"), F("Off Timer")); return result; } #if DECODE_PANASONIC_AC // Decode the supplied Panasonic AC message. // // Args: // results: Ptr to the data to decode and where to store the decode result. // nbits: The number of data bits to expect. Typically kPanasonicAcBits. // strict: Flag indicating if we should perform strict matching. // Returns: // boolean: True if it can decode it, false if it can't. // // Status: Beta / Appears to work with real device(s). // // Panasonic A/C models supported: // A/C Series/models: // JKE, LKE, DKE, & NKE series. // CS-YW9MKD // A/C Remotes: // A75C3747 (Confirmed) // A75C3704 bool IRrecv::decodePanasonicAC(decode_results *results, const uint16_t nbits, const bool strict) { uint8_t min_nr_of_messages = 1; if (strict) { if (nbits != kPanasonicAcBits && nbits != kPanasonicAcShortBits) return false; // Not strictly a PANASONIC_AC message. } if (results->rawlen < min_nr_of_messages * (2 * nbits + kHeader + kFooter) - 1) return false; // Can't possibly be a valid PANASONIC_AC message. uint16_t offset = kStartOffset; // Match Header + Data #1 + Footer uint16_t used; used = matchGeneric(results->rawbuf + offset, results->state, results->rawlen - offset, kPanasonicAcSection1Length * 8, kPanasonicHdrMark, kPanasonicHdrSpace, kPanasonicBitMark, kPanasonicOneSpace, kPanasonicBitMark, kPanasonicZeroSpace, kPanasonicBitMark, kPanasonicAcSectionGap, false, kPanasonicAcTolerance, kPanasonicAcExcess, false); if (!used) return false; offset += used; // Match Header + Data #2 + Footer if (!matchGeneric(results->rawbuf + offset, results->state + kPanasonicAcSection1Length, results->rawlen - offset, nbits - kPanasonicAcSection1Length * 8, kPanasonicHdrMark, kPanasonicHdrSpace, kPanasonicBitMark, kPanasonicOneSpace, kPanasonicBitMark, kPanasonicZeroSpace, kPanasonicBitMark, kPanasonicAcMessageGap, true, kPanasonicAcTolerance, kPanasonicAcExcess, false)) return false; // Compliance if (strict) { // Check the signatures of the section blocks. They start with 0x02& 0x20. if (results->state[0] != 0x02 || results->state[1] != 0x20 || results->state[8] != 0x02 || results->state[9] != 0x20) return false; if (!IRPanasonicAc::validChecksum(results->state, nbits / 8)) return false; } // Success results->decode_type = PANASONIC_AC; results->bits = nbits; return true; } #endif // DECODE_PANASONIC_AC