diff --git a/src/IRrecv.cpp b/src/IRrecv.cpp index 5626db2c7..019297213 100644 --- a/src/IRrecv.cpp +++ b/src/IRrecv.cpp @@ -836,6 +836,12 @@ bool IRrecv::decode(decode_results *results, irparams_t *save, if (decodeHitachiAC(results, offset, kHitachiAc264Bits, true, false)) return true; #endif // DECODE_HITACHI_AC264 +#if DECODE_HITACHI_AC296 + // HitachiAC296 should be checked before HitachiAC + DPRINTLN("Attempting Hitachi AC296 decode"); + if (decodeHitachiAc296(results, offset, kHitachiAc296Bits, true)) + return true; +#endif // DECODE_HITACHI_AC296 #if DECODE_HITACHI_AC2 // HitachiAC2 should be checked before HitachiAC DPRINTLN("Attempting Hitachi AC2 decode"); diff --git a/src/IRrecv.h b/src/IRrecv.h index f9ff4b0ef..f6df07057 100644 --- a/src/IRrecv.h +++ b/src/IRrecv.h @@ -608,6 +608,12 @@ class IRrecv { const uint16_t nbits = kHitachiAc3Bits, const bool strict = true); #endif // DECODE_HITACHI_AC3 +#if DECODE_HITACHI_AC296 + bool decodeHitachiAc296(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kHitachiAc296Bits, + const bool strict = true); +#endif // DECODE_HITACHI_AC296 #if DECODE_HITACHI_AC424 bool decodeHitachiAc424(decode_results *results, uint16_t offset = kStartOffset, diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h index 94027ff39..59cb4034e 100644 --- a/src/IRremoteESP8266.h +++ b/src/IRremoteESP8266.h @@ -497,6 +497,13 @@ #define SEND_HITACHI_AC264 _IR_ENABLE_DEFAULT_ #endif // SEND_HITACHI_AC264 +#ifndef DECODE_HITACHI_AC296 +#define DECODE_HITACHI_AC296 _IR_ENABLE_DEFAULT_ +#endif // DECODE_HITACHI_AC296 +#ifndef SEND_HITACHI_AC296 +#define SEND_HITACHI_AC296 _IR_ENABLE_DEFAULT_ +#endif // SEND_HITACHI_AC296 + #ifndef DECODE_HITACHI_AC344 #define DECODE_HITACHI_AC344 _IR_ENABLE_DEFAULT_ #endif // DECODE_HITACHI_AC344 @@ -869,7 +876,7 @@ DECODE_VOLTAS || DECODE_MIRAGE || DECODE_HAIER_AC176 || \ DECODE_TEKNOPOINT || DECODE_KELON || DECODE_TROTEC_3550 || \ DECODE_SANYO_AC88 || DECODE_RHOSS || DECODE_HITACHI_AC264 || \ - DECODE_KELON168 || \ + DECODE_KELON168 || DECODE_HITACHI_AC296 || \ false) // Add any DECODE to the above if it uses result->state (see kStateSizeMax) // you might also want to add the protocol to hasACState function @@ -1022,8 +1029,9 @@ enum decode_type_t { COOLIX48, // 110 HITACHI_AC264, KELON168, + HITACHI_AC296, // Add new entries before this one, and update it to point to the last entry. - kLastDecodeType = KELON168, + kLastDecodeType = HITACHI_AC296, }; // Message lengths & required repeat values @@ -1135,6 +1143,8 @@ const uint16_t kHitachiAc3MinStateLength = 15; const uint16_t kHitachiAc3MinBits = kHitachiAc3MinStateLength * 8; const uint16_t kHitachiAc264StateLength = 33; const uint16_t kHitachiAc264Bits = kHitachiAc264StateLength * 8; +const uint16_t kHitachiAc296StateLength = 37; +const uint16_t kHitachiAc296Bits = kHitachiAc296StateLength * 8; const uint16_t kHitachiAc344StateLength = 43; const uint16_t kHitachiAc344Bits = kHitachiAc344StateLength * 8; const uint16_t kHitachiAc424StateLength = 53; @@ -1304,6 +1314,8 @@ const uint16_t kRhossDefaultRepeat = 0; #define HITACHI_AC1_BITS kHitachiAc1Bits #define HITACHI_AC2_STATE_LENGTH kHitachiAc2StateLength #define HITACHI_AC2_BITS kHitachiAc2Bits +#define HITACHI_AC296_STATE_LENGTH kHitachiAc296StateLength +#define HITACHI_AC296_BITS kHitachiAc296Bits #define JVC_BITS kJvcBits #define KELVINATOR_STATE_LENGTH kKelvinatorStateLength #define LASERTAG_BITS kLasertagBits diff --git a/src/IRsend.cpp b/src/IRsend.cpp index 6df1aea96..d80b4bbf6 100644 --- a/src/IRsend.cpp +++ b/src/IRsend.cpp @@ -720,6 +720,8 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) { return kHitachiAc3Bits; case HITACHI_AC264: return kHitachiAc264Bits; + case HITACHI_AC296: + return kHitachiAc296Bits; case HITACHI_AC344: return kHitachiAc344Bits; case HITACHI_AC424: @@ -1219,6 +1221,11 @@ bool IRsend::send(const decode_type_t type, const uint8_t *state, sendHitachiAc264(state, nbytes); break; #endif // SEND_HITACHI_AC264 +#if SEND_HITACHI_AC296 + case HITACHI_AC296: + sendHitachiAc296(state, nbytes); + break; +#endif // SEND_HITACHI_AC296 #if SEND_HITACHI_AC344 case HITACHI_AC344: sendHitachiAc344(state, nbytes); diff --git a/src/IRsend.h b/src/IRsend.h index e51a214b0..7b77c3bc1 100644 --- a/src/IRsend.h +++ b/src/IRsend.h @@ -597,6 +597,11 @@ class IRsend { const uint16_t nbytes = kHitachiAc264StateLength, const uint16_t repeat = kHitachiAcDefaultRepeat); #endif // SEND_HITACHI_AC264 +#if SEND_HITACHI_AC296 + void sendHitachiAc296(const unsigned char data[], + const uint16_t nbytes = kHitachiAc296StateLength, + const uint16_t repeat = kHitachiAcDefaultRepeat); +#endif // SEND_HITACHI_AC296 #if SEND_HITACHI_AC344 void sendHitachiAc344(const unsigned char data[], const uint16_t nbytes = kHitachiAc344StateLength, diff --git a/src/IRtext.cpp b/src/IRtext.cpp index bd65ffd98..edd042b52 100644 --- a/src/IRtext.cpp +++ b/src/IRtext.cpp @@ -393,6 +393,7 @@ IRTEXT_CONST_BLOB_DECL(kAllProtocolNamesStr) { D_STR_COOLIX48 "\x0" D_STR_HITACHI_AC264 "\x0" D_STR_KELON168 "\x0" + D_STR_HITACHI_AC296 "\x0" ///< New protocol strings should be added just above this line. "\x0" ///< This string requires double null termination. }; diff --git a/src/IRutils.cpp b/src/IRutils.cpp index 166813035..0ede6ef13 100644 --- a/src/IRutils.cpp +++ b/src/IRutils.cpp @@ -191,6 +191,7 @@ bool hasACState(const decode_type_t protocol) { case HITACHI_AC2: case HITACHI_AC3: case HITACHI_AC264: + case HITACHI_AC296: case HITACHI_AC344: case HITACHI_AC424: case KELON168: diff --git a/src/ir_Hitachi.cpp b/src/ir_Hitachi.cpp index a90ad6bee..b80430424 100644 --- a/src/ir_Hitachi.cpp +++ b/src/ir_Hitachi.cpp @@ -7,6 +7,7 @@ /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1056 /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1060 /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1134 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1757 #include "ir_Hitachi.h" #include @@ -72,6 +73,7 @@ void IRsend::sendHitachiAC(const unsigned char data[], const uint16_t nbytes, bool MSBfirst = true; switch (nbytes) { case kHitachiAc264StateLength: + case kHitachiAc296StateLength: case kHitachiAc344StateLength: MSBfirst = false; } @@ -1704,3 +1706,195 @@ String IRHitachiAc264::toString(void) const { #if DECODE_HITACHI_AC264 // For Decoding HITACHI_AC264, see `decodeHitachiAC` #endif // DECODE_HITACHI_AC264 + + +#if SEND_HITACHI_AC296 +/// Send a HitachiAc 37-byte/296-bit A/C message (HITACHI_AC296) +/// Status: STABLE / Working on a real device. +/// @param[in] data containing the IR command. +/// @param[in] nbytes Nr. of bytes to send. usually kHitachiAc296StateLength +/// @param[in] repeat Nr. of times the message is to be repeated. +void IRsend::sendHitachiAc296(const unsigned char data[], + const uint16_t nbytes, + const uint16_t repeat) { + if (nbytes < kHitachiAc296StateLength) + return; // Not enough bytes to send a proper message. + sendHitachiAC(data, nbytes, repeat); +} +#endif // SEND_HITACHIAC296 + +IRHitachiAc296::IRHitachiAc296(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +void IRHitachiAc296::stateReset(void) { + // Header + _.raw[0] = 0x01; + _.raw[1] = 0x10; + _.raw[2] = 0x00; + + // Every next byte is a parity byte + _.raw[3] = 0x40; + _.raw[5] = 0xFF; + _.raw[7] = 0xCC; + _.raw[9] = 0x92; + _.raw[11] = 0x43; + // 13-14 is Temperature and parity + _.raw[15] = 0x00; + _.raw[17] = 0x00; // Off timer LSB + _.raw[19] = 0x00; // Off timer cont + _.raw[21] = 0x00; // On timer LSB + _.raw[23] = 0x00; // On timer cont + // 25-26 is Mode and fan + _.raw[27] = 0xF1; // Power on + _.raw[29] = 0x00; + _.raw[31] = 0x00; + _.raw[33] = 0x00; + _.raw[35] = 0x03; // Humidity + + setTemp(24); + setMode(kHitachiAc296Heat); + setFan(kHitachiAc296FanAuto); + + setInvertedStates(); +} + +/// Update the internal consistency check for the protocol. +void IRHitachiAc296::setInvertedStates(void) { + invertBytePairs(_.raw + 3, kHitachiAc296StateLength - 3); +} + +/// Check if every second byte of the state, after the fixed header +/// is inverted to the previous byte. +/// @param[in] state The state array to be checked. +/// @param[in] length The size of the state array. +/// @note This is this protocols integrity check. +bool IRHitachiAc296::hasInvertedStates(const uint8_t state[], + const uint16_t length) { + return IRHitachiAc3::hasInvertedStates(state, length); +} + +/// Set up hardware to be able to send a message. +void IRHitachiAc296::begin(void) { _irsend.begin(); } + +#if SEND_HITACHI_AC296 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRHitachiAc296::send(const uint16_t repeat) { + _irsend.sendHitachiAc296(getRaw(), kHitachiAc296StateLength, repeat); +} +#endif // SEND_HITACHI_AC296 + + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRHitachiAc296::getPower(void) const { + return _.Power; +} + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRHitachiAc296::setPower(const bool on) { + _.Power = on; +} + +/// Change the power setting to On. +void IRHitachiAc296::on(void) { setPower(true); } + +/// Change the power setting to Off. +void IRHitachiAc296::off(void) { setPower(false); } + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRHitachiAc296::getMode(void) const { + return _.Mode; +} + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRHitachiAc296::setMode(const uint8_t mode) { + uint8_t newMode = mode; + switch (mode) { + case kHitachiAc296Heat: + case kHitachiAc296Cool: + case kHitachiAc296Auto: break; + default: newMode = kHitachiAc296Auto; + } + + _.Mode = newMode; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRHitachiAc296::getTemp(void) const { + return _.Temp; +} + +/// Set the temperature. +/// @param[in] celsius The temperature in degrees celsius. +void IRHitachiAc296::setTemp(const uint8_t celsius) { + uint8_t temp; + temp = std::min(celsius, kHitachiAc296MaxTemp); + _.Temp = std::max(temp, kHitachiAc296MinTemp); +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRHitachiAc296::getFan(void) const { + return _.Fan; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRHitachiAc296::setFan(const uint8_t speed) { + uint8_t newSpeed = speed; + newSpeed = std::max(newSpeed, kHitachiAc296FanSilent); + _.Fan = std::min(newSpeed, kHitachiAc296FanAuto); +} + +/// 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 *IRHitachiAc296::getRaw(void) { + setInvertedStates(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +/// @param[in] length Size (in bytes) of the code for this protocol. +void IRHitachiAc296::setRaw(const uint8_t new_code[], const uint16_t length) { + memcpy(_.raw, new_code, std::min(length, kHitachiAc296StateLength)); +} + +#if DECODE_HITACHI_AC296 +/// Decode the supplied Hitachi 37-byte A/C message. +/// Status: STABLE / Working on a real device. +/// @param[in,out] results Ptr to the data to decode & where to store the result +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1757 +bool IRrecv::decodeHitachiAc296(decode_results *results, uint16_t offset, + const uint16_t nbits, + const bool strict) { + uint16_t used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kHitachiAcHdrMark, kHitachiAcHdrSpace, + kHitachiAcBitMark, kHitachiAcOneSpace, + kHitachiAcBitMark, kHitachiAcZeroSpace, + kHitachiAcBitMark, kHitachiAcMinGap, true, + kUseDefTol, 0, false); + if (used == 0) return false; + + // Compliance + if (strict && !IRHitachiAc296::hasInvertedStates(results->state, nbits / 8)) + return false; + + // Success + results->decode_type = decode_type_t::HITACHI_AC296; + results->bits = nbits; + return true; +} +#endif // DECODE_HITACHI_AC296 diff --git a/src/ir_Hitachi.h b/src/ir_Hitachi.h index 5b9e93f14..8c58b2974 100644 --- a/src/ir_Hitachi.h +++ b/src/ir_Hitachi.h @@ -8,6 +8,7 @@ /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1060 /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1134 /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1729 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1757 // Supports: // Brand: Hitachi, Model: RAS-35THA6 remote @@ -22,6 +23,8 @@ // Brand: Hitachi, Model: RF11T1 remote (HITACHI_AC344) // Brand: Hitachi, Model: RAR-2P2 remote (HITACHI_AC264) // Brand: Hitachi, Model: RAK-25NH5 A/C (HITACHI_AC264) +// Brand: Hitachi, Model: RAR-3U3 remote (HITACHI_AC296) +// Brand: Hitachi, Model: RAS-70YHA3 A/C (HITACHI_AC296) #ifndef IR_HITACHI_H_ #define IR_HITACHI_H_ @@ -293,6 +296,76 @@ const uint8_t kHitachiAc264FanMedium = kHitachiAc424FanMedium; const uint8_t kHitachiAc264FanHigh = kHitachiAc424FanHigh; const uint8_t kHitachiAc264FanAuto = kHitachiAc424FanAuto; +// HitachiAc296 +union HitachiAC296Protocol{ + uint8_t raw[kHitachiAc296StateLength]; + struct { + // Byte 0~12 + uint8_t pad0[13]; + // Byte 13 + uint8_t :2; + uint8_t Temp :5; // LSB + uint8_t :1; + uint8_t :8; + // Byte 15~16 + uint8_t :8; + uint8_t :8; + // Byte 17~24 + uint8_t OffTimerLow :8; // LSB + uint8_t /* Parity */ :8; + uint8_t OffTimerHigh :8; + uint8_t /* Parity */ :8; + uint8_t OnTimerLow :8; // LSB + uint8_t /* Parity */ :8; + uint8_t OnTimerHigh :4; + uint8_t OffTimerActive :1; + uint8_t OnTimerActive :1; + uint8_t :2; + uint8_t /* Parity */ :8; + // Byte 25~26 + uint8_t Mode :4; + uint8_t Fan :3; + uint8_t :1; + uint8_t :8; + // Byte 27~28 + uint8_t :4; + uint8_t Power :1; + uint8_t :2; + uint8_t TimerActive :1; + uint8_t :8; + // Byte 29~34 + uint8_t pad1[6]; + // Byte 35~36 + uint8_t :4; + uint8_t Humidity :4; // LSB + uint8_t :8; + }; +}; + +// Mode & Fan +const uint8_t kHitachiAc296Cool = 0b0011; +const uint8_t kHitachiAc296DryCool = 0b0100; +const uint8_t kHitachiAc296Dehumidify = 0b0101; +const uint8_t kHitachiAc296Heat = 0b0110; +const uint8_t kHitachiAc296Auto = 0b0111; +const uint8_t kHitachiAc296AutoDehumidifying = 0b1001; +const uint8_t kHitachiAc296QuickLaundry = 0b1010; +const uint8_t kHitachiAc296CondensationControl = 0b1100; + +const uint8_t kHitachiAc296FanSilent = 0b001; +const uint8_t kHitachiAc296FanLow = 0b010; +const uint8_t kHitachiAc296FanMedium = 0b011; +const uint8_t kHitachiAc296FanHigh = 0b100; +const uint8_t kHitachiAc296FanAuto = 0b101; + +const uint8_t kHitachiAc296TempSize = 5; +const uint8_t kHitachiAc296MinTemp = 16; +const uint8_t kHitachiAc296MaxTemp = 32; + +const uint8_t kHitachiAc296PowerOn = 1; +const uint8_t kHitachiAc296PowerOff = 0; + + // Classes /// Class for handling detailed Hitachi 224-bit A/C messages. /// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/HitachiHeatpumpIR.cpp @@ -547,4 +620,43 @@ class IRHitachiAc264: public IRHitachiAc424 { #endif // SEND_HITACHI_AC264 String toString(void) const override; }; + +class IRHitachiAc296 { + public: + explicit IRHitachiAc296(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); + +#if SEND_HITACHI_AC296 + void send(const uint16_t repeat = kHitachiAcDefaultRepeat); +#endif + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + static bool hasInvertedStates(const uint8_t state[], const uint16_t length); + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[], + const uint16_t length = kHitachiAc296StateLength); + +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + + HitachiAC296Protocol _; + void setInvertedStates(void); +}; #endif // IR_HITACHI_H_ diff --git a/src/locale/defaults.h b/src/locale/defaults.h index 4fb188f6c..cade71219 100644 --- a/src/locale/defaults.h +++ b/src/locale/defaults.h @@ -811,6 +811,9 @@ D_STR_INDIRECT " " D_STR_MODE #ifndef D_STR_HITACHI_AC264 #define D_STR_HITACHI_AC264 D_STR_HITACHI_AC "264" #endif // D_STR_HITACHI_AC264 +#ifndef D_STR_HITACHI_AC296 +#define D_STR_HITACHI_AC296 D_STR_HITACHI_AC "296" +#endif // D_STR_HITACHI_AC296 #ifndef D_STR_HITACHI_AC344 #define D_STR_HITACHI_AC344 D_STR_HITACHI_AC "344" #endif // D_STR_HITACHI_AC344