Tasmota/lib/IRremoteESP8266-2.6.5/src/ir_Hitachi.cpp

443 lines
14 KiB
C++
Executable File

// Copyright 2018 David Conran
//
// Code to emulate Hitachi protocol compatible devices.
// Should be compatible with:
// * Hitachi RAS-35THA6 remote
//
#include "ir_Hitachi.h"
#include <algorithm>
#ifndef ARDUINO
#include <string>
#endif
#include "IRrecv.h"
#include "IRremoteESP8266.h"
#include "IRsend.h"
#include "IRutils.h"
// Constants
// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/417
const uint16_t kHitachiAcHdrMark = 3300;
const uint16_t kHitachiAcHdrSpace = 1700;
const uint16_t kHitachiAc1HdrMark = 3400;
const uint16_t kHitachiAc1HdrSpace = 3400;
const uint16_t kHitachiAcBitMark = 400;
const uint16_t kHitachiAcOneSpace = 1250;
const uint16_t kHitachiAcZeroSpace = 500;
const uint32_t kHitachiAcMinGap = kDefaultMessageGap; // Just a guess.
using irutils::addBoolToString;
using irutils::addIntToString;
using irutils::addLabeledString;
using irutils::addModeToString;
using irutils::addFanToString;
using irutils::addTempToString;
#if (SEND_HITACHI_AC || SEND_HITACHI_AC2)
// Send a Hitachi A/C message.
//
// Args:
// data: An array of bytes containing the IR command.
// nbytes: Nr. of bytes of data in the array. (>=kHitachiAcStateLength)
// repeat: Nr. of times the message is to be repeated. (Default = 0).
//
// Status: ALPHA / Untested.
//
// Ref:
// https://github.com/crankyoldgit/IRremoteESP8266/issues/417
void IRsend::sendHitachiAC(const unsigned char data[], const uint16_t nbytes,
const uint16_t repeat) {
if (nbytes < kHitachiAcStateLength)
return; // Not enough bytes to send a proper message.
sendGeneric(kHitachiAcHdrMark, kHitachiAcHdrSpace, kHitachiAcBitMark,
kHitachiAcOneSpace, kHitachiAcBitMark, kHitachiAcZeroSpace,
kHitachiAcBitMark, kHitachiAcMinGap, data, nbytes, 38, true,
repeat, 50);
}
#endif // (SEND_HITACHI_AC || SEND_HITACHI_AC2)
#if SEND_HITACHI_AC1
// Send a Hitachi A/C 13-byte message.
//
// For devices:
// Hitachi A/C Series VI (Circa 2007) / Remote: LT0541-HTA
//
// Args:
// data: An array of bytes containing the IR command.
// nbytes: Nr. of bytes of data in the array. (>=kHitachiAc1StateLength)
// repeat: Nr. of times the message is to be repeated. (Default = 0).
//
// Status: BETA / Appears to work.
//
// Ref:
// https://github.com/crankyoldgit/IRremoteESP8266/issues/453
// Basically the same as sendHitatchiAC() except different size and header.
void IRsend::sendHitachiAC1(const unsigned char data[], const uint16_t nbytes,
const uint16_t repeat) {
if (nbytes < kHitachiAc1StateLength)
return; // Not enough bytes to send a proper message.
sendGeneric(kHitachiAc1HdrMark, kHitachiAc1HdrSpace, kHitachiAcBitMark,
kHitachiAcOneSpace, kHitachiAcBitMark, kHitachiAcZeroSpace,
kHitachiAcBitMark, kHitachiAcMinGap, data, nbytes, 38, true,
repeat, 50);
}
#endif // SEND_HITACHI_AC1
#if SEND_HITACHI_AC2
// Send a Hitachi A/C 53-byte message.
//
// For devices:
// Hitachi A/C Series VI (Circa 2007) / Remote: LT0541-HTA
//
// Args:
// data: An array of bytes containing the IR command.
// nbytes: Nr. of bytes of data in the array. (>=kHitachiAc2StateLength)
// repeat: Nr. of times the message is to be repeated. (Default = 0).
//
// Status: BETA / Appears to work.
//
// Ref:
// https://github.com/crankyoldgit/IRremoteESP8266/issues/417
// Basically the same as sendHitatchiAC() except different size.
void IRsend::sendHitachiAC2(const unsigned char data[], const uint16_t nbytes,
const uint16_t repeat) {
if (nbytes < kHitachiAc2StateLength)
return; // Not enough bytes to send a proper message.
sendHitachiAC(data, nbytes, repeat);
}
#endif // SEND_HITACHI_AC2
// Class for handling the remote control on a Hitachi 28 byte A/C message.
// Inspired by:
// https://github.com/ToniA/arduino-heatpumpir/blob/master/HitachiHeatpumpIR.cpp
IRHitachiAc::IRHitachiAc(const uint16_t pin, const bool inverted,
const bool use_modulation)
: _irsend(pin, inverted, use_modulation) { stateReset(); }
void IRHitachiAc::stateReset(void) {
remote_state[0] = 0x80;
remote_state[1] = 0x08;
remote_state[2] = 0x0C;
remote_state[3] = 0x02;
remote_state[4] = 0xFD;
remote_state[5] = 0x80;
remote_state[6] = 0x7F;
remote_state[7] = 0x88;
remote_state[8] = 0x48;
remote_state[9] = 0x10;
for (uint8_t i = 10; i < kHitachiAcStateLength; i++) remote_state[i] = 0x00;
remote_state[14] = 0x60;
remote_state[15] = 0x60;
remote_state[24] = 0x80;
setTemp(23);
}
void IRHitachiAc::begin(void) { _irsend.begin(); }
uint8_t IRHitachiAc::calcChecksum(const uint8_t state[],
const uint16_t length) {
int8_t sum = 62;
for (uint16_t i = 0; i < length - 1; i++) sum -= reverseBits(state[i], 8);
return reverseBits((uint8_t)sum, 8);
}
void IRHitachiAc::checksum(const uint16_t length) {
remote_state[length - 1] = calcChecksum(remote_state, length);
}
bool IRHitachiAc::validChecksum(const uint8_t state[], const uint16_t length) {
if (length < 2) return true; // Assume true for lengths that are too short.
return (state[length - 1] == calcChecksum(state, length));
}
uint8_t *IRHitachiAc::getRaw(void) {
checksum();
return remote_state;
}
void IRHitachiAc::setRaw(const uint8_t new_code[], const uint16_t length) {
for (uint8_t i = 0; i < length && i < kHitachiAcStateLength; i++)
remote_state[i] = new_code[i];
}
#if SEND_HITACHI_AC
void IRHitachiAc::send(const uint16_t repeat) {
checksum();
_irsend.sendHitachiAC(remote_state, kHitachiAcStateLength, repeat);
}
#endif // SEND_HITACHI_AC
bool IRHitachiAc::getPower(void) { return (remote_state[17] & 0x01); }
void IRHitachiAc::setPower(const bool on) {
if (on)
remote_state[17] |= 0x01;
else
remote_state[17] &= 0xFE;
}
void IRHitachiAc::on(void) { setPower(true); }
void IRHitachiAc::off(void) { setPower(false); }
uint8_t IRHitachiAc::getMode(void) { return reverseBits(remote_state[10], 8); }
void IRHitachiAc::setMode(const uint8_t mode) {
uint8_t newmode = mode;
switch (mode) {
case kHitachiAcFan:
// Fan mode sets a special temp.
setTemp(64);
break;
case kHitachiAcAuto:
case kHitachiAcHeat:
case kHitachiAcCool:
case kHitachiAcDry:
break;
default:
newmode = kHitachiAcAuto;
}
remote_state[10] = reverseBits(newmode, 8);
if (mode != kHitachiAcFan) setTemp(_previoustemp);
setFan(getFan()); // Reset the fan speed after the mode change.
}
uint8_t IRHitachiAc::getTemp(void) {
return reverseBits(remote_state[11], 8) >> 1;
}
void IRHitachiAc::setTemp(const uint8_t celsius) {
uint8_t temp;
if (celsius != 64) _previoustemp = celsius;
switch (celsius) {
case 64:
temp = celsius;
break;
default:
temp = std::min(celsius, kHitachiAcMaxTemp);
temp = std::max(temp, kHitachiAcMinTemp);
}
remote_state[11] = reverseBits(temp << 1, 8);
if (temp == kHitachiAcMinTemp)
remote_state[9] = 0x90;
else
remote_state[9] = 0x10;
}
uint8_t IRHitachiAc::getFan(void) { return reverseBits(remote_state[13], 8); }
void IRHitachiAc::setFan(const uint8_t speed) {
uint8_t fanmin = kHitachiAcFanAuto;
uint8_t fanmax = kHitachiAcFanHigh;
switch (getMode()) {
case kHitachiAcDry: // Only 2 x low speeds in Dry mode.
fanmin = kHitachiAcFanLow;
fanmax = kHitachiAcFanLow + 1;
break;
case kHitachiAcFan:
fanmin = kHitachiAcFanLow; // No Auto in Fan mode.
break;
}
uint8_t newspeed = std::max(speed, fanmin);
newspeed = std::min(newspeed, fanmax);
remote_state[13] = reverseBits(newspeed, 8);
}
bool IRHitachiAc::getSwingVertical(void) { return remote_state[14] & 0x80; }
void IRHitachiAc::setSwingVertical(const bool on) {
if (on)
remote_state[14] |= 0x80;
else
remote_state[14] &= 0x7F;
}
bool IRHitachiAc::getSwingHorizontal(void) { return remote_state[15] & 0x80; }
void IRHitachiAc::setSwingHorizontal(const bool on) {
if (on)
remote_state[15] |= 0x80;
else
remote_state[15] &= 0x7F;
}
// Convert a standard A/C mode into its native mode.
uint8_t IRHitachiAc::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kCool:
return kHitachiAcCool;
case stdAc::opmode_t::kHeat:
return kHitachiAcHeat;
case stdAc::opmode_t::kDry:
return kHitachiAcDry;
case stdAc::opmode_t::kFan:
return kHitachiAcFan;
default:
return kHitachiAcAuto;
}
}
// Convert a standard A/C Fan speed into its native fan speed.
uint8_t IRHitachiAc::convertFan(const stdAc::fanspeed_t speed) {
switch (speed) {
case stdAc::fanspeed_t::kMin:
case stdAc::fanspeed_t::kLow:
return kHitachiAcFanLow;
case stdAc::fanspeed_t::kMedium:
return kHitachiAcFanLow + 1;
case stdAc::fanspeed_t::kHigh:
return kHitachiAcFanHigh - 1;
case stdAc::fanspeed_t::kMax:
return kHitachiAcFanHigh;
default:
return kHitachiAcFanAuto;
}
}
// Convert a native mode to it's common equivalent.
stdAc::opmode_t IRHitachiAc::toCommonMode(const uint8_t mode) {
switch (mode) {
case kHitachiAcCool: return stdAc::opmode_t::kCool;
case kHitachiAcHeat: return stdAc::opmode_t::kHeat;
case kHitachiAcDry: return stdAc::opmode_t::kDry;
case kHitachiAcFan: return stdAc::opmode_t::kFan;
default: return stdAc::opmode_t::kAuto;
}
}
// Convert a native fan speed to it's common equivalent.
stdAc::fanspeed_t IRHitachiAc::toCommonFanSpeed(const uint8_t speed) {
switch (speed) {
case kHitachiAcFanHigh: return stdAc::fanspeed_t::kMax;
case kHitachiAcFanHigh - 1: return stdAc::fanspeed_t::kHigh;
case kHitachiAcFanLow + 1: return stdAc::fanspeed_t::kMedium;
case kHitachiAcFanLow: return stdAc::fanspeed_t::kLow;
default: return stdAc::fanspeed_t::kAuto;
}
}
// Convert the A/C state to it's common equivalent.
stdAc::state_t IRHitachiAc::toCommon(void) {
stdAc::state_t result;
result.protocol = decode_type_t::HITACHI_AC;
result.model = -1; // No models used.
result.power = this->getPower();
result.mode = this->toCommonMode(this->getMode());
result.celsius = true;
result.degrees = this->getTemp();
result.fanspeed = this->toCommonFanSpeed(this->getFan());
result.swingv = this->getSwingVertical() ? stdAc::swingv_t::kAuto :
stdAc::swingv_t::kOff;
result.swingh = this->getSwingHorizontal() ? stdAc::swingh_t::kAuto :
stdAc::swingh_t::kOff;
// Not supported.
result.quiet = false;
result.turbo = false;
result.clean = false;
result.econo = false;
result.filter = false;
result.light = false;
result.beep = false;
result.sleep = -1;
result.clock = -1;
return result;
}
// Convert the internal state into a human readable string.
String IRHitachiAc::toString(void) {
String result = "";
result.reserve(110); // Reserve some heap for the string to reduce fragging.
result += addBoolToString(getPower(), F("Power"), false);
result += addModeToString(getMode(), kHitachiAcAuto, kHitachiAcCool,
kHitachiAcHeat, kHitachiAcDry, kHitachiAcFan);
result += addTempToString(getTemp());
result += addFanToString(getFan(), kHitachiAcFanHigh, kHitachiAcFanLow,
kHitachiAcFanAuto, kHitachiAcFanAuto,
kHitachiAcFanMed);
result += addBoolToString(getSwingVertical(), F("Swing (Vertical)"));
result += addBoolToString(getSwingHorizontal(), F("Swing (Horizontal)"));
return result;
}
#if (DECODE_HITACHI_AC || DECODE_HITACHI_AC1 || DECODE_HITACHI_AC2)
// Decode the supplied Hitachi A/C message.
//
// Args:
// results: Ptr to the data to decode and where to store the decode result.
// nbits: The number of data bits to expect.
// Typically kHitachiAcBits, kHitachiAc1Bits, kHitachiAc2Bits
// strict: Flag indicating if we should perform strict matching.
// Returns:
// boolean: True if it can decode it, false if it can't.
//
// Status: ALPHA / Untested.
//
// Supported devices:
// Hitachi A/C Series VI (Circa 2007) / Remote: LT0541-HTA
//
// Ref:
// https://github.com/crankyoldgit/IRremoteESP8266/issues/417
// https://github.com/crankyoldgit/IRremoteESP8266/issues/453
bool IRrecv::decodeHitachiAC(decode_results *results, const uint16_t nbits,
const bool strict) {
const uint8_t k_tolerance = _tolerance + 5;
if (results->rawlen < 2 * nbits + kHeader + kFooter - 1)
return false; // Can't possibly be a valid HitachiAC message.
if (strict) {
switch (nbits) {
case kHitachiAcBits:
case kHitachiAc1Bits:
case kHitachiAc2Bits:
break; // Okay to continue.
default:
return false; // Not strictly a Hitachi message.
}
}
uint16_t offset = kStartOffset;
uint16_t hmark;
uint32_t hspace;
if (nbits == kHitachiAc1Bits) {
hmark = kHitachiAc1HdrMark;
hspace = kHitachiAc1HdrSpace;
} else {
hmark = kHitachiAcHdrMark;
hspace = kHitachiAcHdrSpace;
}
// Match Header + Data + Footer
if (!matchGeneric(results->rawbuf + offset, results->state,
results->rawlen - offset, nbits,
hmark, hspace,
kHitachiAcBitMark, kHitachiAcOneSpace,
kHitachiAcBitMark, kHitachiAcZeroSpace,
kHitachiAcBitMark, kHitachiAcMinGap, true,
k_tolerance)) return false;
// Compliance
if (strict) {
if (nbits / 8 == kHitachiAcStateLength &&
!IRHitachiAc::validChecksum(results->state, kHitachiAcStateLength))
return false;
}
// Success
switch (nbits) {
case kHitachiAc1Bits:
results->decode_type = HITACHI_AC1;
break;
case kHitachiAc2Bits:
results->decode_type = HITACHI_AC2;
break;
case kHitachiAcBits:
default:
results->decode_type = HITACHI_AC;
}
results->bits = nbits;
// No need to record the state as we stored it as we decoded it.
// As we use result->state, we don't record value, address, or command as it
// is a union data type.
return true;
}
#endif // (DECODE_HITACHI_AC || DECODE_HITACHI_AC1 || DECODE_HITACHI_AC2)