mirror of
https://github.com/crankyoldgit/IRremoteESP8266.git
synced 2026-01-12 00:05:10 +08:00
Transcold: Add detailed support. (#1278)
* Manually merge in the work done by @iamDshetty * Lots of code clean up to get it working properly. - Code style - Better constants. - Lint - Bugs - Remove dead/unused code & settings. * Add unit test for basic features. * Settings/Features supported: - Mode - Fan speed - Power - Temp - Swing * Sending confirmed working in: https://github.com/crankyoldgit/IRremoteESP8266/issues/1256#issuecomment-696055698 Fixes #1256
This commit is contained in:
72
src/IRac.cpp
72
src/IRac.cpp
@@ -41,6 +41,7 @@
|
||||
#include "ir_Technibel.h"
|
||||
#include "ir_Teco.h"
|
||||
#include "ir_Toshiba.h"
|
||||
#include "ir_Transcold.h"
|
||||
#include "ir_Trotec.h"
|
||||
#include "ir_Vestel.h"
|
||||
#include "ir_Voltas.h"
|
||||
@@ -261,6 +262,9 @@ bool IRac::isProtocolSupported(const decode_type_t protocol) {
|
||||
#if SEND_TOSHIBA_AC
|
||||
case decode_type_t::TOSHIBA_AC:
|
||||
#endif
|
||||
#if SEND_TRANSCOLD
|
||||
case decode_type_t::TRANSCOLD:
|
||||
#endif
|
||||
#if SEND_TROTEC
|
||||
case decode_type_t::TROTEC:
|
||||
#endif
|
||||
@@ -2013,6 +2017,48 @@ void IRac::whirlpool(IRWhirlpoolAc *ac, const whirlpool_ac_remote_model_t model,
|
||||
}
|
||||
#endif // SEND_WHIRLPOOL_AC
|
||||
|
||||
#if SEND_TRANSCOLD
|
||||
/// Send a Transcold A/C message with the supplied settings.
|
||||
/// @note May result in multiple messages being sent.
|
||||
/// @param[in, out] ac A Ptr to an IRTranscoldAc object to use.
|
||||
/// @param[in] on The power setting.
|
||||
/// @param[in] mode The operation mode setting.
|
||||
/// @param[in] degrees The temperature setting in degrees.
|
||||
/// @param[in] fan The speed setting for the fan.
|
||||
/// @param[in] swingv The vertical swing setting.
|
||||
/// @param[in] swingh The horizontal swing setting.
|
||||
/// @note -1 is Off, >= 0 is on.
|
||||
void IRac::transcold(IRTranscoldAc *ac,
|
||||
const bool on, const stdAc::opmode_t mode,
|
||||
const float degrees, const stdAc::fanspeed_t fan,
|
||||
const stdAc::swingv_t swingv,
|
||||
const stdAc::swingh_t swingh) {
|
||||
ac->begin();
|
||||
ac->setPower(on);
|
||||
if (!on) {
|
||||
// after turn off AC no more commands should
|
||||
// be accepted
|
||||
ac->send();
|
||||
return;
|
||||
}
|
||||
ac->setMode(ac->convertMode(mode));
|
||||
ac->setTemp(degrees);
|
||||
ac->setFan(ac->convertFan(fan));
|
||||
// No Filter setting available.
|
||||
// No Beep setting available.
|
||||
// No Clock setting available.
|
||||
// No Econo setting available.
|
||||
// No Quiet setting available.
|
||||
if (swingv != stdAc::swingv_t::kOff || swingh != stdAc::swingh_t::kOff) {
|
||||
// Swing has a special command that needs to be sent independently.
|
||||
ac->setSwing();
|
||||
ac->send();
|
||||
}
|
||||
|
||||
ac->send();
|
||||
}
|
||||
#endif // SEND_TRANSCOLD
|
||||
|
||||
/// Create a new state base on the provided state that has been suitably fixed.
|
||||
/// @note This is for use with Home Assistant, which requires mode to be off if
|
||||
/// the power is off.
|
||||
@@ -2040,6 +2086,7 @@ stdAc::state_t IRac::handleToggles(const stdAc::state_t desired,
|
||||
// Check if we have to handle toggle settings for specific A/C protocols.
|
||||
switch (desired.protocol) {
|
||||
case decode_type_t::COOLIX:
|
||||
case decode_type_t::TRANSCOLD:
|
||||
if ((desired.swingv == stdAc::swingv_t::kOff) ^
|
||||
(prev->swingv == stdAc::swingv_t::kOff)) // It changed, so toggle.
|
||||
result.swingv = stdAc::swingv_t::kAuto;
|
||||
@@ -2569,6 +2616,15 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) {
|
||||
break;
|
||||
}
|
||||
#endif // SEND_WHIRLPOOL_AC
|
||||
#if SEND_TRANSCOLD
|
||||
case TRANSCOLD:
|
||||
{
|
||||
IRTranscoldAc ac(_pin, _inverted, _modulation);
|
||||
transcold(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv,
|
||||
send.swingh);
|
||||
break;
|
||||
}
|
||||
#endif // SEND_TRANSCOLD_AC
|
||||
default:
|
||||
return false; // Fail, didn't match anything.
|
||||
}
|
||||
@@ -3272,6 +3328,14 @@ namespace IRAcUtils {
|
||||
return ac.isValidLgAc() ? ac.toString() : "";
|
||||
}
|
||||
#endif // DECODE_LG
|
||||
#if DECODE_TRANSCOLD
|
||||
case decode_type_t::TRANSCOLD: {
|
||||
IRTranscoldAc ac(kGpioUnused);
|
||||
ac.on();
|
||||
ac.setRaw(result->value); // TRANSCOLD uses value instead of state.
|
||||
return ac.toString();
|
||||
}
|
||||
#endif // DECODE_TRANSCOLD
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@@ -3668,6 +3732,14 @@ namespace IRAcUtils {
|
||||
break;
|
||||
}
|
||||
#endif // DECODE_WHIRLPOOL_AC
|
||||
#if DECODE_TRANSCOLD
|
||||
case decode_type_t::TRANSCOLD: {
|
||||
IRTranscoldAc ac(kGpioUnused);
|
||||
ac.setRaw(decode->value); // TRANSCOLD Uses value instead of state.
|
||||
*result = ac.toCommon(prev);
|
||||
break;
|
||||
}
|
||||
#endif // DECODE_TRANSCOLD
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "ir_Technibel.h"
|
||||
#include "ir_Teco.h"
|
||||
#include "ir_Toshiba.h"
|
||||
#include "ir_Transcold.h"
|
||||
#include "ir_Trotec.h"
|
||||
#include "ir_Vestel.h"
|
||||
#include "ir_Voltas.h"
|
||||
@@ -429,6 +430,12 @@ void electra(IRElectraAc *ac,
|
||||
const bool turbo, const bool light,
|
||||
const int16_t sleep = -1, const int16_t clock = -1);
|
||||
#endif // SEND_WHIRLPOOL_AC
|
||||
#if SEND_TRANSCOLD
|
||||
void transcold(IRTranscoldAc *ac,
|
||||
const bool on, const stdAc::opmode_t mode, const float degrees,
|
||||
const stdAc::fanspeed_t fan,
|
||||
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh);
|
||||
#endif // SEND_TRANSCOLD
|
||||
static stdAc::state_t cleanState(const stdAc::state_t state);
|
||||
static stdAc::state_t handleToggles(const stdAc::state_t desired,
|
||||
const stdAc::state_t *prev = NULL);
|
||||
|
||||
@@ -1050,7 +1050,8 @@ const uint16_t kToshibaACStateLengthShort = kToshibaACStateLength - 2;
|
||||
const uint16_t kToshibaACBitsShort = kToshibaACStateLengthShort * 8;
|
||||
const uint16_t kToshibaACStateLengthLong = kToshibaACStateLength + 1;
|
||||
const uint16_t kToshibaACBitsLong = kToshibaACStateLengthLong * 8;
|
||||
const uint16_t kTranscoldBits = 48;
|
||||
const uint16_t kTranscoldBits = 24;
|
||||
const uint16_t kTranscoldDefaultRepeat = kNoRepeat;
|
||||
const uint16_t kTrotecStateLength = 9;
|
||||
const uint16_t kTrotecBits = kTrotecStateLength * 8;
|
||||
const uint16_t kTrotecDefaultRepeat = kNoRepeat;
|
||||
|
||||
@@ -628,6 +628,7 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) {
|
||||
case MIDEA24:
|
||||
case NIKAI:
|
||||
case RCMM:
|
||||
case TRANSCOLD:
|
||||
return 24;
|
||||
case LG:
|
||||
case LG2:
|
||||
@@ -655,7 +656,6 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) {
|
||||
return kSanyoLC7461Bits; // 42
|
||||
case GOODWEATHER:
|
||||
case MIDEA:
|
||||
case TRANSCOLD:
|
||||
case PANASONIC:
|
||||
return 48;
|
||||
case MAGIQUEST:
|
||||
|
||||
@@ -659,7 +659,7 @@ class IRsend {
|
||||
#endif // SEND_METZ
|
||||
#if SEND_TRANSCOLD
|
||||
void sendTranscold(const uint64_t data, const uint16_t nbits = kTranscoldBits,
|
||||
const uint16_t repeat = kNoRepeat);
|
||||
const uint16_t repeat = kTranscoldDefaultRepeat);
|
||||
#endif // SEND_TRANSCOLD
|
||||
|
||||
protected:
|
||||
|
||||
@@ -1,93 +1,541 @@
|
||||
// Copyright 2020 David Conran (crankyoldgit)
|
||||
// Copyright 2020 Chandrashekar Shetty (iamDshetty)
|
||||
// Copyright 2020 crankyoldgit
|
||||
|
||||
/// @file
|
||||
/// @brief Support for Transcold protocol.
|
||||
/// @brief Support for Transcold A/C protocols.
|
||||
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1256
|
||||
|
||||
// Supports:
|
||||
// Brand: Transcold, Model: M1-F-NO-6 A/C
|
||||
|
||||
#include "ir_Transcold.h"
|
||||
#include <algorithm>
|
||||
#ifndef ARDUINO
|
||||
#include <string>
|
||||
#endif
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtext.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
// Constants
|
||||
|
||||
const uint16_t kTranscoldHdrMark = 5944; ///< uSeconds.
|
||||
const uint16_t kTranscoldBitMark = 555; ///< uSeconds.
|
||||
const uint16_t kTranscoldHdrSpace = 7563; ///< uSeconds.
|
||||
const uint16_t kTranscoldOneSpace = 3556; ///< uSeconds.
|
||||
const uint16_t kTranscoldZeroSpace = 1526; ///< uSeconds.
|
||||
const uint16_t kTranscoldFreq = 38000; ///< Hz.
|
||||
|
||||
using irutils::addBoolToString;
|
||||
using irutils::addIntToString;
|
||||
using irutils::addLabeledString;
|
||||
using irutils::addModeToString;
|
||||
using irutils::addTempToString;
|
||||
using irutils::setBit;
|
||||
using irutils::setBits;
|
||||
|
||||
|
||||
#if SEND_TRANSCOLD
|
||||
/// Send a Transcold formatted message.
|
||||
/// Status: BETA / Probably works, needs to be tested on a real device.
|
||||
/// @note Data bit ordering not yet confirmed. MSBF at present.
|
||||
/// @param[in] data containing the IR command.
|
||||
/// @param[in] nbits Nr. of bits to send. usually kTranscoldBits
|
||||
/// @param[in] repeat Nr. of times the message is to be repeated.
|
||||
void IRsend::sendTranscold(const uint64_t data, const uint16_t nbits,
|
||||
const uint16_t repeat) {
|
||||
/// Send a Transcold message
|
||||
/// Status: STABLE / Confirmed 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.
|
||||
void IRsend::sendTranscold(uint64_t data, uint16_t nbits, uint16_t repeat) {
|
||||
if (nbits % 8 != 0) return; // nbits is required to be a multiple of 8.
|
||||
|
||||
// Set IR carrier frequency
|
||||
enableIROut(38);
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
sendGeneric(kTranscoldHdrMark, kTranscoldHdrSpace, // Header
|
||||
kTranscoldBitMark, kTranscoldOneSpace, // Data
|
||||
kTranscoldBitMark, kTranscoldZeroSpace,
|
||||
kTranscoldBitMark, kTranscoldHdrSpace, // Footer (1 of 2).
|
||||
data, nbits, // Payload
|
||||
kTranscoldFreq, true, 0, // Repeat handled by outer loop.
|
||||
kDutyDefault);
|
||||
// Footer (2 of 2)
|
||||
// Header
|
||||
mark(kTranscoldHdrMark);
|
||||
space(kTranscoldHdrSpace);
|
||||
// Data
|
||||
// Break data into byte segments, starting at the Most Significant
|
||||
// Byte. Each byte then being sent normal, then followed inverted.
|
||||
for (uint16_t i = 8; i <= nbits; i += 8) {
|
||||
// Grab a bytes worth of data.
|
||||
// uint8_t segment = (data >> (nbits - i)) & 0xFF;
|
||||
uint8_t segment = GETBITS64(data, nbits - i, 8);
|
||||
// Normal + Inverted
|
||||
uint16_t both = (segment << 8) | (~segment & 0xFF);
|
||||
sendData(kTranscoldBitMark, kTranscoldOneSpace, kTranscoldBitMark,
|
||||
kTranscoldZeroSpace, both, 16, true);
|
||||
}
|
||||
// Footer
|
||||
mark(kTranscoldBitMark);
|
||||
space(kDefaultMessageGap); // A guess of the gap between messages.
|
||||
space(kTranscoldHdrSpace);
|
||||
mark(kTranscoldBitMark);
|
||||
space(kDefaultMessageGap);
|
||||
}
|
||||
}
|
||||
#endif // SEND_TRANSCOLD
|
||||
|
||||
/// 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?
|
||||
IRTranscoldAc::IRTranscoldAc(const uint16_t pin, const bool inverted,
|
||||
const bool use_modulation)
|
||||
: _irsend(pin, inverted, use_modulation) { stateReset(); }
|
||||
|
||||
/// Reset the internal state to a fixed known good state.
|
||||
void IRTranscoldAc::stateReset() {
|
||||
setRaw(kTranscoldKnownGoodState);
|
||||
saved_state = kTranscoldKnownGoodState;
|
||||
powerFlag = false;
|
||||
swingFlag = false;
|
||||
swingHFlag = false;
|
||||
swingVFlag = false;
|
||||
}
|
||||
|
||||
/// Set up hardware to be able to send a message.
|
||||
void IRTranscoldAc::begin() { _irsend.begin(); }
|
||||
|
||||
#if SEND_TRANSCOLD
|
||||
/// Send the current internal state as an IR message.
|
||||
/// @param[in] repeat Nr. of times the message will be repeated.
|
||||
void IRTranscoldAc::send(uint16_t repeat) {
|
||||
_irsend.sendTranscold(remote_state, kTranscoldBits, repeat);
|
||||
// make sure to remove special state from remote_state
|
||||
// after command has being transmitted.
|
||||
recoverSavedState();
|
||||
}
|
||||
#endif // SEND_TRANSCOLD
|
||||
|
||||
/// Get a copy of the internal state as a valid code for this protocol.
|
||||
/// @return A valid code for this protocol based on the current internal state.
|
||||
uint32_t IRTranscoldAc::getRaw() { return remote_state; }
|
||||
|
||||
/// Set the internal state from a valid code for this protocol.
|
||||
/// @param[in] new_code A valid code for this protocol.
|
||||
void IRTranscoldAc::setRaw(const uint32_t new_code) {
|
||||
powerFlag = true; // Everything that is not the special power off mesg is On.
|
||||
if (!handleSpecialState(new_code)) {
|
||||
// it isn`t special so might affect Temp|mode|Fan
|
||||
if (new_code == kTranscoldCmdFan) {
|
||||
setMode(kTranscoldFan);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// must be a command changing Temp|Mode|Fan
|
||||
// it is safe to just copy to remote var
|
||||
remote_state = new_code;
|
||||
}
|
||||
|
||||
/// Is the current state is a special state?
|
||||
/// @return true, if it is. false if it isn't.
|
||||
bool IRTranscoldAc::isSpecialState(void) {
|
||||
switch (remote_state) {
|
||||
case kTranscoldOff:
|
||||
case kTranscoldSwing: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Adjust any internal settings based on the type of special state we are
|
||||
/// supplied. Does nothing if it isn't a special state.
|
||||
/// @param[in] data The state we need to act upon.
|
||||
/// @note Special state means commands that are not affecting
|
||||
/// Temperature/Mode/Fan
|
||||
/// @return true, if it is a special state. false if it isn't.
|
||||
bool IRTranscoldAc::handleSpecialState(const uint32_t data) {
|
||||
switch (data) {
|
||||
case kTranscoldOff:
|
||||
powerFlag = false;
|
||||
break;
|
||||
case kTranscoldSwing:
|
||||
swingFlag = !swingFlag;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Backup the current internal state as long as it isn't a special state.
|
||||
/// @note: Must be called before every special state to make sure the
|
||||
/// remote_state is safe
|
||||
void IRTranscoldAc::updateSavedState(void) {
|
||||
if (!isSpecialState()) saved_state = remote_state;
|
||||
}
|
||||
|
||||
/// Restore the current internal state from backup as long as it isn't a
|
||||
/// special state.
|
||||
void IRTranscoldAc::recoverSavedState(void) {
|
||||
// If the current state is a special one, last known normal one.
|
||||
if (isSpecialState()) remote_state = saved_state;
|
||||
// If the saved_state was also a special state, reset as we expect a normal
|
||||
// state out of all this.
|
||||
if (isSpecialState()) stateReset();
|
||||
}
|
||||
|
||||
/// Set the raw (native) temperature value.
|
||||
/// @note Bypasses any checks.
|
||||
/// @param[in] code The desired native temperature.
|
||||
void IRTranscoldAc::setTempRaw(const uint8_t code) {
|
||||
setBits(&remote_state, kTranscoldTempOffset, kTranscoldTempSize, code);
|
||||
}
|
||||
|
||||
/// Get the raw (native) temperature value.
|
||||
/// @return The native temperature value.
|
||||
uint8_t IRTranscoldAc::getTempRaw() {
|
||||
return GETBITS32(remote_state, kTranscoldTempOffset, kTranscoldTempSize);
|
||||
}
|
||||
|
||||
/// Set the temperature.
|
||||
/// @param[in] desired The temperature in degrees celsius.
|
||||
void IRTranscoldAc::setTemp(const uint8_t desired) {
|
||||
// Range check.
|
||||
uint8_t temp = std::min(desired, kTranscoldTempMax);
|
||||
temp = std::max(temp, kTranscoldTempMin) - kTranscoldTempMin + 1;
|
||||
setTempRaw(reverseBits(invertBits(temp, kTranscoldTempSize),
|
||||
kTranscoldTempSize));
|
||||
}
|
||||
|
||||
/// Get the current temperature setting.
|
||||
/// @return The current setting for temp. in degrees celsius.
|
||||
uint8_t IRTranscoldAc::getTemp() {
|
||||
return reverseBits(invertBits(getTempRaw(), kTranscoldTempSize),
|
||||
kTranscoldTempSize) + kTranscoldTempMin - 1;
|
||||
}
|
||||
|
||||
/// Get the value of the current power setting.
|
||||
/// @return true, the setting is on. false, the setting is off.
|
||||
bool IRTranscoldAc::getPower() {
|
||||
// There is only an off state. Everything else is "on".
|
||||
return powerFlag;
|
||||
}
|
||||
|
||||
/// Change the power setting.
|
||||
/// @param[in] on true, the setting is on. false, the setting is off.
|
||||
void IRTranscoldAc::setPower(const bool on) {
|
||||
if (!on) {
|
||||
updateSavedState();
|
||||
remote_state = kTranscoldOff;
|
||||
} else if (!powerFlag) {
|
||||
// at this point remote_state must be ready
|
||||
// to be transmitted
|
||||
recoverSavedState();
|
||||
}
|
||||
powerFlag = on;
|
||||
}
|
||||
|
||||
/// Change the power setting to On.
|
||||
void IRTranscoldAc::on(void) { setPower(true); }
|
||||
|
||||
/// Change the power setting to Off.
|
||||
void IRTranscoldAc::off(void) { setPower(false); }
|
||||
|
||||
/// Get the Swing setting of the A/C.
|
||||
/// @return true, the setting is on. false, the setting is off.
|
||||
bool IRTranscoldAc::getSwing() { return swingFlag; }
|
||||
|
||||
/// Toggle the Swing mode of the A/C.
|
||||
void IRTranscoldAc::setSwing() {
|
||||
// Assumes that repeated sending "swing" toggles the action on the device.
|
||||
updateSavedState();
|
||||
remote_state = kTranscoldSwing;
|
||||
swingFlag = !swingFlag;
|
||||
}
|
||||
|
||||
/// Set the operating mode of the A/C.
|
||||
/// @param[in] mode The desired operating mode.
|
||||
void IRTranscoldAc::setMode(const uint8_t mode) {
|
||||
uint32_t actualmode = mode;
|
||||
switch (actualmode) {
|
||||
case kTranscoldAuto:
|
||||
case kTranscoldDry:
|
||||
setFan(kTranscoldFanAuto0, false);
|
||||
break;
|
||||
case kTranscoldCool:
|
||||
case kTranscoldHeat:
|
||||
case kTranscoldFan:
|
||||
setFan(kTranscoldFanAuto, false);
|
||||
break;
|
||||
default: // Anything else, go with Auto mode.
|
||||
setMode(kTranscoldAuto);
|
||||
setFan(kTranscoldFanAuto0, false);
|
||||
return;
|
||||
}
|
||||
setTemp(getTemp());
|
||||
// Fan mode is a special case of Dry.
|
||||
if (mode == kTranscoldFan) {
|
||||
actualmode = kTranscoldDry;
|
||||
setTempRaw(kTranscoldFanTempCode);
|
||||
}
|
||||
setBits(&remote_state, kTranscoldModeOffset, kTranscoldModeSize, actualmode);
|
||||
}
|
||||
|
||||
/// Get the operating mode setting of the A/C.
|
||||
/// @return The current operating mode setting.
|
||||
uint8_t IRTranscoldAc::getMode() {
|
||||
uint8_t mode = GETBITS32(remote_state, kTranscoldModeOffset,
|
||||
kTranscoldModeSize);
|
||||
if (mode == kTranscoldDry)
|
||||
if (getTempRaw() == kTranscoldFanTempCode) return kTranscoldFan;
|
||||
return mode;
|
||||
}
|
||||
|
||||
/// Get the current fan speed setting.
|
||||
/// @return The current fan speed.
|
||||
uint8_t IRTranscoldAc::getFan() {
|
||||
return GETBITS32(remote_state, kTranscoldFanOffset, kTranscoldFanSize);
|
||||
}
|
||||
|
||||
/// Set the speed of the fan.
|
||||
/// @param[in] speed The desired setting.
|
||||
/// @param[in] modecheck Do we enforce any mode limitations before setting?
|
||||
void IRTranscoldAc::setFan(const uint8_t speed, const bool modecheck) {
|
||||
uint8_t newspeed = speed;
|
||||
switch (speed) {
|
||||
case kTranscoldFanAuto: // Dry & Auto mode can't have this speed.
|
||||
if (modecheck) {
|
||||
switch (getMode()) {
|
||||
case kTranscoldAuto:
|
||||
case kTranscoldDry:
|
||||
newspeed = kTranscoldFanAuto0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case kTranscoldFanAuto0: // Only Dry & Auto mode can have this speed.
|
||||
if (modecheck) {
|
||||
switch (getMode()) {
|
||||
case kTranscoldAuto:
|
||||
case kTranscoldDry: break;
|
||||
default: newspeed = kTranscoldFanAuto;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case kTranscoldFanMin:
|
||||
case kTranscoldFanMed:
|
||||
case kTranscoldFanMax:
|
||||
case kTranscoldFanZoneFollow:
|
||||
case kTranscoldFanFixed:
|
||||
break;
|
||||
default: // Unknown speed requested.
|
||||
newspeed = kTranscoldFanAuto;
|
||||
break;
|
||||
}
|
||||
setBits(&remote_state, kTranscoldFanOffset, kTranscoldFanSize, newspeed);
|
||||
}
|
||||
|
||||
/// Convert a standard A/C mode into its native mode.
|
||||
/// @param[in] mode A stdAc::opmode_t to be converted to it's native equivalent.
|
||||
/// @return The corresponding native mode.
|
||||
uint8_t IRTranscoldAc::convertMode(const stdAc::opmode_t mode) {
|
||||
switch (mode) {
|
||||
case stdAc::opmode_t::kCool: return kTranscoldCool;
|
||||
case stdAc::opmode_t::kHeat: return kTranscoldHeat;
|
||||
case stdAc::opmode_t::kDry: return kTranscoldDry;
|
||||
case stdAc::opmode_t::kFan: return kTranscoldFan;
|
||||
default: return kTranscoldAuto;
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a stdAc::fanspeed_t enum into it's native speed.
|
||||
/// @param[in] speed The enum to be converted.
|
||||
/// @return The native equivilant of the enum.
|
||||
uint8_t IRTranscoldAc::convertFan(const stdAc::fanspeed_t speed) {
|
||||
switch (speed) {
|
||||
case stdAc::fanspeed_t::kMin:
|
||||
case stdAc::fanspeed_t::kLow: return kTranscoldFanMin;
|
||||
case stdAc::fanspeed_t::kMedium: return kTranscoldFanMed;
|
||||
case stdAc::fanspeed_t::kHigh:
|
||||
case stdAc::fanspeed_t::kMax: return kTranscoldFanMax;
|
||||
default: return kTranscoldFanAuto;
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a native mode to it's common stdAc::opmode_t equivalent.
|
||||
/// @param[in] mode A native operation mode to be converted.
|
||||
/// @return The corresponding common stdAc::opmode_t mode.
|
||||
stdAc::opmode_t IRTranscoldAc::toCommonMode(const uint8_t mode) {
|
||||
switch (mode) {
|
||||
case kTranscoldCool: return stdAc::opmode_t::kCool;
|
||||
case kTranscoldHeat: return stdAc::opmode_t::kHeat;
|
||||
case kTranscoldDry: return stdAc::opmode_t::kDry;
|
||||
case kTranscoldFan: return stdAc::opmode_t::kFan;
|
||||
default: return stdAc::opmode_t::kAuto;
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a native fan speed into its stdAc equivilant.
|
||||
/// @param[in] speed The native setting to be converted.
|
||||
/// @return The stdAc equivilant of the native setting.
|
||||
stdAc::fanspeed_t IRTranscoldAc::toCommonFanSpeed(const uint8_t speed) {
|
||||
switch (speed) {
|
||||
case kTranscoldFanMax: return stdAc::fanspeed_t::kMax;
|
||||
case kTranscoldFanMed: return stdAc::fanspeed_t::kMedium;
|
||||
case kTranscoldFanMin: return stdAc::fanspeed_t::kMin;
|
||||
default: return stdAc::fanspeed_t::kAuto;
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the A/C state to it's common stdAc::state_t equivalent.
|
||||
/// @param[in] prev Ptr to the previous state if required.
|
||||
/// @return A stdAc::state_t state.
|
||||
stdAc::state_t IRTranscoldAc::toCommon(const stdAc::state_t *prev) {
|
||||
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.swingv = stdAc::swingv_t::kOff;
|
||||
}
|
||||
// Not supported.
|
||||
result.model = -1; // No models used.
|
||||
result.swingh = stdAc::swingh_t::kOff;
|
||||
result.turbo = false;
|
||||
result.clean = false;
|
||||
result.light = false;
|
||||
result.quiet = false;
|
||||
result.econo = false;
|
||||
result.filter = false;
|
||||
result.beep = false;
|
||||
result.clock = -1;
|
||||
result.sleep = -1;
|
||||
|
||||
// Supported.
|
||||
result.protocol = decode_type_t::TRANSCOLD;
|
||||
result.celsius = true;
|
||||
result.power = getPower();
|
||||
// Power off state no other state info. Use the previous state if we have it.
|
||||
if (!result.power) return result;
|
||||
// Handle the special single command (Swing/Turbo/Light/Clean/Sleep) toggle
|
||||
// messages. These have no other state info so use the rest of the previous
|
||||
// state if we have it for them.
|
||||
if (getSwing()) {
|
||||
result.swingv = result.swingv != stdAc::swingv_t::kOff ?
|
||||
stdAc::swingv_t::kOff : stdAc::swingv_t::kAuto; // Invert swing.
|
||||
return result;
|
||||
}
|
||||
// Back to "normal" stateful messages.
|
||||
result.mode = toCommonMode(getMode());
|
||||
result.degrees = getTemp();
|
||||
result.fanspeed = toCommonFanSpeed(getFan());
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Convert the internal state into a human readable string.
|
||||
/// @return The current internal state expressed as a human readable String.
|
||||
String IRTranscoldAc::toString(void) {
|
||||
String result = "";
|
||||
result.reserve(100); // Reserve some heap for the string to reduce fragging.
|
||||
result += addBoolToString(getPower(), kPowerStr, false);
|
||||
if (!getPower()) return result; // If it's off, there is no other info.
|
||||
// Special modes.
|
||||
if (getSwing()) {
|
||||
result += kCommaSpaceStr;
|
||||
result += kSwingStr;
|
||||
result += kColonSpaceStr;
|
||||
result += kToggleStr;
|
||||
return result;
|
||||
}
|
||||
result += addModeToString(getMode(), kTranscoldAuto, kTranscoldCool,
|
||||
kTranscoldHeat, kTranscoldDry, kTranscoldFan);
|
||||
result += addIntToString(getFan(), kFanStr);
|
||||
result += kSpaceLBraceStr;
|
||||
switch (getFan()) {
|
||||
case kTranscoldFanAuto:
|
||||
result += kAutoStr;
|
||||
break;
|
||||
case kTranscoldFanAuto0:
|
||||
result += kAutoStr;
|
||||
result += '0';
|
||||
break;
|
||||
case kTranscoldFanMax:
|
||||
result += kMaxStr;
|
||||
break;
|
||||
case kTranscoldFanMin:
|
||||
result += kMinStr;
|
||||
break;
|
||||
case kTranscoldFanMed:
|
||||
result += kMedStr;
|
||||
break;
|
||||
case kTranscoldFanZoneFollow:
|
||||
result += kZoneFollowStr;
|
||||
break;
|
||||
case kTranscoldFanFixed:
|
||||
result += kFixedStr;
|
||||
break;
|
||||
default:
|
||||
result += kUnknownStr;
|
||||
}
|
||||
result += ')';
|
||||
// Fan mode doesn't have a temperature.
|
||||
if (getMode() != kTranscoldFan) result += addTempToString(getTemp());
|
||||
return result;
|
||||
}
|
||||
|
||||
#if DECODE_TRANSCOLD
|
||||
/// Decode the supplied Transcold message.
|
||||
/// Status: BETA / Probably works.
|
||||
/// @note Data bit ordering not yet confirmed. MSBF at present.
|
||||
/// Decode the supplied Transcold A/C message.
|
||||
/// Status: STABLE / Known 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.
|
||||
/// @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.
|
||||
bool IRrecv::decodeTranscold(decode_results *results, uint16_t offset,
|
||||
const uint16_t nbits, const bool strict) {
|
||||
if (results->rawlen < 2 * nbits + kHeader + (2 * kFooter) - offset)
|
||||
return false; // Too short a message to match.
|
||||
if (strict && nbits != kTranscoldBits)
|
||||
const uint16_t nbits, const bool strict) {
|
||||
// The protocol sends the data normal + inverted, alternating on
|
||||
// each byte. Hence twice the number of expected data bits.
|
||||
if (results->rawlen <= 2 * 2 * nbits + kHeader + kFooter - 1 + offset)
|
||||
return false;
|
||||
if (strict && nbits != kTranscoldBits) return false;
|
||||
if (nbits % 8 != 0) return false;
|
||||
|
||||
uint64_t data = 0;
|
||||
uint64_t inverted = 0;
|
||||
|
||||
// Match Header + Data + Footer (1 of 2)
|
||||
uint16_t used = matchGeneric(results->rawbuf + offset, &data,
|
||||
results->rawlen - offset, nbits,
|
||||
// Header
|
||||
kTranscoldHdrMark, kTranscoldHdrSpace,
|
||||
// Data
|
||||
kTranscoldBitMark, kTranscoldOneSpace,
|
||||
kTranscoldBitMark, kTranscoldZeroSpace,
|
||||
// Footer (1 of 2)
|
||||
kTranscoldBitMark, kTranscoldHdrSpace,
|
||||
false, _tolerance, 0, true);
|
||||
if (!used) return false; // Didn't fully match.
|
||||
offset += used;
|
||||
if (nbits > sizeof(data) * 8)
|
||||
return false; // We can't possibly capture a Transcold packet that big.
|
||||
|
||||
// Footer (2 of 2)
|
||||
if (!matchMark(results->rawbuf[offset++], kTranscoldBitMark))
|
||||
return false;
|
||||
if (offset <= results->rawlen &&
|
||||
// Header
|
||||
if (!matchMark(results->rawbuf[offset++], kTranscoldHdrMark)) return false;
|
||||
if (!matchSpace(results->rawbuf[offset++], kTranscoldHdrSpace)) return false;
|
||||
|
||||
// Data
|
||||
// Twice as many bits as there are normal plus inverted bits.
|
||||
for (uint16_t i = 0; i < nbits * 2; i++, offset++) {
|
||||
bool flip = (i / 8) % 2;
|
||||
if (!matchMark(results->rawbuf[offset++], kTranscoldBitMark))
|
||||
return false;
|
||||
if (matchSpace(results->rawbuf[offset], kTranscoldOneSpace)) {
|
||||
if (flip)
|
||||
inverted = (inverted << 1) | 1;
|
||||
else
|
||||
data = (data << 1) | 1;
|
||||
} else if (matchSpace(results->rawbuf[offset], kTranscoldZeroSpace)) {
|
||||
if (flip)
|
||||
inverted <<= 1;
|
||||
else
|
||||
data <<= 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Footer
|
||||
if (!matchMark(results->rawbuf[offset++], kTranscoldBitMark)) return false;
|
||||
if (!matchSpace(results->rawbuf[offset++], kTranscoldHdrSpace)) return false;
|
||||
if (!matchMark(results->rawbuf[offset++], kTranscoldBitMark)) return false;
|
||||
if (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset], kDefaultMessageGap))
|
||||
return false;
|
||||
|
||||
// Compliance
|
||||
if (strict && inverted != invertBits(data, nbits)) return false;
|
||||
|
||||
// Success
|
||||
results->decode_type = decode_type_t::TRANSCOLD;
|
||||
results->bits = nbits;
|
||||
results->value = data;
|
||||
results->command = 0;
|
||||
results->address = 0;
|
||||
results->command = 0;
|
||||
return true;
|
||||
}
|
||||
#endif // DECODE_TRANSCOLD
|
||||
|
||||
170
src/ir_Transcold.h
Normal file
170
src/ir_Transcold.h
Normal file
@@ -0,0 +1,170 @@
|
||||
// Copyright 2020 Chandrashekar Shetty (iamDshetty)
|
||||
// Copyright 2020 crankyoldgit
|
||||
|
||||
/// @file
|
||||
/// @brief Support for Transcold A/C protocols.
|
||||
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1256
|
||||
/// @see https://docs.google.com/spreadsheets/d/1qdoyB0FyJm85HPP9oXcfui0n4ztXBFlik6kiNlkO2IM/edit?usp=sharing
|
||||
|
||||
// Supports:
|
||||
// Brand: Transcold, Model: M1-F-NO-6 A/C
|
||||
|
||||
/***************************************************************************************************************
|
||||
|
||||
Raw Data Calculation: (UR 12)
|
||||
//ON button
|
||||
ON 24 Auto cool close (right) 111011110001000001100001100111100101010010101011
|
||||
|
||||
//OFF button
|
||||
OFF 24 Auto cool close (right) 111011110001000001110001100011100101010010101011
|
||||
|
||||
// MODE
|
||||
Hot mode 24 auto hot close (right) 111010010001011010100001010111100101010010101011
|
||||
Fan mode 0 (prev24) low fan close (right) "11101001 0001011000100001110111100101010010101011"
|
||||
Dry mode 24 low dry close (right) "11101001 0001011011000001 00111110 0101010010101011"
|
||||
Auto Mode 0(prev24) low auto close (right) "11101001 0001011011100001 00011110 0101010010101011"
|
||||
Cool Mode 24 low cool close (right) "11101001 0001011001100001 10011110 0101010010101011"
|
||||
|
||||
//FAN SPEED
|
||||
fan Speed low 24 low cool close (right) "11101001 0001011001100001 10011110 0101010010101011"
|
||||
fan Speed medium 24 medium cool close (right) "11101101 000100100110000110011110 0101010010101011"
|
||||
fan Speed high 24 high cool close (right) "11101011 000101000110000110011110 0101010010101011"
|
||||
fan Speed auto 24 auto cool close (right) "11101111 000100000110000110011110 0101010010101011"
|
||||
|
||||
//SWING
|
||||
Swing open 24 auto cool open (left) "11110111 000010000110000110011110 0101010010101011"
|
||||
Swing close 24 auto cool close (right) "11101111 000100000110000110011110 0101010010101011"
|
||||
|
||||
//TEMPERATURE
|
||||
temp 30degC Auto cool close (right) 111011110001000001100100100100010101010010101011
|
||||
temp 29 Auto cool close (right) 111011110001000001101100100100010101010010101011
|
||||
temp 28 Auto cool close (right) 111011110001000001100010100100010101010010101011
|
||||
temp 27 Auto cool close (right) 111011110001000001101010100100010101010010101011
|
||||
temp 26 Auto cool close (right) 111011110001000001100110100100010101010010101011
|
||||
temp 25 Auto cool close (right) 111011110001000001101110100100010101010010101011
|
||||
temp 24 Auto cool close (right) 111011110001000001100001100111100101010010101011
|
||||
temp 23 Auto cool close (right) 111011110001000001101001100101100101010010101011
|
||||
temp 22 Auto cool close (right) 111011110001000001100101100101100101010010101011
|
||||
temp 21 Auto cool close (right) 111011110001000001101101100101100101010010101011
|
||||
temp 20 Auto cool close (right) 111011110001000001100011100101100101010010101011
|
||||
temp 19 Auto cool close (right) 111011110001000001101011100101100101010010101011
|
||||
temp 18 Auto cool close (right) 111011110001000001100111100110000101010010101011
|
||||
temp 17 Auto cool close (right) 111011110001000001100111100110000101010010101011
|
||||
temp 16 Auto cool close (right) 111011110001000001100111100110000101010010101011
|
||||
|
||||
**************************************************************************************************************/
|
||||
|
||||
#ifndef IR_TRANSCOLD_H_
|
||||
#define IR_TRANSCOLD_H_
|
||||
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
#ifndef UNIT_TEST
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
#include "IRremoteESP8266.h"
|
||||
#include "IRsend.h"
|
||||
#ifdef UNIT_TEST
|
||||
#include "IRsend_test.h"
|
||||
#endif
|
||||
|
||||
// Constants
|
||||
// Modes
|
||||
const uint8_t kTranscoldCool = 0b0110;
|
||||
const uint8_t kTranscoldDry = 0b1100;
|
||||
const uint8_t kTranscoldAuto = 0b1110;
|
||||
const uint8_t kTranscoldHeat = 0b1010;
|
||||
const uint8_t kTranscoldFan = 0b0010;
|
||||
const uint8_t kTranscoldModeOffset = 12;
|
||||
const uint8_t kTranscoldModeSize = 4;
|
||||
|
||||
// Fan Control
|
||||
const uint8_t kTranscoldFanOffset = 16;
|
||||
const uint8_t kTranscoldFanSize = 4;
|
||||
const uint8_t kTranscoldFanMin = 0b1001;
|
||||
const uint8_t kTranscoldFanMed = 0b1101;
|
||||
const uint8_t kTranscoldFanMax = 0b1011;
|
||||
const uint8_t kTranscoldFanAuto = 0b1111;
|
||||
const uint8_t kTranscoldFanAuto0 = 0b0110;
|
||||
const uint8_t kTranscoldFanZoneFollow = 0b0000;
|
||||
const uint8_t kTranscoldFanFixed = 0b1100;
|
||||
|
||||
// Temperature
|
||||
const uint8_t kTranscoldTempMin = 18; // Celsius
|
||||
const uint8_t kTranscoldTempMax = 30; // Celsius
|
||||
const uint8_t kTranscoldFanTempCode = 0b1111; // Part of Fan Mode.
|
||||
const uint8_t kTranscoldTempOffset = 8;
|
||||
const uint8_t kTranscoldTempSize = 4;
|
||||
|
||||
const uint8_t kTranscoldPrefix = 0b0000;
|
||||
const uint8_t kTranscoldUnknown = 0xFF;
|
||||
const uint32_t kTranscoldOff = 0b111011110111100101010100;
|
||||
const uint32_t kTranscoldSwing = 0b111001110110000101010100;
|
||||
const uint32_t kTranscoldSwingH = 0b111101110110000101010100; // NA
|
||||
const uint32_t kTranscoldSwingV = 0b111001110110000101010100; // NA
|
||||
const uint32_t kTranscoldCmdFan = 0b111011110110000101010100; // NA
|
||||
|
||||
const uint32_t kTranscoldKnownGoodState = 0xE96554;
|
||||
|
||||
// Classes
|
||||
class IRTranscoldAc {
|
||||
public:
|
||||
explicit IRTranscoldAc(const uint16_t pin, const bool inverted = false,
|
||||
const bool use_modulation = true);
|
||||
void stateReset();
|
||||
#if SEND_TRANSCOLD
|
||||
void send(const uint16_t repeat = kTranscoldDefaultRepeat);
|
||||
/// Run the calibration to calculate uSec timing offsets for this platform.
|
||||
/// @return The uSec timing offset needed per modulation of the IR Led.
|
||||
/// @note This will produce a 65ms IR signal pulse at 38kHz.
|
||||
/// Only ever needs to be run once per object instantiation, if at all.
|
||||
int8_t calibrate(void) { return _irsend.calibrate(); }
|
||||
#endif // SEND_TRANSCOLD
|
||||
void begin();
|
||||
void on();
|
||||
void off();
|
||||
void setPower(const bool state);
|
||||
bool getPower();
|
||||
void setTemp(const uint8_t temp);
|
||||
uint8_t getTemp();
|
||||
void setFan(const uint8_t speed, const bool modecheck = true);
|
||||
uint8_t getFan();
|
||||
void setMode(const uint8_t mode);
|
||||
uint8_t getMode();
|
||||
void setSwing();
|
||||
bool getSwing();
|
||||
uint32_t getRaw();
|
||||
void setRaw(const uint32_t new_code);
|
||||
uint8_t convertMode(const stdAc::opmode_t mode);
|
||||
uint8_t convertFan(const stdAc::fanspeed_t speed);
|
||||
static stdAc::opmode_t toCommonMode(const uint8_t mode);
|
||||
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
|
||||
stdAc::state_t toCommon(const stdAc::state_t *prev = NULL);
|
||||
String toString();
|
||||
#ifndef UNIT_TEST
|
||||
|
||||
private:
|
||||
IRsend _irsend; ///< Instance of the IR send class
|
||||
#else
|
||||
/// @cond IGNORE
|
||||
IRsendTest _irsend; ///< Instance of the testing IR send class
|
||||
/// @endcond
|
||||
#endif
|
||||
// internal state
|
||||
bool powerFlag;
|
||||
bool swingFlag;
|
||||
bool swingHFlag;
|
||||
bool swingVFlag;
|
||||
|
||||
uint32_t remote_state; ///< The state of the IR remote in IR code form.
|
||||
uint32_t saved_state; ///< Copy of the state if we required a special mode.
|
||||
void setTempRaw(const uint8_t code);
|
||||
uint8_t getTempRaw();
|
||||
bool isSpecialState(void);
|
||||
bool handleSpecialState(const uint32_t data);
|
||||
void updateSavedState(void);
|
||||
void recoverSavedState(void);
|
||||
uint32_t getNormalState(void);
|
||||
};
|
||||
|
||||
#endif // IR_TRANSCOLD_H_
|
||||
@@ -1467,6 +1467,36 @@ TEST(TestIRac, Toshiba) {
|
||||
ac._irsend.outputStr());
|
||||
}
|
||||
|
||||
TEST(TestIRac, Transcold) {
|
||||
IRTranscoldAc ac(kGpioUnused);
|
||||
IRac irac(kGpioUnused);
|
||||
IRrecv capture(kGpioUnused);
|
||||
char expected[] =
|
||||
"Power: On, Mode: 6 (Cool), Fan: 11 (Max), Temp: 19C";
|
||||
|
||||
ac.begin();
|
||||
irac.transcold(&ac,
|
||||
true, // Power
|
||||
stdAc::opmode_t::kCool, // Mode
|
||||
19, // Celsius
|
||||
stdAc::fanspeed_t::kMax, // Fan speed
|
||||
stdAc::swingv_t::kOff, // Vertical swing
|
||||
stdAc::swingh_t::kOff); // Horizontal swing
|
||||
EXPECT_TRUE(ac.getPower());
|
||||
EXPECT_EQ(kTranscoldCool, ac.getMode());
|
||||
EXPECT_EQ(19, ac.getTemp());
|
||||
EXPECT_EQ(kTranscoldFanMax, ac.getFan());
|
||||
EXPECT_FALSE(ac.getSwing());
|
||||
ASSERT_EQ(expected, ac.toString());
|
||||
ac._irsend.makeDecodeResult();
|
||||
EXPECT_TRUE(capture.decode(&ac._irsend.capture));
|
||||
ASSERT_EQ(decode_type_t::TRANSCOLD, ac._irsend.capture.decode_type);
|
||||
ASSERT_EQ(kTranscoldBits, ac._irsend.capture.bits);
|
||||
ASSERT_EQ(expected, IRAcUtils::resultAcToString(&ac._irsend.capture));
|
||||
stdAc::state_t r, p;
|
||||
ASSERT_TRUE(IRAcUtils::decodeToState(&ac._irsend.capture, &r, &p));
|
||||
}
|
||||
|
||||
TEST(TestIRac, Trotec) {
|
||||
IRTrotecESP ac(kGpioUnused);
|
||||
IRac irac(kGpioUnused);
|
||||
|
||||
@@ -34,9 +34,14 @@ TEST(TestDecodeTranscold, RealExample) {
|
||||
ASSERT_TRUE(irrecv.decode(&irsend.capture));
|
||||
ASSERT_EQ(decode_type_t::TRANSCOLD, irsend.capture.decode_type);
|
||||
ASSERT_EQ(kTranscoldBits, irsend.capture.bits);
|
||||
EXPECT_EQ(0xE916659A54AB, irsend.capture.value);
|
||||
EXPECT_EQ(0xE96554, irsend.capture.value);
|
||||
EXPECT_EQ(0x0, irsend.capture.command);
|
||||
EXPECT_EQ(0x0, irsend.capture.address);
|
||||
EXPECT_EQ(
|
||||
"Power: On, Mode: 6 (Cool), Fan: 9 (Min), Temp: 22C",
|
||||
IRAcUtils::resultAcToString(&irsend.capture));
|
||||
stdAc::state_t r, p;
|
||||
ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &r, &p));
|
||||
}
|
||||
|
||||
TEST(TestDecodeTranscold, SyntheticExample) {
|
||||
@@ -44,13 +49,13 @@ TEST(TestDecodeTranscold, SyntheticExample) {
|
||||
IRrecv irrecv(kGpioUnused);
|
||||
irsend.begin();
|
||||
irsend.reset();
|
||||
irsend.sendTranscold(0xE916659A54AB);
|
||||
irsend.sendTranscold(0xE96554);
|
||||
irsend.makeDecodeResult();
|
||||
|
||||
ASSERT_TRUE(irrecv.decode(&irsend.capture));
|
||||
EXPECT_EQ(decode_type_t::TRANSCOLD, irsend.capture.decode_type);
|
||||
EXPECT_EQ(kTranscoldBits, irsend.capture.bits);
|
||||
EXPECT_EQ(0xE916659A54AB, irsend.capture.value);
|
||||
EXPECT_EQ(0xE96554, irsend.capture.value);
|
||||
EXPECT_EQ(0x0, irsend.capture.command);
|
||||
EXPECT_EQ(0x0, irsend.capture.address);
|
||||
|
||||
@@ -72,7 +77,134 @@ TEST(TestUtils, Housekeeping) {
|
||||
ASSERT_EQ("TRANSCOLD", typeToString(decode_type_t::TRANSCOLD));
|
||||
ASSERT_EQ(decode_type_t::TRANSCOLD, strToDecodeType("TRANSCOLD"));
|
||||
ASSERT_FALSE(hasACState(decode_type_t::TRANSCOLD));
|
||||
ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::TRANSCOLD));
|
||||
ASSERT_TRUE(IRac::isProtocolSupported(decode_type_t::TRANSCOLD));
|
||||
ASSERT_EQ(kTranscoldBits, IRsend::defaultBits(decode_type_t::TRANSCOLD));
|
||||
ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::TRANSCOLD));
|
||||
ASSERT_EQ(kTranscoldDefaultRepeat,
|
||||
IRsend::minRepeats(decode_type_t::TRANSCOLD));
|
||||
}
|
||||
|
||||
// Tests for the IRTranscoldAc class.
|
||||
TEST(TestTranscoldAcClass, SetAndGetRaw) {
|
||||
IRTranscoldAc ac(kGpioUnused);
|
||||
|
||||
ac.setRaw(0xB21F28);
|
||||
EXPECT_EQ(0xB21F28, ac.getRaw());
|
||||
ac.setRaw(kTranscoldKnownGoodState);
|
||||
EXPECT_EQ(kTranscoldKnownGoodState, ac.getRaw());
|
||||
}
|
||||
|
||||
TEST(TestTranscoldAcClass, SetAndGetTemp) {
|
||||
IRTranscoldAc ac(kGpioUnused);
|
||||
|
||||
ac.setTemp(25);
|
||||
EXPECT_EQ(25, ac.getTemp());
|
||||
ac.setTemp(kTranscoldTempMin);
|
||||
EXPECT_EQ(kTranscoldTempMin, ac.getTemp());
|
||||
ac.setTemp(kTranscoldTempMax);
|
||||
EXPECT_EQ(kTranscoldTempMax, ac.getTemp());
|
||||
ac.setTemp(kTranscoldTempMin - 1);
|
||||
EXPECT_EQ(kTranscoldTempMin, ac.getTemp());
|
||||
ac.setTemp(kTranscoldTempMax + 1);
|
||||
EXPECT_EQ(kTranscoldTempMax, ac.getTemp());
|
||||
}
|
||||
|
||||
TEST(TestTranscoldAcClass, SetAndGetMode) {
|
||||
IRTranscoldAc ac(kGpioUnused);
|
||||
|
||||
ac.setMode(kTranscoldHeat);
|
||||
EXPECT_EQ(kTranscoldHeat, ac.getMode());
|
||||
ac.setMode(kTranscoldCool);
|
||||
EXPECT_EQ(kTranscoldCool, ac.getMode());
|
||||
ac.setMode(kTranscoldDry);
|
||||
EXPECT_EQ(kTranscoldDry, ac.getMode());
|
||||
ac.setMode(kTranscoldAuto);
|
||||
EXPECT_EQ(kTranscoldAuto, ac.getMode());
|
||||
ac.setMode(kTranscoldFan);
|
||||
EXPECT_EQ(kTranscoldFan, ac.getMode());
|
||||
}
|
||||
|
||||
TEST(TestTranscoldAcClass, SetAndGetFan) {
|
||||
IRTranscoldAc ac(kGpioUnused);
|
||||
|
||||
// This mode allows pretty much everything except Auto0 speed.
|
||||
ac.setMode(kTranscoldCool);
|
||||
ac.setFan(kTranscoldFanMax);
|
||||
EXPECT_EQ(kTranscoldFanMax, ac.getFan());
|
||||
ac.setFan(kTranscoldFanMin);
|
||||
EXPECT_EQ(kTranscoldFanMin, ac.getFan());
|
||||
ac.setFan(kTranscoldFanZoneFollow);
|
||||
EXPECT_EQ(kTranscoldFanZoneFollow, ac.getFan());
|
||||
ac.setFan(kTranscoldFanAuto);
|
||||
EXPECT_EQ(kTranscoldFanAuto, ac.getFan());
|
||||
ac.setFan(kTranscoldFanAuto0);
|
||||
EXPECT_EQ(kTranscoldFanAuto, ac.getFan());
|
||||
ac.setFan(kTranscoldFanMax);
|
||||
EXPECT_EQ(kTranscoldFanMax, ac.getFan());
|
||||
ASSERT_NE(3, kTranscoldFanAuto);
|
||||
// Now try some unexpected value.
|
||||
ac.setFan(3);
|
||||
EXPECT_EQ(kTranscoldFanAuto, ac.getFan());
|
||||
|
||||
// These modes allows pretty much everything except Auto speed.
|
||||
ac.setMode(kTranscoldDry);
|
||||
EXPECT_EQ(kTranscoldFanAuto0, ac.getFan());
|
||||
ac.setFan(kTranscoldFanMax);
|
||||
EXPECT_EQ(kTranscoldFanMax, ac.getFan());
|
||||
ac.setFan(kTranscoldFanAuto);
|
||||
EXPECT_EQ(kTranscoldFanAuto0, ac.getFan());
|
||||
|
||||
ac.setMode(kTranscoldAuto);
|
||||
EXPECT_EQ(kTranscoldFanAuto0, ac.getFan());
|
||||
ac.setFan(kTranscoldFanMax);
|
||||
EXPECT_EQ(kTranscoldFanMax, ac.getFan());
|
||||
ac.setFan(kTranscoldFanAuto0);
|
||||
EXPECT_EQ(kTranscoldFanAuto0, ac.getFan());
|
||||
}
|
||||
|
||||
TEST(TestTranscoldAcClass, SpecialModesAndReset) {
|
||||
IRTranscoldAc ac(kGpioUnused);
|
||||
ASSERT_NE(kTranscoldSwing, ac.getRaw());
|
||||
ac.setSwing();
|
||||
ASSERT_EQ(kTranscoldSwing, ac.getRaw());
|
||||
ac.stateReset();
|
||||
ASSERT_NE(kTranscoldSwing, ac.getRaw());
|
||||
}
|
||||
|
||||
TEST(TestTranscoldAcClass, HumanReadable) {
|
||||
IRTranscoldAc ac(kGpioUnused);
|
||||
ac.begin();
|
||||
|
||||
// Initial starting point.
|
||||
EXPECT_EQ(
|
||||
"Power: Off", ac.toString());
|
||||
ac.setPower(true);
|
||||
EXPECT_EQ(
|
||||
"Power: On, Mode: 6 (Cool), Fan: 9 (Min), Temp: 22C",
|
||||
ac.toString());
|
||||
ac.setMode(kTranscoldHeat);
|
||||
ac.setFan(kTranscoldFanMin);
|
||||
ac.setTemp(22);
|
||||
EXPECT_EQ(
|
||||
"Power: On, Mode: 10 (Heat), Fan: 9 (Min), Temp: 22C",
|
||||
ac.toString());
|
||||
ac.setSwing();
|
||||
EXPECT_EQ("Power: On, Swing: Toggle", ac.toString());
|
||||
ac.setPower(false);
|
||||
EXPECT_EQ("Power: Off", ac.toString());
|
||||
}
|
||||
|
||||
TEST(TestTranscoldAcClass, BuildKnownState) {
|
||||
IRTranscoldAc ac(kGpioUnused);
|
||||
// "temp down, 19, Auto, cool, close (right)"
|
||||
// Data from:
|
||||
// https://docs.google.com/spreadsheets/d/1qdoyB0FyJm85HPP9oXcfui0n4ztXBFlik6kiNlkO2IM/edit#gid=694351627&range=A25:F25
|
||||
const uint32_t state = 0xEF6B54;
|
||||
ac.stateReset();
|
||||
ac.on();
|
||||
ac.setMode(kTranscoldCool);
|
||||
ac.setFan(kTranscoldFanAuto);
|
||||
ac.setTemp(19);
|
||||
EXPECT_EQ("Power: On, Mode: 6 (Cool), Fan: 15 (Auto), Temp: 19C",
|
||||
ac.toString());
|
||||
ASSERT_EQ(state, ac.getRaw());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user