1340 lines
54 KiB
C++
1340 lines
54 KiB
C++
// Copyright 2015 Kristian Lauszus
|
|
// Copyright 2017, 2018 David Conran
|
|
|
|
/// @file
|
|
/// @brief Support for Panasonic protocols.
|
|
/// Panasonic protocol originally added by Kristian Lauszus
|
|
/// (Thanks to zenwheel and other people at the original blog post)
|
|
/// @see Panasonic https://github.com/z3t0/Arduino-IRremote
|
|
/// @see http://www.remotecentral.com/cgi-bin/mboard/rc-pronto/thread.cgi?2615
|
|
/// @see Panasonic A/C support 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
|
|
|
|
#include "ir_Panasonic.h"
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#ifndef ARDUINO
|
|
#include <string>
|
|
#endif
|
|
#include "IRrecv.h"
|
|
#include "IRsend.h"
|
|
#include "IRtext.h"
|
|
#include "IRutils.h"
|
|
|
|
// Constants
|
|
/// @see http://www.remotecentral.com/cgi-bin/mboard/rc-pronto/thread.cgi?26152
|
|
const uint16_t kPanasonicHdrMark = 3456; ///< uSeconds.
|
|
const uint16_t kPanasonicHdrSpace = 1728; ///< uSeconds.
|
|
const uint16_t kPanasonicBitMark = 432; ///< uSeconds.
|
|
const uint16_t kPanasonicOneSpace = 1296; ///< uSeconds.
|
|
const uint16_t kPanasonicZeroSpace = 432; ///< uSeconds.
|
|
const uint32_t kPanasonicMinCommandLength = 163296; ///< uSeconds.
|
|
const uint16_t kPanasonicEndGap = 5000; ///< uSeconds. See #245
|
|
const uint32_t kPanasonicMinGap = 74736; ///< uSeconds.
|
|
|
|
const uint16_t kPanasonicAcSectionGap = 10000; ///< uSeconds.
|
|
const uint16_t kPanasonicAcSection1Length = 8;
|
|
const uint32_t kPanasonicAcMessageGap = kDefaultMessageGap; // Just a guess.
|
|
|
|
const uint16_t kPanasonicAc32HdrMark = 3543; ///< uSeconds.
|
|
const uint16_t kPanasonicAc32BitMark = 920; ///< uSeconds.
|
|
const uint16_t kPanasonicAc32HdrSpace = 3450; ///< uSeconds.
|
|
const uint16_t kPanasonicAc32OneSpace = 2575; ///< uSeconds.
|
|
const uint16_t kPanasonicAc32ZeroSpace = 828; ///< uSeconds.
|
|
const uint16_t kPanasonicAc32SectionGap = 13946; ///< uSeconds.
|
|
const uint8_t kPanasonicAc32Sections = 2;
|
|
const uint8_t kPanasonicAc32BlocksPerSection = 2;
|
|
|
|
using irutils::addBoolToString;
|
|
using irutils::addFanToString;
|
|
using irutils::addIntToString;
|
|
using irutils::addLabeledString;
|
|
using irutils::addModeToString;
|
|
using irutils::addModelToString;
|
|
using irutils::addSwingHToString;
|
|
using irutils::addSwingVToString;
|
|
using irutils::addTempToString;
|
|
using irutils::minsToString;
|
|
using irutils::setBit;
|
|
using irutils::setBits;
|
|
|
|
// Used by Denon as well.
|
|
#if (SEND_PANASONIC || SEND_DENON)
|
|
/// Send a Panasonic formatted message.
|
|
/// Status: STABLE / Should be 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 is a modified version of Kaseikyo.
|
|
/// @note Use this method if you want to send the results of `decodePanasonic`.
|
|
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.
|
|
/// Status: STABLE, but DEPRECATED
|
|
/// @deprecated This is only for legacy use only, please use `sendPanasonic64()`
|
|
/// instead.
|
|
/// @param[in] address The 16-bit manufacturer code.
|
|
/// @param[in] data The 32-bit data portion of 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 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.
|
|
/// Status: STABLE / Should be working.
|
|
/// @param[in] manufacturer A 16-bit manufacturer code. e.g. 0x4004 is Panasonic
|
|
/// @param[in] device An 8-bit code.
|
|
/// @param[in] subdevice An 8-bit code.
|
|
/// @param[in] function An 8-bit code.
|
|
/// @return A value suitable for use with `sendPanasonic64()`.
|
|
/// @note Panasonic 48-bit protocol is a modified version of Kaseikyo.
|
|
/// @see 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)
|
|
|
|
// Used by Denon as well.
|
|
#if (DECODE_PANASONIC || DECODE_DENON)
|
|
/// Decode the supplied Panasonic message.
|
|
/// Status: STABLE / Should be 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] manufacturer A 16-bit manufacturer code. e.g. 0x4004 is Panasonic
|
|
/// @param[in] strict Flag indicating if we should perform strict matching.
|
|
/// @return True if it can decode it, false if it can't.
|
|
/// @warning Results to be used with `sendPanasonic64()`, not `sendPanasonic()`.
|
|
/// @note Panasonic 48-bit protocol is a modified version of Kaseikyo.
|
|
/// @see http://www.remotecentral.com/cgi-bin/mboard/rc-pronto/thread.cgi?2615
|
|
/// @see http://www.hifi-remote.com/wiki/index.php?title=Panasonic
|
|
bool IRrecv::decodePanasonic(decode_results *results, uint16_t offset,
|
|
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;
|
|
|
|
// 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;
|
|
if (strict) {
|
|
if (address != manufacturer) // Verify the Manufacturer code.
|
|
return false;
|
|
// Verify the checksum.
|
|
uint8_t checksumOrig = data;
|
|
uint8_t checksumCalc = (data >> 24) ^ (data >> 16) ^ (data >> 8);
|
|
if (checksumOrig != checksumCalc) return false;
|
|
}
|
|
|
|
// Success
|
|
results->value = data;
|
|
results->address = address;
|
|
results->command = command;
|
|
results->decode_type = decode_type_t::PANASONIC;
|
|
results->bits = nbits;
|
|
return true;
|
|
}
|
|
#endif // (DECODE_PANASONIC || DECODE_DENON)
|
|
|
|
#if SEND_PANASONIC_AC
|
|
/// Send a Panasonic A/C message.
|
|
/// Status: STABLE / Work with real device(s).
|
|
/// @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::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
|
|
|
|
/// 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?
|
|
IRPanasonicAc::IRPanasonicAc(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 IRPanasonicAc::stateReset(void) {
|
|
memcpy(remote_state, kPanasonicKnownGoodState, kPanasonicAcStateLength);
|
|
_temp = 25; // An initial saved desired temp. Completely made up.
|
|
_swingh = kPanasonicAcSwingHMiddle; // A similar made up value for H Swing.
|
|
}
|
|
|
|
/// Set up hardware to be able to send a message.
|
|
void IRPanasonicAc::begin(void) { _irsend.begin(); }
|
|
|
|
/// Verify the checksum is valid for a given state.
|
|
/// @param[in] state The array to verify the checksum of.
|
|
/// @param[in] length The length of the state array.
|
|
/// @return true, if the state has a valid checksum. Otherwise, false.
|
|
bool IRPanasonicAc::validChecksum(const 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));
|
|
}
|
|
|
|
/// Calculate the checksum for a given state.
|
|
/// @param[in] state The value to calc the checksum of.
|
|
/// @param[in] length The size/length of the state.
|
|
/// @return The calculated checksum value.
|
|
uint8_t IRPanasonicAc::calcChecksum(const uint8_t *state,
|
|
const uint16_t length) {
|
|
return sumBytes(state, length - 1, kPanasonicAcChecksumInit);
|
|
}
|
|
|
|
/// Calculate and set the checksum values for the internal state.
|
|
/// @param[in] length The size/length of the state.
|
|
void IRPanasonicAc::fixChecksum(const uint16_t length) {
|
|
remote_state[length - 1] = calcChecksum(remote_state, length);
|
|
}
|
|
|
|
#if SEND_PANASONIC_AC
|
|
/// Send the current internal state as an IR message.
|
|
/// @param[in] repeat Nr. of times the message will be repeated.
|
|
void IRPanasonicAc::send(const uint16_t repeat) {
|
|
_irsend.sendPanasonicAC(getRaw(), kPanasonicAcStateLength, repeat);
|
|
}
|
|
#endif // SEND_PANASONIC_AC
|
|
|
|
/// Set the model of the A/C to emulate.
|
|
/// @param[in] model The enum of the appropriate model.
|
|
void IRPanasonicAc::setModel(const panasonic_ac_remote_model_t model) {
|
|
switch (model) {
|
|
case panasonic_ac_remote_model_t::kPanasonicDke:
|
|
case panasonic_ac_remote_model_t::kPanasonicJke:
|
|
case panasonic_ac_remote_model_t::kPanasonicLke:
|
|
case panasonic_ac_remote_model_t::kPanasonicNke:
|
|
case panasonic_ac_remote_model_t::kPanasonicCkp:
|
|
case panasonic_ac_remote_model_t::kPanasonicRkr: break;
|
|
// Only proceed if we know what to do.
|
|
default: 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
|
|
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;
|
|
}
|
|
// Reset the Ion filter.
|
|
setIon(getIon());
|
|
}
|
|
|
|
/// Get/Detect the model of the A/C.
|
|
/// @return The enum of the compatible model.
|
|
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 panasonic_ac_remote_model_t::kPanasonicCkp;
|
|
if (remote_state[23] & 0x80)
|
|
return panasonic_ac_remote_model_t::kPanasonicJke;
|
|
}
|
|
if (remote_state[17] == 0x06 && (remote_state[13] & 0x0F) == 0x02)
|
|
return panasonic_ac_remote_model_t::kPanasonicLke;
|
|
if (remote_state[23] == 0x01)
|
|
return panasonic_ac_remote_model_t::kPanasonicDke;
|
|
if (remote_state[17] == 0x06)
|
|
return panasonic_ac_remote_model_t::kPanasonicNke;
|
|
return panasonic_ac_remote_model_t::kPanasonicUnknown; // Default
|
|
}
|
|
|
|
/// 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 *IRPanasonicAc::getRaw(void) {
|
|
fixChecksum();
|
|
return remote_state;
|
|
}
|
|
|
|
/// Set the internal state from a valid code for this protocol.
|
|
/// @param[in] state A valid code for this protocol.
|
|
void IRPanasonicAc::setRaw(const uint8_t state[]) {
|
|
memcpy(remote_state, state, kPanasonicAcStateLength);
|
|
}
|
|
|
|
/// Control the power state of the A/C unit.
|
|
/// @param[in] on true, the setting is on. false, the setting is off.
|
|
/// @warning 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 message.
|
|
/// 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) {
|
|
setBit(&remote_state[13], kPanasonicAcPowerOffset, on);
|
|
}
|
|
|
|
/// Get the A/C power state of the remote.
|
|
/// @return true, the setting is on. false, the setting is off.
|
|
/// @warning 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 GETBIT8(remote_state[13], kPanasonicAcPowerOffset);
|
|
}
|
|
|
|
/// Change the power setting to On.
|
|
void IRPanasonicAc::on(void) { setPower(true); }
|
|
|
|
/// Change the power setting to Off.
|
|
void IRPanasonicAc::off(void) { setPower(false); }
|
|
|
|
/// Get the operating mode setting of the A/C.
|
|
/// @return The current operating mode setting.
|
|
uint8_t IRPanasonicAc::getMode(void) {
|
|
return GETBITS8(remote_state[13], kHighNibble, kModeBitsSize);
|
|
}
|
|
|
|
/// Set the operating mode of the A/C.
|
|
/// @param[in] desired The desired operating mode.
|
|
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.
|
|
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.
|
|
setTemp(_temp);
|
|
break;
|
|
}
|
|
remote_state[13] &= 0x0F; // Clear the previous mode bits.
|
|
setBits(&remote_state[13], kHighNibble, kModeBitsSize, mode);
|
|
}
|
|
|
|
/// Get the current temperature setting.
|
|
/// @return The current setting for temp. in degrees celsius.
|
|
uint8_t IRPanasonicAc::getTemp(void) {
|
|
return GETBITS8(remote_state[14], kPanasonicAcTempOffset,
|
|
kPanasonicAcTempSize);
|
|
}
|
|
|
|
/// Set the temperature.
|
|
/// @param[in] celsius The temperature in degrees celsius.
|
|
/// @param[in] remember: A flag for the class to remember the temperature.
|
|
/// @note 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);
|
|
if (remember) _temp = temperature;
|
|
setBits(&remote_state[14], kPanasonicAcTempOffset, kPanasonicAcTempSize,
|
|
temperature);
|
|
}
|
|
|
|
/// Get the current vertical swing setting.
|
|
/// @return The current position it is set to.
|
|
uint8_t IRPanasonicAc::getSwingVertical(void) {
|
|
return GETBITS8(remote_state[16], kLowNibble, kNibbleSize);
|
|
}
|
|
|
|
/// Control the vertical swing setting.
|
|
/// @param[in] desired_elevation The position to set the vertical swing to.
|
|
void IRPanasonicAc::setSwingVertical(const uint8_t desired_elevation) {
|
|
uint8_t elevation = desired_elevation;
|
|
if (elevation != kPanasonicAcSwingVAuto) {
|
|
elevation = std::max(elevation, kPanasonicAcSwingVHighest);
|
|
elevation = std::min(elevation, kPanasonicAcSwingVLowest);
|
|
}
|
|
setBits(&remote_state[16], kLowNibble, kNibbleSize, elevation);
|
|
}
|
|
|
|
/// Get the current horizontal swing setting.
|
|
/// @return The current position it is set to.
|
|
uint8_t IRPanasonicAc::getSwingHorizontal(void) {
|
|
return GETBITS8(remote_state[17], kLowNibble, kNibbleSize);
|
|
}
|
|
|
|
/// Control the horizontal swing setting.
|
|
/// @param[in] desired_direction The position to set the horizontal swing to.
|
|
void IRPanasonicAc::setSwingHorizontal(const uint8_t desired_direction) {
|
|
switch (desired_direction) {
|
|
case kPanasonicAcSwingHAuto:
|
|
case kPanasonicAcSwingHMiddle:
|
|
case kPanasonicAcSwingHFullLeft:
|
|
case kPanasonicAcSwingHLeft:
|
|
case kPanasonicAcSwingHRight:
|
|
case kPanasonicAcSwingHFullRight: break;
|
|
// Ignore anything that isn't valid.
|
|
default: return;
|
|
}
|
|
_swingh = desired_direction; // Store the direction for later.
|
|
uint8_t direction = desired_direction;
|
|
switch (getModel()) {
|
|
case kPanasonicDke:
|
|
case kPanasonicRkr:
|
|
break;
|
|
case kPanasonicNke:
|
|
case kPanasonicLke:
|
|
direction = kPanasonicAcSwingHMiddle;
|
|
break;
|
|
default: // Ignore everything else.
|
|
return;
|
|
}
|
|
setBits(&remote_state[17], kLowNibble, kNibbleSize, direction);
|
|
}
|
|
|
|
/// Set the speed of the fan.
|
|
/// @param[in] speed The desired setting.
|
|
void IRPanasonicAc::setFan(const uint8_t speed) {
|
|
switch (speed) {
|
|
case kPanasonicAcFanMin:
|
|
case kPanasonicAcFanMed:
|
|
case kPanasonicAcFanMax:
|
|
case kPanasonicAcFanAuto:
|
|
setBits(&remote_state[16], kHighNibble, kNibbleSize,
|
|
speed + kPanasonicAcFanDelta);
|
|
break;
|
|
default: setFan(kPanasonicAcFanAuto);
|
|
}
|
|
}
|
|
|
|
/// Get the current fan speed setting.
|
|
/// @return The current fan speed.
|
|
uint8_t IRPanasonicAc::getFan(void) {
|
|
return GETBITS8(remote_state[16], kHighNibble, kNibbleSize) -
|
|
kPanasonicAcFanDelta;
|
|
}
|
|
|
|
/// Get the Quiet setting of the A/C.
|
|
/// @return true, the setting is on. false, the setting is off.
|
|
bool IRPanasonicAc::getQuiet(void) {
|
|
switch (getModel()) {
|
|
case kPanasonicRkr:
|
|
case kPanasonicCkp:
|
|
return GETBIT8(remote_state[21], kPanasonicAcQuietCkpOffset);
|
|
default:
|
|
return GETBIT8(remote_state[21], kPanasonicAcQuietOffset);
|
|
}
|
|
}
|
|
|
|
/// Set the Quiet setting of the A/C.
|
|
/// @param[in] on true, the setting is on. false, the setting is off.
|
|
void IRPanasonicAc::setQuiet(const bool on) {
|
|
uint8_t offset;
|
|
switch (getModel()) {
|
|
case kPanasonicRkr:
|
|
case kPanasonicCkp: offset = kPanasonicAcQuietCkpOffset; break;
|
|
default: offset = kPanasonicAcQuietOffset;
|
|
}
|
|
if (on) setPowerful(false); // Powerful is mutually exclusive.
|
|
setBit(&remote_state[21], offset, on);
|
|
}
|
|
|
|
/// Get the Powerful (Turbo) setting of the A/C.
|
|
/// @return true, the setting is on. false, the setting is off.
|
|
bool IRPanasonicAc::getPowerful(void) {
|
|
switch (getModel()) {
|
|
case kPanasonicRkr:
|
|
case kPanasonicCkp:
|
|
return GETBIT8(remote_state[21], kPanasonicAcPowerfulCkpOffset);
|
|
default:
|
|
return GETBIT8(remote_state[21], kPanasonicAcPowerfulOffset);
|
|
}
|
|
}
|
|
|
|
/// Set the Powerful (Turbo) setting of the A/C.
|
|
/// @param[in] on true, the setting is on. false, the setting is off.
|
|
void IRPanasonicAc::setPowerful(const bool on) {
|
|
uint8_t offset;
|
|
switch (getModel()) {
|
|
case kPanasonicRkr:
|
|
case kPanasonicCkp: offset = kPanasonicAcPowerfulCkpOffset; break;
|
|
default: offset = kPanasonicAcPowerfulOffset;
|
|
}
|
|
|
|
if (on) setQuiet(false); // Quiet is mutually exclusive.
|
|
setBit(&remote_state[21], offset, on);
|
|
}
|
|
|
|
/// Convert standard (military/24hr) time to nr. of minutes since midnight.
|
|
/// @param[in] hours The hours component of the time.
|
|
/// @param[in] mins The minutes component of the time.
|
|
/// @return The nr of minutes since midnight.
|
|
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);
|
|
}
|
|
|
|
/// Get the time from a given pointer location.
|
|
/// @param[in] ptr A pointer to a time location in a state.
|
|
/// @return The time expressed as nr. of minutes past midnight.
|
|
/// @note Internal use only.
|
|
uint16_t IRPanasonicAc::_getTime(const uint8_t ptr[]) {
|
|
uint16_t result = (GETBITS8(
|
|
ptr[1], kLowNibble, kPanasonicAcTimeOverflowSize) <<
|
|
(kPanasonicAcTimeSize - kPanasonicAcTimeOverflowSize)) + ptr[0];
|
|
if (result == kPanasonicAcTimeSpecial) return 0;
|
|
return result;
|
|
}
|
|
|
|
/// Get the current clock time value.
|
|
/// @return The time expressed as nr. of minutes past midnight.
|
|
uint16_t IRPanasonicAc::getClock(void) { return _getTime(&remote_state[24]); }
|
|
|
|
/// Set the time at a given pointer location.
|
|
/// @param[in, out] ptr A pointer to a time location in a state.
|
|
/// @param[in] mins_since_midnight The time as nr. of minutes past midnight.
|
|
/// @param[in] round_down Do we round to the nearest 10 minute mark?
|
|
/// @note Internal use only.
|
|
void IRPanasonicAc::_setTime(uint8_t * const ptr,
|
|
const uint16_t mins_since_midnight,
|
|
const bool round_down) {
|
|
uint16_t corrected = std::min(mins_since_midnight, kPanasonicAcTimeMax);
|
|
if (round_down) corrected -= corrected % 10;
|
|
if (mins_since_midnight == kPanasonicAcTimeSpecial)
|
|
corrected = kPanasonicAcTimeSpecial;
|
|
ptr[0] = corrected;
|
|
setBits(&ptr[1], kLowNibble, kPanasonicAcTimeOverflowSize,
|
|
corrected >> (kPanasonicAcTimeSize - kPanasonicAcTimeOverflowSize));
|
|
}
|
|
|
|
/// Set the current clock time value.
|
|
/// @param[in] mins_since_midnight The time as nr. of minutes past midnight.
|
|
void IRPanasonicAc::setClock(const uint16_t mins_since_midnight) {
|
|
_setTime(&remote_state[24], mins_since_midnight, false);
|
|
}
|
|
|
|
/// Get the On Timer time value.
|
|
/// @return The time expressed as nr. of minutes past midnight.
|
|
uint16_t IRPanasonicAc::getOnTimer(void) { return _getTime(&remote_state[18]); }
|
|
|
|
/// Set/Enable the On Timer.
|
|
/// @param[in] mins_since_midnight The time as nr. of minutes past midnight.
|
|
/// @param[in] enable Do we enable the timer or not?
|
|
void IRPanasonicAc::setOnTimer(const uint16_t mins_since_midnight,
|
|
const bool enable) {
|
|
// Set the timer flag.
|
|
setBit(&remote_state[13], kPanasonicAcOnTimerOffset, enable);
|
|
// Store the time.
|
|
_setTime(&remote_state[18], mins_since_midnight, true);
|
|
}
|
|
|
|
/// Cancel the On Timer.
|
|
void IRPanasonicAc::cancelOnTimer(void) { setOnTimer(0, false); }
|
|
|
|
/// Check if the On Timer is Enabled.
|
|
/// @return true, the setting is on. false, the setting is off.
|
|
bool IRPanasonicAc::isOnTimerEnabled(void) {
|
|
return GETBIT8(remote_state[13], kPanasonicAcOnTimerOffset);
|
|
}
|
|
|
|
/// Get the Off Timer time value.
|
|
/// @return The time expressed as nr. of minutes past midnight.
|
|
uint16_t IRPanasonicAc::getOffTimer(void) {
|
|
uint16_t result = (GETBITS8(remote_state[20], 0, 7) << kNibbleSize) |
|
|
GETBITS8(remote_state[19], kHighNibble, kNibbleSize);
|
|
if (result == kPanasonicAcTimeSpecial) return 0;
|
|
return result;
|
|
}
|
|
|
|
/// Set/Enable the Off Timer.
|
|
/// @param[in] mins_since_midnight The time as nr. of minutes past midnight.
|
|
/// @param[in] enable Do we enable the timer or not?
|
|
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;
|
|
// Set the timer flag.
|
|
setBit(&remote_state[13], kPanasonicAcOffTimerOffset, enable);
|
|
// Store the time.
|
|
setBits(&remote_state[19], kHighNibble, kNibbleSize, corrected);
|
|
setBits(&remote_state[20], 0, 7, corrected >> kNibbleSize);
|
|
}
|
|
|
|
/// Cancel the Off Timer.
|
|
void IRPanasonicAc::cancelOffTimer(void) { setOffTimer(0, false); }
|
|
|
|
/// Check if the Off Timer is Enabled.
|
|
/// @return true, the setting is on. false, the setting is off.
|
|
bool IRPanasonicAc::isOffTimerEnabled(void) {
|
|
return GETBIT8(remote_state[13], kPanasonicAcOffTimerOffset);
|
|
}
|
|
|
|
/// Get the Ion (filter) setting of the A/C.
|
|
/// @return true, the setting is on. false, the setting is off.
|
|
bool IRPanasonicAc::getIon(void) {
|
|
switch (getModel()) {
|
|
case kPanasonicDke:
|
|
return GETBIT8(remote_state[kPanasonicAcIonFilterByte],
|
|
kPanasonicAcIonFilterOffset);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Set the Ion (filter) setting of the A/C.
|
|
/// @param[in] on true, the setting is on. false, the setting is off.
|
|
void IRPanasonicAc::setIon(const bool on) {
|
|
if (getModel() == kPanasonicDke)
|
|
setBit(&remote_state[kPanasonicAcIonFilterByte],
|
|
kPanasonicAcIonFilterOffset, on);
|
|
}
|
|
|
|
/// 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 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 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 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.
|
|
/// @param[in] position A stdAc::swingv_t position to convert.
|
|
/// @return The equivalent native horizontal swing position.
|
|
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:
|
|
case stdAc::swingv_t::kLow:
|
|
case stdAc::swingv_t::kLowest: return (uint8_t)position;
|
|
default: return kPanasonicAcSwingVAuto;
|
|
}
|
|
}
|
|
|
|
/// Convert a standard A/C horizontal swing into its native setting.
|
|
/// @param[in] position A stdAc::swingh_t position to convert.
|
|
/// @return The equivalent native horizontal swing position.
|
|
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 into its stdAc equivalent.
|
|
/// @param[in] mode The native setting to be converted.
|
|
/// @return The stdAc equivalent of the native setting.
|
|
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 into its stdAc equivalent.
|
|
/// @param[in] spd The native setting to be converted.
|
|
/// @return The stdAc equivalent of the native setting.
|
|
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 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 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 postion to it's common equivalent.
|
|
/// @param[in] pos A native position to convert.
|
|
/// @return The common vertical swing position.
|
|
stdAc::swingv_t IRPanasonicAc::toCommonSwingV(const uint8_t pos) {
|
|
if (pos >= kPanasonicAcSwingVHighest && pos <= kPanasonicAcSwingVLowest)
|
|
return (stdAc::swingv_t)pos;
|
|
else
|
|
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 IRPanasonicAc::toCommon(void) {
|
|
stdAc::state_t result;
|
|
result.protocol = decode_type_t::PANASONIC_AC;
|
|
result.model = getModel();
|
|
result.power = getPower();
|
|
result.mode = toCommonMode(getMode());
|
|
result.celsius = true;
|
|
result.degrees = getTemp();
|
|
result.fanspeed = toCommonFanSpeed(getFan());
|
|
result.swingv = toCommonSwingV(getSwingVertical());
|
|
result.swingh = toCommonSwingH(getSwingHorizontal());
|
|
result.quiet = getQuiet();
|
|
result.turbo = getPowerful();
|
|
result.filter = getIon();
|
|
// Not supported.
|
|
result.econo = false;
|
|
result.clean = 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 IRPanasonicAc::toString(void) {
|
|
String result = "";
|
|
result.reserve(180); // Reserve some heap for the string to reduce fragging.
|
|
result += addModelToString(decode_type_t::PANASONIC_AC, getModel(), false);
|
|
result += addBoolToString(getPower(), kPowerStr);
|
|
result += addModeToString(getMode(), kPanasonicAcAuto, kPanasonicAcCool,
|
|
kPanasonicAcHeat, kPanasonicAcDry, kPanasonicAcFan);
|
|
result += addTempToString(getTemp());
|
|
result += addFanToString(getFan(), kPanasonicAcFanMax, kPanasonicAcFanMin,
|
|
kPanasonicAcFanAuto, kPanasonicAcFanAuto,
|
|
kPanasonicAcFanMed);
|
|
result += addSwingVToString(getSwingVertical(), kPanasonicAcSwingVAuto,
|
|
kPanasonicAcSwingVHighest,
|
|
kPanasonicAcSwingVHigh,
|
|
kPanasonicAcSwingVAuto, // Upper Middle is unused
|
|
kPanasonicAcSwingVMiddle,
|
|
kPanasonicAcSwingVAuto, // Lower Middle is unused
|
|
kPanasonicAcSwingVLow,
|
|
kPanasonicAcSwingVLowest,
|
|
// Below are unused.
|
|
kPanasonicAcSwingVAuto,
|
|
kPanasonicAcSwingVAuto,
|
|
kPanasonicAcSwingVAuto,
|
|
kPanasonicAcSwingVAuto);
|
|
switch (getModel()) {
|
|
case kPanasonicJke:
|
|
case kPanasonicCkp:
|
|
break; // No Horizontal Swing support.
|
|
default:
|
|
result += addSwingHToString(getSwingHorizontal(), kPanasonicAcSwingHAuto,
|
|
kPanasonicAcSwingHFullLeft,
|
|
kPanasonicAcSwingHLeft,
|
|
kPanasonicAcSwingHMiddle,
|
|
kPanasonicAcSwingHRight,
|
|
kPanasonicAcSwingHFullRight,
|
|
// Below are unused.
|
|
kPanasonicAcSwingHAuto,
|
|
kPanasonicAcSwingHAuto,
|
|
kPanasonicAcSwingHAuto,
|
|
kPanasonicAcSwingHAuto,
|
|
kPanasonicAcSwingHAuto);
|
|
}
|
|
result += addBoolToString(getQuiet(), kQuietStr);
|
|
result += addBoolToString(getPowerful(), kPowerfulStr);
|
|
if (getModel() == kPanasonicDke)
|
|
result += addBoolToString(getIon(), kIonStr);
|
|
result += addLabeledString(minsToString(getClock()), kClockStr);
|
|
result += addLabeledString(
|
|
isOnTimerEnabled() ? minsToString(getOnTimer()) : kOffStr,
|
|
kOnTimerStr);
|
|
result += addLabeledString(
|
|
isOffTimerEnabled() ? minsToString(getOffTimer()) : kOffStr,
|
|
kOffTimerStr);
|
|
return result;
|
|
}
|
|
|
|
#if DECODE_PANASONIC_AC
|
|
/// Decode the supplied Panasonic AC message.
|
|
/// Status: STABLE / Works with real device(s).
|
|
/// @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.
|
|
bool IRrecv::decodePanasonicAC(decode_results *results, uint16_t offset,
|
|
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 + offset)
|
|
return false; // Can't possibly be a valid PANASONIC_AC message.
|
|
|
|
// 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 = decode_type_t::PANASONIC_AC;
|
|
results->bits = nbits;
|
|
return true;
|
|
}
|
|
#endif // DECODE_PANASONIC_AC
|
|
|
|
#if SEND_PANASONIC_AC32
|
|
/// Send a Panasonic AC 32/16bit formatted message.
|
|
/// Status: STABLE / Confirmed working.
|
|
/// @param[in] data containing the IR command.
|
|
/// @param[in] nbits Nr. of bits to send. Usually kPanasonicAc32Bits
|
|
/// @param[in] repeat Nr. of times the message is to be repeated.
|
|
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1307
|
|
void IRsend::sendPanasonicAC32(const uint64_t data, const uint16_t nbits,
|
|
const uint16_t repeat) {
|
|
uint16_t section_bits;
|
|
uint16_t sections;
|
|
uint16_t blocks;
|
|
// Calculate the section, block, and bit sizes based on the requested bit size
|
|
if (nbits > kPanasonicAc32Bits / 2) { // A long message
|
|
section_bits = nbits / kPanasonicAc32Sections;
|
|
sections = kPanasonicAc32Sections;
|
|
blocks = kPanasonicAc32BlocksPerSection;
|
|
} else { // A short message
|
|
section_bits = nbits;
|
|
sections = kPanasonicAc32Sections - 1;
|
|
blocks = kPanasonicAc32BlocksPerSection + 1;
|
|
}
|
|
for (uint16_t r = 0; r <= repeat; r++) {
|
|
for (uint8_t section = 0; section < sections; section++) {
|
|
uint64_t section_data;
|
|
section_data = GETBITS64(data, section_bits * (sections - section - 1),
|
|
section_bits);
|
|
|
|
// Duplicate bytes in the data.
|
|
uint64_t expanded_data = 0;
|
|
for (uint8_t i = 0; i < sizeof(expanded_data); i++) {
|
|
const uint8_t first_byte = section_data >> 56;
|
|
for (uint8_t i = 0; i < 2; i++)
|
|
expanded_data = (expanded_data << 8) | first_byte;
|
|
section_data <<= 8;
|
|
}
|
|
// Two data blocks per section (i.e. 1 + a repeat)
|
|
sendGeneric(kPanasonicAc32HdrMark, kPanasonicAc32HdrSpace, // Header
|
|
kPanasonicAc32BitMark, kPanasonicAc32OneSpace, // Data
|
|
kPanasonicAc32BitMark, kPanasonicAc32ZeroSpace,
|
|
0, 0, // No Footer
|
|
expanded_data, section_bits * 2, kPanasonicFreq, false,
|
|
blocks - 1, // Repeat
|
|
50);
|
|
// Section Footer
|
|
sendGeneric(kPanasonicAc32HdrMark, kPanasonicAc32HdrSpace, // Header
|
|
0, 0, 0, 0, // No Data
|
|
kPanasonicAc32BitMark, kPanasonicAc32SectionGap, // Footer
|
|
data, 0, // No data (bits)
|
|
kPanasonicFreq, true, 0, 50);
|
|
}
|
|
}
|
|
}
|
|
#endif // SEND_PANASONIC_AC32
|
|
|
|
#if DECODE_PANASONIC_AC32
|
|
/// Decode the supplied Panasonic AC 32/16bit message.
|
|
/// Status: STABLE / Confirmed working.
|
|
/// @param[in,out] results Ptr to the data to decode & where to store the decode
|
|
/// result.
|
|
/// @param[in] offset The starting index to use when attempting to decode the
|
|
/// raw data. Typically/Defaults to kStartOffset.
|
|
/// @param[in] nbits The number of data bits to expect.
|
|
/// Typically: kPanasonicAc32Bits or kPanasonicAc32Bits/2
|
|
/// @param[in] strict Flag indicating if we should perform strict matching.
|
|
/// @return A boolean. True if it can decode it, false if it can't.
|
|
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1307
|
|
/// @note Protocol has two known configurations:
|
|
/// (long)
|
|
/// Two sections of identical 32 bit data block pairs. ie. (32+32)+(32+32)=128
|
|
/// or
|
|
/// (short)
|
|
/// A single section of 3 x identical 32 bit data blocks i.e. (32+32+32)=96
|
|
/// Each data block also has a pair of 8 bits repeated identical bits.
|
|
/// e.g. (8+8)+(8+8)=32
|
|
///
|
|
/// So each long version really only has 32 unique bits, and the short version
|
|
/// really only has 16 unique bits.
|
|
bool IRrecv::decodePanasonicAC32(decode_results *results, uint16_t offset,
|
|
const uint16_t nbits, const bool strict) {
|
|
if (strict && (nbits != kPanasonicAc32Bits &&
|
|
nbits != kPanasonicAc32Bits / 2))
|
|
return false; // Not strictly a valid bit size.
|
|
|
|
// Determine if this is a long or a short message we are looking for.
|
|
const bool is_long = (nbits > kPanasonicAc32Bits / 2);
|
|
const uint16_t min_length = is_long ?
|
|
kPanasonicAc32Sections * kPanasonicAc32BlocksPerSection *
|
|
((2 * nbits) + kHeader + kFooter) - 1 + offset :
|
|
(kPanasonicAc32BlocksPerSection + 1) * ((4 * nbits) + kHeader) +
|
|
kFooter - 1 + offset;
|
|
|
|
if (results->rawlen < min_length)
|
|
return false; // Can't possibly be a valid message.
|
|
|
|
// Calculate the parameters for the decode based on it's length.
|
|
uint16_t sections;
|
|
uint16_t blocks_per_section;
|
|
if (is_long) {
|
|
sections = kPanasonicAc32Sections;
|
|
blocks_per_section = kPanasonicAc32BlocksPerSection;
|
|
} else {
|
|
sections = kPanasonicAc32Sections - 1;
|
|
blocks_per_section = kPanasonicAc32BlocksPerSection + 1;
|
|
}
|
|
const uint16_t bits_per_block = nbits / sections;
|
|
|
|
uint64_t data = 0;
|
|
uint64_t section_data = 0;
|
|
uint32_t prev_section_data;
|
|
|
|
// Match all the expected data blocks.
|
|
for (uint16_t block = 0;
|
|
block < sections * blocks_per_section;
|
|
block++) {
|
|
prev_section_data = section_data;
|
|
uint16_t used = matchGeneric(results->rawbuf + offset, §ion_data,
|
|
results->rawlen - offset, bits_per_block * 2,
|
|
kPanasonicAc32HdrMark, kPanasonicAc32HdrSpace,
|
|
kPanasonicAc32BitMark, kPanasonicAc32OneSpace,
|
|
kPanasonicAc32BitMark, kPanasonicAc32ZeroSpace,
|
|
0, 0, // No Footer
|
|
false, kUseDefTol, kMarkExcess, false);
|
|
if (!used) return false;
|
|
offset += used;
|
|
// Is it the first block of the section?
|
|
if (block % blocks_per_section == 0) {
|
|
// The protocol repeats each byte twice, so to shrink the code we
|
|
// remove the duplicate bytes in the collected data. We only need to do
|
|
// this for the first block in a section.
|
|
uint64_t shrunk_data = 0;
|
|
uint64_t data_copy = section_data;
|
|
for (uint8_t i = 0; i < sizeof(data_copy); i += 2) {
|
|
const uint8_t first_byte = GETBITS64(data_copy,
|
|
(sizeof(data_copy) - 1) * 8, 8);
|
|
shrunk_data = (shrunk_data << 8) | first_byte;
|
|
// Compliance
|
|
if (strict) {
|
|
// Every second byte must be a duplicate of the previous.
|
|
const uint8_t next_byte = GETBITS64(data_copy,
|
|
(sizeof(data_copy) - 2) * 8, 8);
|
|
if (first_byte != next_byte) return false;
|
|
}
|
|
data_copy <<= 16;
|
|
}
|
|
// Keep the data from the first of the block in the section.
|
|
data = (data << bits_per_block) | shrunk_data;
|
|
} else { // Not the first block in a section.
|
|
// Compliance
|
|
if (strict)
|
|
// Compare the data from the blocks in pairs.
|
|
if (section_data != prev_section_data) return false;
|
|
// Look for the section footer at the end of the blocks.
|
|
if ((block + 1) % blocks_per_section == 0) {
|
|
uint64_t junk;
|
|
used = matchGeneric(results->rawbuf + offset, &junk,
|
|
results->rawlen - offset, 0,
|
|
// Header
|
|
kPanasonicAc32HdrMark, kPanasonicAc32HdrSpace,
|
|
// No Data
|
|
0, 0,
|
|
0, 0,
|
|
// Footer
|
|
kPanasonicAc32BitMark, kPanasonicAc32SectionGap,
|
|
true);
|
|
if (!used) return false;
|
|
offset += used;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Success
|
|
results->value = data;
|
|
results->decode_type = decode_type_t::PANASONIC_AC32;
|
|
results->bits = nbits;
|
|
results->address = 0;
|
|
results->command = 0;
|
|
return true;
|
|
}
|
|
#endif // DECODE_PANASONIC_AC32
|
|
|
|
/// 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?
|
|
IRPanasonicAc32::IRPanasonicAc32(const uint16_t pin, const bool inverted,
|
|
const bool use_modulation)
|
|
: _irsend(pin, inverted, use_modulation) { stateReset(); }
|
|
|
|
#if SEND_PANASONIC_AC32
|
|
/// Send the current internal state as IR messages.
|
|
/// @param[in] repeat Nr. of times the message will be repeated.
|
|
void IRPanasonicAc32::send(const uint16_t repeat) {
|
|
_irsend.sendPanasonicAC32(getRaw(), kPanasonicAc32Bits, repeat);
|
|
}
|
|
#endif // SEND_PANASONIC_AC32
|
|
|
|
/// Set up hardware to be able to send a message.
|
|
void IRPanasonicAc32::begin(void) { _irsend.begin(); }
|
|
|
|
/// Get a copy of the internal state/code for this protocol.
|
|
/// @return The code for this protocol based on the current internal state.
|
|
uint32_t IRPanasonicAc32::getRaw(void) const { return _.raw; }
|
|
|
|
/// Set the internal state from a valid code for this protocol.
|
|
/// @param[in] state A valid code for this protocol.
|
|
void IRPanasonicAc32::setRaw(const uint32_t state) { _.raw = state; }
|
|
|
|
/// Reset the state of the remote to a known good state/sequence.
|
|
void IRPanasonicAc32::stateReset(void) { setRaw(kPanasonicAc32KnownGood); }
|
|
|
|
/// Set the Power Toggle setting of the A/C.
|
|
/// @param[in] on true, the setting is on. false, the setting is off.
|
|
void IRPanasonicAc32::setPowerToggle(const bool on) { _.PowerToggle = !on; }
|
|
|
|
/// Get the Power Toggle setting of the A/C.
|
|
/// @return true, the setting is on. false, the setting is off.
|
|
bool IRPanasonicAc32::getPowerToggle(void) const { return !_.PowerToggle; }
|
|
|
|
/// Set the desired temperature.
|
|
/// @param[in] degrees The temperature in degrees celsius.
|
|
void IRPanasonicAc32::setTemp(const uint8_t degrees) {
|
|
uint8_t temp = std::max((uint8_t)kPanasonicAcMinTemp, degrees);
|
|
temp = std::min((uint8_t)kPanasonicAcMaxTemp, temp);
|
|
_.Temp = temp - (kPanasonicAcMinTemp - 1);
|
|
}
|
|
|
|
/// Get the current desired temperature setting.
|
|
/// @return The current setting for temp. in degrees celsius.
|
|
uint8_t IRPanasonicAc32::getTemp(void) const {
|
|
return _.Temp + (kPanasonicAcMinTemp - 1);
|
|
}
|
|
|
|
/// Get the operating mode setting of the A/C.
|
|
/// @return The current operating mode setting.
|
|
uint8_t IRPanasonicAc32::getMode(void) const { return _.Mode; }
|
|
|
|
/// Set the operating mode of the A/C.
|
|
/// @param[in] mode The desired operating mode.
|
|
/// @note If we get an unexpected mode, default to AUTO.
|
|
void IRPanasonicAc32::setMode(const uint8_t mode) {
|
|
switch (mode) {
|
|
case kPanasonicAc32Auto:
|
|
case kPanasonicAc32Cool:
|
|
case kPanasonicAc32Dry:
|
|
case kPanasonicAc32Heat:
|
|
case kPanasonicAc32Fan:
|
|
_.Mode = mode;
|
|
break;
|
|
default: _.Mode = kPanasonicAc32Auto;
|
|
}
|
|
}
|
|
|
|
/// 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 IRPanasonicAc32::convertMode(const stdAc::opmode_t mode) {
|
|
switch (mode) {
|
|
case stdAc::opmode_t::kCool: return kPanasonicAc32Cool;
|
|
case stdAc::opmode_t::kHeat: return kPanasonicAc32Heat;
|
|
case stdAc::opmode_t::kDry: return kPanasonicAc32Dry;
|
|
case stdAc::opmode_t::kFan: return kPanasonicAc32Fan;
|
|
default: return kPanasonicAc32Auto;
|
|
}
|
|
}
|
|
|
|
/// 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 IRPanasonicAc32::toCommonMode(const uint8_t mode) {
|
|
switch (mode) {
|
|
case kPanasonicAc32Cool: return stdAc::opmode_t::kCool;
|
|
case kPanasonicAc32Heat: return stdAc::opmode_t::kHeat;
|
|
case kPanasonicAc32Dry: return stdAc::opmode_t::kDry;
|
|
case kPanasonicAc32Fan: return stdAc::opmode_t::kFan;
|
|
default: return stdAc::opmode_t::kAuto;
|
|
}
|
|
}
|
|
|
|
/// Set the speed of the fan.
|
|
/// @param[in] speed The desired setting.
|
|
void IRPanasonicAc32::setFan(const uint8_t speed) {
|
|
switch (speed) {
|
|
case kPanasonicAc32FanMin:
|
|
case kPanasonicAc32FanLow:
|
|
case kPanasonicAc32FanMed:
|
|
case kPanasonicAc32FanHigh:
|
|
case kPanasonicAc32FanMax:
|
|
case kPanasonicAc32FanAuto:
|
|
_.Fan = speed;
|
|
break;
|
|
default: _.Fan = kPanasonicAc32FanAuto;
|
|
}
|
|
}
|
|
|
|
/// Get the current fan speed setting.
|
|
/// @return The current fan speed.
|
|
uint8_t IRPanasonicAc32::getFan(void) const { return _.Fan; }
|
|
|
|
/// 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 IRPanasonicAc32::toCommonFanSpeed(const uint8_t spd) {
|
|
switch (spd) {
|
|
case kPanasonicAc32FanMax: return stdAc::fanspeed_t::kMax;
|
|
case kPanasonicAc32FanHigh: return stdAc::fanspeed_t::kHigh;
|
|
case kPanasonicAc32FanMed: return stdAc::fanspeed_t::kMedium;
|
|
case kPanasonicAc32FanLow: return stdAc::fanspeed_t::kLow;
|
|
case kPanasonicAc32FanMin: return stdAc::fanspeed_t::kMin;
|
|
default: return stdAc::fanspeed_t::kAuto;
|
|
}
|
|
}
|
|
|
|
/// 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 IRPanasonicAc32::convertFan(const stdAc::fanspeed_t speed) {
|
|
switch (speed) {
|
|
case stdAc::fanspeed_t::kMin: return kPanasonicAc32FanMin;
|
|
case stdAc::fanspeed_t::kLow: return kPanasonicAc32FanLow;
|
|
case stdAc::fanspeed_t::kMedium: return kPanasonicAc32FanMed;
|
|
case stdAc::fanspeed_t::kHigh: return kPanasonicAc32FanHigh;
|
|
case stdAc::fanspeed_t::kMax: return kPanasonicAc32FanMax;
|
|
default: return kPanasonicAc32FanAuto;
|
|
}
|
|
}
|
|
|
|
/// Get the current horizontal swing setting.
|
|
/// @return The current position it is set to.
|
|
bool IRPanasonicAc32::getSwingHorizontal(void) const { return _.SwingH; }
|
|
|
|
/// Control the horizontal swing setting.
|
|
/// @param[in] on true, the setting is on. false, the setting is off.
|
|
void IRPanasonicAc32::setSwingHorizontal(const bool on) { _.SwingH = on; }
|
|
|
|
/// Get the current vertical swing setting.
|
|
/// @return The current position it is set to.
|
|
uint8_t IRPanasonicAc32::getSwingVertical(void) const { return _.SwingV; }
|
|
|
|
/// Control the vertical swing setting.
|
|
/// @param[in] pos The position to set the vertical swing to.
|
|
void IRPanasonicAc32::setSwingVertical(const uint8_t pos) {
|
|
uint8_t elevation = pos;
|
|
if (elevation != kPanasonicAc32SwingVAuto) {
|
|
elevation = std::max(elevation, kPanasonicAcSwingVHighest);
|
|
elevation = std::min(elevation, kPanasonicAcSwingVLowest);
|
|
}
|
|
_.SwingV = elevation;
|
|
}
|
|
|
|
/// 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 IRPanasonicAc32::toCommonSwingV(const uint8_t pos) {
|
|
return IRPanasonicAc::toCommonSwingV(pos);
|
|
}
|
|
|
|
/// Convert a standard A/C vertical swing into its native setting.
|
|
/// @param[in] position A stdAc::swingv_t position to convert.
|
|
/// @return The equivalent native horizontal swing position.
|
|
uint8_t IRPanasonicAc32::convertSwingV(const stdAc::swingv_t position) {
|
|
switch (position) {
|
|
case stdAc::swingv_t::kHighest:
|
|
case stdAc::swingv_t::kHigh:
|
|
case stdAc::swingv_t::kMiddle:
|
|
case stdAc::swingv_t::kLow:
|
|
case stdAc::swingv_t::kLowest: return (uint8_t)position;
|
|
default: return kPanasonicAc32SwingVAuto;
|
|
}
|
|
}
|
|
|
|
/// Convert the current internal state into a human readable string.
|
|
/// @return A human readable string.
|
|
String IRPanasonicAc32::toString(void) const {
|
|
String result = "";
|
|
result.reserve(110);
|
|
result += addBoolToString(getPowerToggle(), kPowerToggleStr, false);
|
|
result += addModeToString(_.Mode, kPanasonicAc32Auto, kPanasonicAc32Cool,
|
|
kPanasonicAc32Heat, kPanasonicAc32Dry,
|
|
kPanasonicAc32Fan);
|
|
result += addTempToString(getTemp());
|
|
result += addFanToString(_.Fan, kPanasonicAc32FanHigh, kPanasonicAc32FanLow,
|
|
kPanasonicAc32FanAuto, kPanasonicAc32FanMin,
|
|
kPanasonicAc32FanMed, kPanasonicAc32FanMax);
|
|
result += addBoolToString(_.SwingH, kSwingHStr);
|
|
result += addSwingVToString(getSwingVertical(),
|
|
kPanasonicAc32SwingVAuto,
|
|
kPanasonicAcSwingVHighest,
|
|
kPanasonicAcSwingVHigh,
|
|
kPanasonicAc32SwingVAuto, // Upper Middle unused
|
|
kPanasonicAcSwingVMiddle,
|
|
kPanasonicAc32SwingVAuto, // Lower Middle unused
|
|
kPanasonicAcSwingVLow,
|
|
kPanasonicAcSwingVLowest,
|
|
// Below are unused.
|
|
kPanasonicAc32SwingVAuto,
|
|
kPanasonicAc32SwingVAuto,
|
|
kPanasonicAc32SwingVAuto,
|
|
kPanasonicAc32SwingVAuto);
|
|
return result;
|
|
}
|
|
|
|
/// Convert the current internal state into its stdAc::state_t equivalent.
|
|
/// @param[in] prev Ptr to the previous state if required.
|
|
/// @return The stdAc equivalent of the native settings.
|
|
stdAc::state_t IRPanasonicAc32::toCommon(const stdAc::state_t *prev) const {
|
|
stdAc::state_t result;
|
|
// Start with the previous state if given it.
|
|
if (prev != NULL) {
|
|
result = *prev;
|
|
} else {
|
|
// Set defaults for non-zero values that are not implicitly set for when
|
|
// there is no previous state.
|
|
// e.g. Any setting that toggles should probably go here.
|
|
result.power = false;
|
|
}
|
|
result.protocol = decode_type_t::PANASONIC_AC32;
|
|
result.model = -1;
|
|
if (getPowerToggle()) result.power = !result.power;
|
|
result.mode = toCommonMode(getMode());
|
|
result.celsius = true;
|
|
result.degrees = getTemp();
|
|
result.fanspeed = toCommonFanSpeed(getFan());
|
|
result.swingv = toCommonSwingV(getSwingVertical());
|
|
result.swingh = getSwingHorizontal() ? stdAc::swingh_t::kAuto
|
|
: stdAc::swingh_t::kOff;
|
|
// Not supported.
|
|
result.quiet = false;
|
|
result.turbo = false;
|
|
result.filter = false;
|
|
result.econo = false;
|
|
result.clean = false;
|
|
result.light = false;
|
|
result.beep = false;
|
|
result.sleep = -1;
|
|
result.clock = -1;
|
|
return result;
|
|
}
|