Skip to content

Commit

Permalink
Add basic support for Panasonic A/C 32bit/16bit protocol. (crankyoldg…
Browse files Browse the repository at this point in the history
…it#1316)

* `sendPanasonicAC32()` & `decodePanasonicAC32()` added.
* Support the short (16 bit) version of the messages used for swing, quiet, & powerful.
* Unit tests for the above.
* LSBF order determined by Temperature ranges.

For crankyoldgit#1307
  • Loading branch information
crankyoldgit authored Nov 1, 2020
1 parent 18c725d commit 43ad2ad
Show file tree
Hide file tree
Showing 10 changed files with 458 additions and 40 deletions.
7 changes: 7 additions & 0 deletions src/IRrecv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,13 @@ bool IRrecv::decode(decode_results *results, irparams_t *save,
DPRINTLN("Attempting EliteScreens decode");
if (decodeElitescreens(results, offset)) return true;
#endif // DECODE_ELITESCREENS
#if DECODE_PANASONIC_AC32
DPRINTLN("Attempting Panasonic AC (32bit) long decode");
if (decodePanasonicAC32(results, offset, kPanasonicAc32Bits)) return true;
DPRINTLN("Attempting Panasonic AC (32bit) short decode");
if (decodePanasonicAC32(results, offset, kPanasonicAc32Bits / 2))
return true;
#endif // DECODE_PANASONIC_AC32
// Typically new protocols are added above this line.
}
#if DECODE_HASH
Expand Down
8 changes: 7 additions & 1 deletion src/IRrecv.h
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,13 @@ class IRrecv {
uint16_t offset = kStartOffset,
const uint16_t nbits = kPanasonicAcBits,
const bool strict = true);
#endif
#endif // DECODE_PANASONIC_AC
#if DECODE_PANASONIC_AC32
bool decodePanasonicAC32(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kPanasonicAc32Bits,
const bool strict = true);
#endif // DECODE_PANASONIC_AC32
#if DECODE_PIONEER
bool decodePioneer(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kPioneerBits,
Expand Down
11 changes: 10 additions & 1 deletion src/IRremoteESP8266.h
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,13 @@
#define SEND_PANASONIC_AC _IR_ENABLE_DEFAULT_
#endif // SEND_PANASONIC_AC

#ifndef DECODE_PANASONIC_AC32
#define DECODE_PANASONIC_AC32 _IR_ENABLE_DEFAULT_
#endif // DECODE_PANASONIC_AC32
#ifndef SEND_PANASONIC_AC32
#define SEND_PANASONIC_AC32 _IR_ENABLE_DEFAULT_
#endif // SEND_PANASONIC_AC32

#ifndef DECODE_MWM
#define DECODE_MWM _IR_ENABLE_DEFAULT_
#endif // DECODE_MWM
Expand Down Expand Up @@ -859,8 +866,9 @@ enum decode_type_t {
TECHNIBEL_AC,
MIRAGE,
ELITESCREENS, // 95
PANASONIC_AC32,
// Add new entries before this one, and update it to point to the last entry.
kLastDecodeType = ELITESCREENS,
kLastDecodeType = PANASONIC_AC32,
};

// Message lengths & required repeat values
Expand Down Expand Up @@ -1022,6 +1030,7 @@ const uint16_t kPanasonicAcStateShortLength = 16;
const uint16_t kPanasonicAcBits = kPanasonicAcStateLength * 8;
const uint16_t kPanasonicAcShortBits = kPanasonicAcStateShortLength * 8;
const uint16_t kPanasonicAcDefaultRepeat = kNoRepeat;
const uint16_t kPanasonicAc32Bits = 32;
const uint16_t kPioneerBits = 64;
const uint16_t kProntoMinLength = 6;
const uint16_t kRC5RawBits = 14;
Expand Down
8 changes: 7 additions & 1 deletion src/IRsend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,7 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) {
case EPSON:
case NEC:
case NEC_LIKE:
case PANASONIC_AC32:
case SAMSUNG:
case SHERWOOD:
case WHYNTER:
Expand Down Expand Up @@ -926,7 +927,12 @@ bool IRsend::send(const decode_type_t type, const uint64_t data,
case PANASONIC:
sendPanasonic64(data, nbits, min_repeat);
break;
#endif
#endif // SEND_PANASONIC
#if SEND_PANASONIC_AC32
case PANASONIC_AC32:
sendPanasonicAC32(data, nbits, min_repeat);
break;
#endif // SEND_PANASONIC_AC32
#if SEND_PIONEER
case PIONEER:
sendPioneer(data, nbits, min_repeat);
Expand Down
7 changes: 6 additions & 1 deletion src/IRsend.h
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,12 @@ class IRsend {
void sendPanasonicAC(const unsigned char data[],
const uint16_t nbytes = kPanasonicAcStateLength,
const uint16_t repeat = kPanasonicAcDefaultRepeat);
#endif
#endif // SEND_PANASONIC_AC
#if SEND_PANASONIC_AC32
void sendPanasonicAC32(const uint64_t data,
const uint16_t nbits = kPanasonicAc32Bits,
const uint16_t repeat = kPanasonicAcDefaultRepeat);
#endif // SEND_PANASONIC_AC32
#if SEND_PIONEER
void sendPioneer(const uint64_t data, const uint16_t nbits = kPioneerBits,
const uint16_t repeat = kNoRepeat);
Expand Down
1 change: 1 addition & 0 deletions src/IRtext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,5 +274,6 @@ const PROGMEM char *kAllProtocolNamesStr =
D_STR_TECHNIBEL_AC "\x0"
D_STR_MIRAGE "\x0"
D_STR_ELITESCREENS "\x0"
D_STR_PANASONIC_AC32 "\x0"
///< New protocol strings should be added just above this line.
"\x0"; ///< This string requires double null termination.
223 changes: 200 additions & 23 deletions src/ir_Panasonic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,28 @@

// Constants
/// @see https://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 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;
Expand Down Expand Up @@ -926,3 +922,184 @@ bool IRrecv::decodePanasonicAC(decode_results *results, uint16_t offset,
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, &section_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
2 changes: 2 additions & 0 deletions src/ir_Panasonic.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
// Brand: Panasonic, Model: A75C2616-1 remote (PANASONIC_AC DKE/3)
// Brand: Panasonic, Model: A75C3704 remote (PANASONIC_AC DKE/3)
// Brand: Panasonic, Model: A75C3747 remote (PANASONIC_AC JKE/4)
// Brand: Panasonic, Model: CS-E9CKP series A/C (PANASONIC_AC32)
// Brand: Panasonic, Model: A75C2295 remote (PANASONIC_AC32)

#ifndef IR_PANASONIC_H_
#define IR_PANASONIC_H_
Expand Down
3 changes: 3 additions & 0 deletions src/locale/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,9 @@
#ifndef D_STR_PANASONIC_AC
#define D_STR_PANASONIC_AC "PANASONIC_AC"
#endif // D_STR_PANASONIC_AC
#ifndef D_STR_PANASONIC_AC32
#define D_STR_PANASONIC_AC32 D_STR_PANASONIC_AC"32"
#endif // D_STR_PANASONIC_AC32
#ifndef D_STR_PIONEER
#define D_STR_PIONEER "PIONEER"
#endif // D_STR_PIONEER
Expand Down
Loading

0 comments on commit 43ad2ad

Please sign in to comment.