Skip to content

Commit

Permalink
Add basic support for HITACHI_AC3 protocol. (#1063)
Browse files Browse the repository at this point in the history
* Supports Hitachi PC-LH3B
* Add send & decode routines.
  - Support all known state/bit sizes captured so far.
* Update ancillary support routines.
* Add synthetic and real unit test cases.
* very basic IRHitachiAc3 class added.
  - Enough code to verify the checksum/integrity check for the protocol.
  - Add integrity check to `decodeHitachiAc3()`

For #1060
  • Loading branch information
crankyoldgit authored Mar 24, 2020
1 parent 6d265b5 commit a31c6bd
Show file tree
Hide file tree
Showing 11 changed files with 631 additions and 19 deletions.
5 changes: 3 additions & 2 deletions SupportedProtocols.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!--- WARNING: Do NOT edit this file directly.
It is generated by './tools/scrape_supported_devices.py'.
Last generated: Sat Mar 7 23:57:26 2020 --->
Last generated: Tue Mar 17 13:45:51 2020 --->
# IR Protocols supported by this library

| Protocol | Brand | Model | A/C Model | Detailed A/C Support |
Expand Down Expand Up @@ -28,7 +28,7 @@
| [Gree](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.cpp) | **[RusClimate](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.h)** | EACS/I-09HAR_X/N3 A/C<BR>YAW1F remote | YAW1F<BR>YBOFB | Yes |
| [Gree](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.cpp) | **[Ultimate](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Gree.h)** | Heat Pump | YAW1F<BR>YBOFB | Yes |
| [Haier](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Haier.cpp) | **[Haier](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Haier.h)** | HSU-09HMC203 A/C<BR>HSU07-HEA03 remote<BR>YR-W02 remote | | Yes |
| [Hitachi](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Hitachi.cpp) | **[Hitachi](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Hitachi.h)** | LT0541-HTA remote<BR>RAR-8P2 remote<BR>RAS-35THA6 remote<BR>RAS-AJ25H A/C<BR>Series VI A/C (Circa 2007) | | Yes |
| [Hitachi](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Hitachi.cpp) | **[Hitachi](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Hitachi.h)** | LT0541-HTA remote<BR>PC-LH3B (HITACHI_AC3)<BR>RAR-8P2 remote<BR>RAS-35THA6 remote<BR>RAS-AJ25H A/C<BR>Series VI A/C (Circa 2007) | | Yes |
| [Inax](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Inax.cpp) | **Lixil** | Inax DT-BA283 Toilet | | - |
| [JVC](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_JVC.cpp) | **Unknown** | | | - |
| [Kelvinator](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelvinator.cpp) | **[Green](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Kelvinator.h)** | YAPOF3 remote | | Yes |
Expand Down Expand Up @@ -107,6 +107,7 @@
- HITACHI_AC
- HITACHI_AC1
- HITACHI_AC2
- HITACHI_AC3
- HITACHI_AC424
- INAX
- JVC
Expand Down
17 changes: 17 additions & 0 deletions examples/IRMQTTServer/IRMQTTServer.ino
Original file line number Diff line number Diff line change
Expand Up @@ -1518,6 +1518,23 @@ bool parseStringAndSendAirCon(IRsend *irsend, const decode_type_t irType,
// Lastly, it should never exceed the maximum "normal" size.
stateSize = std::min(stateSize, kFujitsuAcStateLength);
break;
case HITACHI_AC3:
// HitachiAc3 has two distinct & different size states, so make a best
// guess which one we are being presented with based on the number of
// hexadecimal digits provided. i.e. Zero-pad if you need to to get
// the correct length/byte size.
stateSize = inputLength / 2; // Every two hex chars is a byte.
// Use at least the minimum size.
stateSize = std::max(stateSize,
(uint16_t) (kHitachiAc3MinStateLength));
// If we think it isn't a "short" message.
if (stateSize > kHitachiAc3MinStateLength)
// Then it probably the "normal" size.
stateSize = std::max(stateSize,
(uint16_t) (kHitachiAc3StateLength));
// Lastly, it should never exceed the maximum "normal" size.
stateSize = std::min(stateSize, kHitachiAc3StateLength);
break;
case MWM:
// MWM has variable size states, so make a best guess
// which one we are being presented with based on the number of
Expand Down
26 changes: 20 additions & 6 deletions src/IRrecv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -641,10 +641,28 @@ bool IRrecv::decode(decode_results *results, irparams_t *save,
if (decodeHaierACYRW02(results, offset)) return true;
#endif
#if DECODE_HITACHI_AC424
// HitachiAc424 should be checked before HitachiAC & HitachiAC2
// HitachiAc424 should be checked before HitachiAC, HitachiAC2,
// & HitachiAC184
DPRINTLN("Attempting Hitachi AC 424 decode");
if (decodeHitachiAc424(results, offset, kHitachiAc424Bits)) return true;
#endif // DECODE_HITACHI_AC2
#endif // DECODE_HITACHI_AC424
#if DECODE_MITSUBISHI136
// Needs to happen before HitachiAc3 decode.
DPRINTLN("Attempting Mitsubishi136 decode");
if (decodeMitsubishi136(results, offset)) return true;
#endif // DECODE_MITSUBISHI136
#if DECODE_HITACHI_AC3
// HitachiAc3 should be checked before HitachiAC & HitachiAC2
// Attempt normal before the short version.
DPRINTLN("Attempting Hitachi AC3 decode");
// Order these in decreasing bit size, as it is more optimal.
if (decodeHitachiAc3(results, offset, kHitachiAc3Bits) ||
decodeHitachiAc3(results, offset, kHitachiAc3Bits - 4 * 8) ||
decodeHitachiAc3(results, offset, kHitachiAc3Bits - 6 * 8) ||
decodeHitachiAc3(results, offset, kHitachiAc3MinBits + 2 * 8) ||
decodeHitachiAc3(results, offset, kHitachiAc3MinBits))
return true;
#endif // DECODE_HITACHI_AC3
#if DECODE_HITACHI_AC2
// HitachiAC2 should be checked before HitachiAC
DPRINTLN("Attempting Hitachi AC2 decode");
Expand Down Expand Up @@ -759,10 +777,6 @@ bool IRrecv::decode(decode_results *results, irparams_t *save,
DPRINTLN("Attempting Daikin152 decode");
if (decodeDaikin152(results, offset)) return true;
#endif // DECODE_DAIKIN152
#if DECODE_MITSUBISHI136
DPRINTLN("Attempting Mitsubishi136 decode");
if (decodeMitsubishi136(results, offset)) return true;
#endif // DECODE_MITSUBISHI136
#if DECODE_SYMPHONY
DPRINTLN("Attempting Symphony decode");
if (decodeSymphony(results, offset)) return true;
Expand Down
6 changes: 6 additions & 0 deletions src/IRrecv.h
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,12 @@ class IRrecv {
const uint16_t nbits = kHitachiAc1Bits,
const bool strict = true);
#endif
#if DECODE_HITACHI_AC3
bool decodeHitachiAc3(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kHitachiAc3Bits,
const bool strict = true);
#endif // DECODE_HITACHI_AC3
#if DECODE_HITACHI_AC424
bool decodeHitachiAc424(decode_results *results,
uint16_t offset = kStartOffset,
Expand Down
30 changes: 21 additions & 9 deletions src/IRremoteESP8266.h
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,20 @@
#define SEND_HITACHI_AC2 _IR_ENABLE_DEFAULT_
#endif // SEND_HITACHI_AC2

#ifndef DECODE_HITACHI_AC3
#define DECODE_HITACHI_AC3 _IR_ENABLE_DEFAULT_
#endif // DECODE_HITACHI_AC3
#ifndef SEND_HITACHI_AC3
#define SEND_HITACHI_AC3 _IR_ENABLE_DEFAULT_
#endif // SEND_HITACHI_AC3

#ifndef DECODE_HITACHI_AC424
#define DECODE_HITACHI_AC424 _IR_ENABLE_DEFAULT_
#endif // DECODE_HITACHI_AC424
#ifndef SEND_HITACHI_AC424
#define SEND_HITACHI_AC424 _IR_ENABLE_DEFAULT_
#endif // SEND_HITACHI_AC424

#ifndef DECODE_GICABLE
#define DECODE_GICABLE _IR_ENABLE_DEFAULT_
#endif // DECODE_GICABLE
Expand Down Expand Up @@ -558,13 +572,6 @@
#define SEND_DAIKIN152 _IR_ENABLE_DEFAULT_
#endif // SEND_DAIKIN152

#ifndef DECODE_HITACHI_AC424
#define DECODE_HITACHI_AC424 _IR_ENABLE_DEFAULT_
#endif // DECODE_HITACHI_AC424
#ifndef SEND_HITACHI_AC424
#define SEND_HITACHI_AC424 _IR_ENABLE_DEFAULT_
#endif // SEND_HITACHI_AC424

#ifndef DECODE_EPSON
#define DECODE_EPSON _IR_ENABLE_DEFAULT_
#endif // DECODE_EPSON
Expand All @@ -589,7 +596,7 @@
DECODE_DAIKIN216 || DECODE_SHARP_AC || DECODE_DAIKIN160 || \
DECODE_NEOCLIMA || DECODE_DAIKIN176 || DECODE_DAIKIN128 || \
DECODE_AMCOR || DECODE_DAIKIN152 || DECODE_MITSUBISHI136 || \
DECODE_MITSUBISHI112 || DECODE_HITACHI_AC424)
DECODE_MITSUBISHI112 || DECODE_HITACHI_AC424 || DECODE_HITACHI_AC3)
#define DECODE_AC true // We need some common infrastructure for decoding A/Cs.
#else
#define DECODE_AC false // We don't need that infrastructure.
Expand Down Expand Up @@ -703,8 +710,9 @@ enum decode_type_t {
SONY_38K,
EPSON, // 75
SYMPHONY,
HITACHI_AC3,
// Add new entries before this one, and update it to point to the last entry.
kLastDecodeType = SYMPHONY,
kLastDecodeType = HITACHI_AC3,
};

// Message lengths & required repeat values
Expand Down Expand Up @@ -782,6 +790,10 @@ const uint16_t kHitachiAc1StateLength = 13;
const uint16_t kHitachiAc1Bits = kHitachiAc1StateLength * 8;
const uint16_t kHitachiAc2StateLength = 53;
const uint16_t kHitachiAc2Bits = kHitachiAc2StateLength * 8;
const uint16_t kHitachiAc3StateLength = 27;
const uint16_t kHitachiAc3Bits = kHitachiAc3StateLength * 8;
const uint16_t kHitachiAc3MinStateLength = 15;
const uint16_t kHitachiAc3MinBits = kHitachiAc3MinStateLength * 8;
const uint16_t kHitachiAc424StateLength = 53;
const uint16_t kHitachiAc424Bits = kHitachiAc424StateLength * 8;
const uint16_t kInaxBits = 24;
Expand Down
7 changes: 7 additions & 0 deletions src/IRsend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,8 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) {
return kHitachiAc1Bits;
case HITACHI_AC2:
return kHitachiAc2Bits;
case HITACHI_AC3:
return kHitachiAc3Bits;
case HITACHI_AC424:
return kHitachiAc424Bits;
case KELVINATOR:
Expand Down Expand Up @@ -959,6 +961,11 @@ bool IRsend::send(const decode_type_t type, const unsigned char *state,
sendHitachiAC2(state, nbytes);
break;
#endif // SEND_HITACHI_AC2
#if SEND_HITACHI_AC3
case HITACHI_AC3:
sendHitachiAc3(state, nbytes);
break;
#endif // SEND_HITACHI_AC3
#if SEND_HITACHI_AC424
case HITACHI_AC424:
sendHitachiAc424(state, nbytes);
Expand Down
6 changes: 6 additions & 0 deletions src/IRsend.h
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,12 @@ class IRsend {
const uint16_t nbytes = kHitachiAc2StateLength,
const uint16_t repeat = kHitachiAcDefaultRepeat);
#endif
#if SEND_HITACHI_AC3
void sendHitachiAc3(const unsigned char data[],
const uint16_t nbytes, // No default as there as so many
// different sizes
const uint16_t repeat = kHitachiAcDefaultRepeat);
#endif // SEND_HITACHI_AC3
#if SEND_HITACHI_AC424
void sendHitachiAc424(const unsigned char data[],
const uint16_t nbytes = kHitachiAc424StateLength,
Expand Down
6 changes: 6 additions & 0 deletions src/IRutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ decode_type_t strToDecodeType(const char * const str) {
return decode_type_t::HITACHI_AC1;
else if (!strcasecmp(str, "HITACHI_AC2"))
return decode_type_t::HITACHI_AC2;
else if (!strcasecmp(str, "HITACHI_AC3"))
return decode_type_t::HITACHI_AC3;
else if (!strcasecmp(str, "HITACHI_AC424"))
return decode_type_t::HITACHI_AC424;
else if (!strcasecmp(str, "INAX"))
Expand Down Expand Up @@ -347,6 +349,9 @@ String typeToString(const decode_type_t protocol, const bool isRepeat) {
case HITACHI_AC2:
result = F("HITACHI_AC2");
break;
case HITACHI_AC3:
result = F("HITACHI_AC3");
break;
case HITACHI_AC424:
result = F("HITACHI_AC424");
break;
Expand Down Expand Up @@ -530,6 +535,7 @@ bool hasACState(const decode_type_t protocol) {
case HITACHI_AC:
case HITACHI_AC1:
case HITACHI_AC2:
case HITACHI_AC3:
case HITACHI_AC424:
case KELVINATOR:
case MITSUBISHI136:
Expand Down
147 changes: 147 additions & 0 deletions src/ir_Hitachi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ const uint16_t kHitachiAc424BitMark = 463;
const uint16_t kHitachiAc424OneSpace = 1208;
const uint16_t kHitachiAc424ZeroSpace = 372;

// Support for HitachiAc3 protocol
// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1060
const uint16_t kHitachiAc3HdrMark = 3400; // Header
const uint16_t kHitachiAc3HdrSpace = 1660; // Header
const uint16_t kHitachiAc3BitMark = 460;
const uint16_t kHitachiAc3OneSpace = 1250;
const uint16_t kHitachiAc3ZeroSpace = 410;

using irutils::addBoolToString;
using irutils::addIntToString;
using irutils::addLabeledString;
Expand Down Expand Up @@ -800,3 +808,142 @@ String IRHitachiAc424::toString(void) {
result += ')';
return result;
}


#if SEND_HITACHI_AC3
// Send HITACHI_AC3 messages
//
// Note: This protocol is almost exactly the same as HitachiAC424 except this
// variant has subtle timing differences.
// There are five(5) typical sizes:
// * kHitachiAc3MinStateLength (Cancel Timer)
// * kHitachiAc3MinStateLength + 2 (Change Temp)
// * kHitachiAc3StateLength - 6 (Change Mode)
// * kHitachiAc3StateLength- 4 (Normal)
// * kHitachiAc3StateLength (Set Timer)
//
// Args:
// data: An array of bytes containing the IR command.
// It is assumed to be in LSBF order for this code.
// nbytes: Nr. of bytes of data in the array.
// repeat: Nr. of times the message is to be repeated.
//
// Status: BETA / Probably working fine.
void IRsend::sendHitachiAc3(const uint8_t data[], const uint16_t nbytes,
const uint16_t repeat) {
// Header + Data + Footer
sendGeneric(kHitachiAc3HdrMark, kHitachiAc3HdrSpace,
kHitachiAc3BitMark, kHitachiAc3OneSpace,
kHitachiAc3BitMark, kHitachiAc3ZeroSpace,
kHitachiAc3BitMark, kHitachiAcMinGap,
data, nbytes, // Bytes
kHitachiAcFreq, false, repeat, kDutyDefault);
}
#endif // SEND_HITACHI_AC3


// Class for handling the remote control on a Hitachi_AC3 53 A/C message
IRHitachiAc3::IRHitachiAc3(const uint16_t pin, const bool inverted,
const bool use_modulation)
: _irsend(pin, inverted, use_modulation) { stateReset(); }

// Reset to auto fan, cooling, 23° Celcius
void IRHitachiAc3::stateReset(void) {
for (uint8_t i = 0; i < kHitachiAc3StateLength; i++)
remote_state[i] = 0x00;
remote_state[0] = 0x01;
remote_state[1] = 0x10;
remote_state[3] = 0x40;
remote_state[5] = 0xFF;
remote_state[7] = 0xE8;
remote_state[9] = 0x89;
remote_state[11] = 0x0B;
remote_state[13] = 0x3F;
remote_state[15] = 0x15;
remote_state[21] = 0x4B;
remote_state[23] = 0x18;
setInvertedStates();
}

void IRHitachiAc3::setInvertedStates(const uint16_t length) {
for (uint8_t i = 3; i < length - 1; i += 2)
remote_state[i + 1] = ~remote_state[i];
}

bool IRHitachiAc3::hasInvertedStates(const uint8_t state[],
const uint16_t length) {
for (uint8_t i = 3; i < length - 1; i += 2)
if ((state[i + 1] ^ state[i]) != 0xFF) return false;
return true;
}

void IRHitachiAc3::begin(void) { _irsend.begin(); }

uint8_t *IRHitachiAc3::getRaw(void) {
setInvertedStates();
return remote_state;
}

void IRHitachiAc3::setRaw(const uint8_t new_code[], const uint16_t length) {
memcpy(remote_state, new_code, std::min(length, kHitachiAc3StateLength));
}

#if DECODE_HITACHI_AC3
// Decode the supplied HitachiAc3 A/C message.
//
// Note: This protocol is almost exactly the same as HitachiAC424 except this
// variant has subtle timing differences and multiple lengths.
//
// Args:
// results: Ptr to the data to decode and where to store the decode result.
// offset: The starting index to use when attempting to decode the raw data.
// Typically/Defaults to kStartOffset.
// nbits: The number of data bits to expect. Typically kHitachiAc3Bits.
// strict: Flag indicating if we should perform strict matching.
// Returns:
// boolean: True if it can decode it, false if it can't.
//
// Status: BETA / Probably works fine.
//
// Supported devices:
// Hitachi PC-LH3B
//
// Ref:
// https://github.com/crankyoldgit/IRremoteESP8266/issues/1060
bool IRrecv::decodeHitachiAc3(decode_results *results, uint16_t offset,
const uint16_t nbits,
const bool strict) {
if (results->rawlen < 2 * nbits + kHeader + kFooter - 1 + offset)
return false; // Too short a message to match.
if (strict) {
// Check the requested bit length.
switch (nbits) {
case kHitachiAc3MinBits: // Cancel Timer (Min Size)
case kHitachiAc3MinBits + 2 * 8: // Change Temp
case kHitachiAc3Bits - 6 * 8: // Change Mode
case kHitachiAc3Bits - 4 * 8: // Normal
case kHitachiAc3Bits: // Set Temp (Max Size)
break;
default: return false;
}
}

// Header + Data + Footer
if (!matchGeneric(results->rawbuf + offset, results->state,
results->rawlen - offset, nbits,
kHitachiAc3HdrMark, kHitachiAc3HdrSpace,
kHitachiAc3BitMark, kHitachiAc3OneSpace,
kHitachiAc3BitMark, kHitachiAc3ZeroSpace,
kHitachiAc3BitMark, kHitachiAcMinGap, true,
kUseDefTol, 0, false))
return false; // We failed to find any data.

// Compliance
if (strict && !IRHitachiAc3::hasInvertedStates(results->state, nbits / 8))
return false;
// Success
results->decode_type = decode_type_t::HITACHI_AC3;
results->bits = nbits;
return true;
}
#endif // DECODE_HITACHI_AC3
Loading

0 comments on commit a31c6bd

Please sign in to comment.