mirror of
https://github.com/crankyoldgit/IRremoteESP8266.git
synced 2026-01-12 00:05:10 +08:00
Add support for Rhoss Idrowall MPCV 20-30-35-40 A/C protocol (#1630)
- Add send and decode routines - Add support for: - Power - Temp (Celsius) - Swing - Mode - Fan Speed - Checksums - Add Unit tests Co-authored-by: Tom Rosenback <tom@carputec.com>
This commit is contained in:
56
src/IRac.cpp
56
src/IRac.cpp
@@ -36,6 +36,7 @@
|
||||
#include "ir_MitsubishiHeavy.h"
|
||||
#include "ir_Neoclima.h"
|
||||
#include "ir_Panasonic.h"
|
||||
#include "ir_Rhoss.h"
|
||||
#include "ir_Samsung.h"
|
||||
#include "ir_Sanyo.h"
|
||||
#include "ir_Sharp.h"
|
||||
@@ -270,6 +271,9 @@ bool IRac::isProtocolSupported(const decode_type_t protocol) {
|
||||
#if SEND_PANASONIC_AC32
|
||||
case decode_type_t::PANASONIC_AC32:
|
||||
#endif
|
||||
#if SEND_RHOSS
|
||||
case decode_type_t::RHOSS:
|
||||
#endif
|
||||
#if SEND_SAMSUNG_AC
|
||||
case decode_type_t::SAMSUNG_AC:
|
||||
#endif
|
||||
@@ -2362,6 +2366,35 @@ void IRac::transcold(IRTranscoldAc *ac,
|
||||
}
|
||||
#endif // SEND_TRANSCOLD
|
||||
|
||||
#if SEND_RHOSS
|
||||
/// Send an Rhoss A/C message with the supplied settings.
|
||||
/// @param[in, out] ac A Ptr to an IRRhossAc 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] swing The swing setting.
|
||||
void IRac::rhoss(IRRhossAc *ac,
|
||||
const bool on, const stdAc::opmode_t mode, const float degrees,
|
||||
const stdAc::fanspeed_t fan, const stdAc::swingv_t swing) {
|
||||
ac->begin();
|
||||
ac->setPower(on);
|
||||
ac->setMode(ac->convertMode(mode));
|
||||
ac->setSwing(swing != stdAc::swingv_t::kOff);
|
||||
ac->setTemp(degrees);
|
||||
ac->setFan(ac->convertFan(fan));
|
||||
// No Quiet setting available.
|
||||
// No Light setting available.
|
||||
// No Filter setting available.
|
||||
// No Turbo setting available.
|
||||
// No Economy setting available.
|
||||
// No Clean setting available.
|
||||
// No Beep setting available.
|
||||
// No Sleep setting available.
|
||||
ac->send();
|
||||
}
|
||||
#endif // SEND_RHOSS
|
||||
|
||||
/// 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.
|
||||
@@ -2887,6 +2920,14 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) {
|
||||
break;
|
||||
}
|
||||
#endif // SEND_PANASONIC_AC32
|
||||
#if SEND_RHOSS
|
||||
case RHOSS:
|
||||
{
|
||||
IRRhossAc ac(_pin, _inverted, _modulation);
|
||||
rhoss(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv);
|
||||
break;
|
||||
}
|
||||
#endif // SEND_RHOSS
|
||||
#if SEND_SAMSUNG_AC
|
||||
case SAMSUNG_AC:
|
||||
{
|
||||
@@ -3789,6 +3830,13 @@ namespace IRAcUtils {
|
||||
return ac.toString();
|
||||
}
|
||||
#endif // DECODE_TRANSCOLD
|
||||
#if DECODE_RHOSS
|
||||
case decode_type_t::RHOSS: {
|
||||
IRRhossAc ac(kGpioUnused);
|
||||
ac.setRaw(result->state);
|
||||
return ac.toString();
|
||||
}
|
||||
#endif // DECODE_RHOSS
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@@ -4254,6 +4302,14 @@ namespace IRAcUtils {
|
||||
break;
|
||||
}
|
||||
#endif // DECODE_TRANSCOLD
|
||||
#if DECODE_RHOSS
|
||||
case decode_type_t::RHOSS: {
|
||||
IRRhossAc ac(kGpioUnused);
|
||||
ac.setRaw(decode->state);
|
||||
*result = ac.toCommon();
|
||||
break;
|
||||
}
|
||||
#endif // DECODE_RHOSS
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include "ir_Vestel.h"
|
||||
#include "ir_Voltas.h"
|
||||
#include "ir_Whirlpool.h"
|
||||
#include "ir_Rhoss.h"
|
||||
|
||||
// Constants
|
||||
const int8_t kGpioUnused = -1; ///< A placeholder for not using an actual GPIO.
|
||||
@@ -489,6 +490,11 @@ void electra(IRElectraAc *ac,
|
||||
const stdAc::fanspeed_t fan,
|
||||
const stdAc::swingv_t swingv, const stdAc::swingh_t swingh);
|
||||
#endif // SEND_TRANSCOLD
|
||||
#if SEND_RHOSS
|
||||
void rhoss(IRRhossAc *ac,
|
||||
const bool on, const stdAc::opmode_t mode, const float degrees,
|
||||
const stdAc::fanspeed_t fan, const stdAc::swingv_t swing);
|
||||
#endif // SEND_RHOSS
|
||||
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);
|
||||
|
||||
@@ -1040,6 +1040,10 @@ bool IRrecv::decode(decode_results *results, irparams_t *save,
|
||||
DPRINTLN("Attempting Arris decode");
|
||||
if (decodeArris(results, offset)) return true;
|
||||
#endif // DECODE_ARRIS
|
||||
#if DECODE_RHOSS
|
||||
DPRINTLN("Attempting Rhoss decode");
|
||||
if (decodeRhoss(results, offset)) return true;
|
||||
#endif // DECODE_RHOSS
|
||||
// Typically new protocols are added above this line.
|
||||
}
|
||||
#if DECODE_HASH
|
||||
|
||||
@@ -770,6 +770,10 @@ class IRrecv {
|
||||
bool decodeBose(decode_results *results, uint16_t offset = kStartOffset,
|
||||
const uint16_t nbits = kBoseBits, const bool strict = true);
|
||||
#endif // DECODE_BOSE
|
||||
#if DECODE_RHOSS
|
||||
bool decodeRhoss(decode_results *results, uint16_t offset = kStartOffset,
|
||||
const uint16_t nbits = kRhossBits, const bool strict = true);
|
||||
#endif // DECODE_RHOSS
|
||||
};
|
||||
|
||||
#endif // IRRECV_H_
|
||||
|
||||
@@ -797,6 +797,13 @@
|
||||
#define SEND_ARRIS _IR_ENABLE_DEFAULT_
|
||||
#endif // SEND_ARRIS
|
||||
|
||||
#ifndef DECODE_RHOSS
|
||||
#define DECODE_RHOSS _IR_ENABLE_DEFAULT_
|
||||
#endif // DECODE_RHOSS
|
||||
#ifndef SEND_RHOSS
|
||||
#define SEND_RHOSS _IR_ENABLE_DEFAULT_
|
||||
#endif // SEND_RHOSS
|
||||
|
||||
#if (DECODE_ARGO || DECODE_DAIKIN || DECODE_FUJITSU_AC || DECODE_GREE || \
|
||||
DECODE_KELVINATOR || DECODE_MITSUBISHI_AC || DECODE_TOSHIBA_AC || \
|
||||
DECODE_TROTEC || DECODE_HAIER_AC || DECODE_HITACHI_AC || \
|
||||
@@ -811,7 +818,7 @@
|
||||
DECODE_HITACHI_AC344 || DECODE_CORONA_AC || DECODE_SANYO_AC || \
|
||||
DECODE_VOLTAS || DECODE_MIRAGE || DECODE_HAIER_AC176 || \
|
||||
DECODE_TEKNOPOINT || DECODE_KELON || DECODE_TROTEC_3550 || \
|
||||
DECODE_SANYO_AC88 || \
|
||||
DECODE_SANYO_AC88 || DECODE_RHOSS || \
|
||||
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
|
||||
@@ -959,8 +966,9 @@ enum decode_type_t {
|
||||
SANYO_AC88, // 105
|
||||
BOSE,
|
||||
ARRIS,
|
||||
RHOSS,
|
||||
// Add new entries before this one, and update it to point to the last entry.
|
||||
kLastDecodeType = ARRIS,
|
||||
kLastDecodeType = RHOSS,
|
||||
};
|
||||
|
||||
// Message lengths & required repeat values
|
||||
@@ -1204,6 +1212,9 @@ const uint16_t kMilesTag2ShotBits = 14;
|
||||
const uint16_t kMilesTag2MsgBits = 24;
|
||||
const uint16_t kMilesMinRepeat = 0;
|
||||
const uint16_t kBoseBits = 16;
|
||||
const uint16_t kRhossStateLength = 12;
|
||||
const uint16_t kRhossBits = kRhossStateLength * 8;
|
||||
const uint16_t kRhossDefaultRepeat = 0;
|
||||
|
||||
|
||||
// Legacy defines. (Deprecated)
|
||||
|
||||
@@ -739,6 +739,8 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) {
|
||||
return kNeoclimaBits;
|
||||
case PANASONIC_AC:
|
||||
return kPanasonicAcBits;
|
||||
case RHOSS:
|
||||
return kRhossBits;
|
||||
case SAMSUNG_AC:
|
||||
return kSamsungAcBits;
|
||||
case SANYO_AC:
|
||||
@@ -1253,6 +1255,11 @@ bool IRsend::send(const decode_type_t type, const uint8_t *state,
|
||||
sendPanasonicAC(state, nbytes);
|
||||
break;
|
||||
#endif // SEND_PANASONIC_AC
|
||||
#if SEND_RHOSS
|
||||
case RHOSS:
|
||||
sendRhoss(state, nbytes);
|
||||
break;
|
||||
#endif // SEND_RHOSS
|
||||
#if SEND_SAMSUNG_AC
|
||||
case SAMSUNG_AC:
|
||||
sendSamsungAC(state, nbytes);
|
||||
|
||||
@@ -749,6 +749,11 @@ class IRsend {
|
||||
static uint32_t toggleArrisRelease(const uint32_t data);
|
||||
static uint32_t encodeArris(const uint32_t command, const bool release);
|
||||
#endif // SEND_ARRIS
|
||||
#if SEND_RHOSS
|
||||
void sendRhoss(const unsigned char data[],
|
||||
const uint16_t nbytes = kRhossStateLength,
|
||||
const uint16_t repeat = kRhossDefaultRepeat);
|
||||
#endif // SEND_RHOSS
|
||||
|
||||
protected:
|
||||
#ifdef UNIT_TEST
|
||||
|
||||
@@ -322,6 +322,7 @@ IRTEXT_CONST_BLOB_DECL(kAllProtocolNamesStr) {
|
||||
D_STR_SANYO_AC88 "\x0"
|
||||
D_STR_BOSE "\x0"
|
||||
D_STR_ARRIS "\x0"
|
||||
D_STR_RHOSS "\x0"
|
||||
///< New protocol strings should be added just above this line.
|
||||
"\x0" ///< This string requires double null termination.
|
||||
};
|
||||
|
||||
@@ -199,6 +199,7 @@ bool hasACState(const decode_type_t protocol) {
|
||||
case MWM:
|
||||
case NEOCLIMA:
|
||||
case PANASONIC_AC:
|
||||
case RHOSS:
|
||||
case SAMSUNG_AC:
|
||||
case SANYO_AC:
|
||||
case SANYO_AC88:
|
||||
|
||||
364
src/ir_Rhoss.cpp
Normal file
364
src/ir_Rhoss.cpp
Normal file
@@ -0,0 +1,364 @@
|
||||
// Copyright 2021 Tom Rosenback
|
||||
|
||||
/// @file
|
||||
/// @brief Support for Rhoss protocols.
|
||||
|
||||
#include "ir_Rhoss.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include "IRrecv.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRtext.h"
|
||||
#include "IRutils.h"
|
||||
|
||||
const uint16_t kRhossHdrMark = 3042;
|
||||
const uint16_t kRhossHdrSpace = 4248;
|
||||
const uint16_t kRhossBitMark = 648;
|
||||
const uint16_t kRhossOneSpace = 1545;
|
||||
const uint16_t kRhossZeroSpace = 457;
|
||||
const uint32_t kRhossGap = kDefaultMessageGap;
|
||||
const uint16_t kRhossFreq = 38;
|
||||
|
||||
using irutils::addBoolToString;
|
||||
using irutils::addModeToString;
|
||||
using irutils::addFanToString;
|
||||
using irutils::addTempToString;
|
||||
|
||||
#if SEND_RHOSS
|
||||
/// Send a Rhoss HVAC formatted message.
|
||||
/// Status: STABLE / Reported as working.
|
||||
/// @param[in] data The message to be sent.
|
||||
/// @param[in] nbytes The number of bytes of message to be sent.
|
||||
/// @param[in] repeat The number of times the command is to be repeated.
|
||||
void IRsend::sendRhoss(const unsigned char data[], const uint16_t nbytes,
|
||||
const uint16_t repeat) {
|
||||
// Check if we have enough bytes to send a proper message.
|
||||
if (nbytes < kRhossStateLength) return;
|
||||
|
||||
// We always send a message, even for repeat=0, hence '<= repeat'.
|
||||
for (uint16_t r = 0; r <= repeat; r++) {
|
||||
sendGeneric(kRhossHdrMark, kRhossHdrSpace, kRhossBitMark,
|
||||
kRhossOneSpace, kRhossBitMark, kRhossZeroSpace,
|
||||
kRhossBitMark, kRhossZeroSpace,
|
||||
data, nbytes, kRhossFreq, false, 0, kDutyDefault);
|
||||
mark(kRhossBitMark);
|
||||
// Gap
|
||||
space(kRhossGap);
|
||||
}
|
||||
}
|
||||
#endif // SEND_RHOSS
|
||||
|
||||
#if DECODE_RHOSS
|
||||
/// Decode the supplied Rhoss formatted message.
|
||||
/// Status: STABLE / Known working.
|
||||
/// @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.
|
||||
bool IRrecv::decodeRhoss(decode_results *results, uint16_t offset,
|
||||
const uint16_t nbits, const bool strict) {
|
||||
if (strict && nbits != kRhossBits) return false;
|
||||
|
||||
if (results->rawlen <= 2 * nbits + kHeader + kFooter - 1 + offset) {
|
||||
return false; // Can't possibly be a valid Rhoss message.
|
||||
}
|
||||
|
||||
uint16_t used;
|
||||
// Header + Data Block (96 bits) + Footer
|
||||
used = matchGeneric(results->rawbuf + offset, results->state,
|
||||
results->rawlen - offset, kRhossBits,
|
||||
kRhossHdrMark, kRhossHdrSpace,
|
||||
kRhossBitMark, kRhossOneSpace,
|
||||
kRhossBitMark, kRhossZeroSpace,
|
||||
kRhossBitMark, kRhossZeroSpace,
|
||||
false, kUseDefTol, kMarkExcess, false);
|
||||
|
||||
if (!used) return false;
|
||||
offset += used;
|
||||
|
||||
// Footer (Part 2)
|
||||
if (!matchMark(results->rawbuf[offset++], kRhossBitMark)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (offset < results->rawlen &&
|
||||
!matchAtLeast(results->rawbuf[offset], kRhossGap)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strict && !IRRhossAc::validChecksum(results->state)) return false;
|
||||
|
||||
// Success
|
||||
results->decode_type = decode_type_t::RHOSS;
|
||||
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_RHOSS
|
||||
|
||||
/// 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?
|
||||
IRRhossAc::IRRhossAc(const uint16_t pin, const bool inverted,
|
||||
const bool use_modulation)
|
||||
: _irsend(pin, inverted, use_modulation) { this->stateReset(); }
|
||||
|
||||
/// Set up hardware to be able to send a message.
|
||||
void IRRhossAc::begin(void) { _irsend.begin(); }
|
||||
|
||||
#if SEND_RHOSS
|
||||
/// Send the current internal state as an IR message.
|
||||
/// @param[in] repeat Nr. of times the message will be repeated.
|
||||
void IRRhossAc::send(const uint16_t repeat) {
|
||||
_irsend.sendRhoss(getRaw(), kRhossStateLength, repeat);
|
||||
}
|
||||
#endif // SEND_RHOSS
|
||||
|
||||
/// Calculate the checksum for the supplied state.
|
||||
/// @param[in] state The source state to generate the checksum from.
|
||||
/// @param[in] length Length of the supplied state to checksum.
|
||||
/// @return The checksum value.
|
||||
uint8_t IRRhossAc::calcChecksum(const uint8_t state[], const uint16_t length) {
|
||||
return sumBytes(state, length - 1);
|
||||
}
|
||||
|
||||
/// Verify the checksum is valid for a given state.
|
||||
/// @param[in] state The array to verify the checksum of.
|
||||
/// @param[in] length The size of the state.
|
||||
/// @return A boolean indicating if it's checksum is valid.
|
||||
bool IRRhossAc::validChecksum(const uint8_t state[], const uint16_t length) {
|
||||
return (state[length - 1] == IRRhossAc::calcChecksum(state, length));
|
||||
}
|
||||
|
||||
/// Update the checksum value for the internal state.
|
||||
void IRRhossAc::checksum(void) {
|
||||
_.Sum = IRRhossAc::calcChecksum(_.raw, kRhossStateLength);
|
||||
_.raw[kRhossStateLength - 1] = _.Sum;
|
||||
}
|
||||
|
||||
/// Reset the internals of the object to a known good state.
|
||||
void IRRhossAc::stateReset(void) {
|
||||
for (uint8_t i = 1; i < kRhossStateLength; i++) _.raw[i] = 0x0;
|
||||
_.raw[0] = 0xAA;
|
||||
_.raw[2] = 0x60;
|
||||
_.raw[6] = 0x54;
|
||||
_.Power = kRhossDefaultPower;
|
||||
_.Fan = kRhossDefaultFan;
|
||||
_.Mode = kRhossDefaultMode;
|
||||
_.Swing = kRhossDefaultSwing;
|
||||
_.Temp = kRhossDefaultTemp - kRhossTempMin;
|
||||
}
|
||||
|
||||
/// Get the raw state of the object, suitable to be sent with the appropriate
|
||||
/// IRsend object method.
|
||||
/// @return A PTR to the internal state.
|
||||
uint8_t* IRRhossAc::getRaw(void) {
|
||||
checksum(); // Ensure correct bit array before returning
|
||||
return _.raw;
|
||||
}
|
||||
|
||||
/// Set the raw state of the object.
|
||||
/// @param[state] state The raw state from the native IR message.
|
||||
void IRRhossAc::setRaw(const uint8_t state[]) {
|
||||
std::memcpy(_.raw, state, kRhossStateLength);
|
||||
}
|
||||
|
||||
/// Set the internal state to have the power on.
|
||||
void IRRhossAc::on(void) { setPower(true); }
|
||||
|
||||
/// Set the internal state to have the power off.
|
||||
void IRRhossAc::off(void) { setPower(false); }
|
||||
|
||||
/// Set the internal state to have the desired power.
|
||||
/// @param[in] on The desired power state.
|
||||
void IRRhossAc::setPower(const bool on) {
|
||||
_.Power = (on ? kRhossPowerOn : kRhossPowerOff);
|
||||
}
|
||||
|
||||
/// Get the power setting from the internal state.
|
||||
/// @return A boolean indicating the power setting.
|
||||
bool IRRhossAc::getPower(void) const {
|
||||
return _.Power == kRhossPowerOn;
|
||||
}
|
||||
|
||||
/// Set the temperature.
|
||||
/// @param[in] degrees The temperature in degrees celsius.
|
||||
void IRRhossAc::setTemp(const uint8_t degrees) {
|
||||
uint8_t temp = std::max(kRhossTempMin, degrees);
|
||||
_.Temp = std::min(kRhossTempMax, temp) - kRhossTempMin;
|
||||
}
|
||||
|
||||
/// Get the current temperature setting.
|
||||
/// @return Get current setting for temp. in degrees celsius.
|
||||
uint8_t IRRhossAc::getTemp(void) const {
|
||||
return _.Temp + kRhossTempMin;
|
||||
}
|
||||
|
||||
/// Set the speed of the fan.
|
||||
/// @param[in] speed The desired setting.
|
||||
void IRRhossAc::setFan(const uint8_t speed) {
|
||||
switch (speed) {
|
||||
case kRhossFanAuto:
|
||||
case kRhossFanMin:
|
||||
case kRhossFanMed:
|
||||
case kRhossFanMax:
|
||||
_.Fan = speed;
|
||||
break;
|
||||
default:
|
||||
_.Fan = kRhossFanAuto;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current fan speed setting.
|
||||
/// @return The current fan speed.
|
||||
uint8_t IRRhossAc::getFan(void) const {
|
||||
return _.Fan;
|
||||
}
|
||||
|
||||
/// Set the Vertical Swing mode of the A/C.
|
||||
/// @param[in] state true, the Swing is on. false, the Swing is off.
|
||||
void IRRhossAc::setSwing(const bool state) {
|
||||
_.Swing = state;
|
||||
}
|
||||
|
||||
/// Get the Vertical Swing speed of the A/C.
|
||||
/// @return The native swing speed setting.
|
||||
uint8_t IRRhossAc::getSwing(void) const {
|
||||
return _.Swing;
|
||||
}
|
||||
|
||||
/// Get the current operation mode setting.
|
||||
/// @return The current operation mode.
|
||||
uint8_t IRRhossAc::getMode(void) const {
|
||||
return _.Mode;
|
||||
}
|
||||
|
||||
/// Set the desired operation mode.
|
||||
/// @param[in] mode The desired operation mode.
|
||||
void IRRhossAc::setMode(const uint8_t mode) {
|
||||
switch (mode) {
|
||||
case kRhossModeFan:
|
||||
case kRhossModeCool:
|
||||
case kRhossModeDry:
|
||||
case kRhossModeHeat:
|
||||
case kRhossModeAuto:
|
||||
_.Mode = mode;
|
||||
return;
|
||||
default:
|
||||
_.Mode = kRhossDefaultMode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a stdAc::opmode_t enum into its native mode.
|
||||
/// @param[in] mode The enum to be converted.
|
||||
/// @return The native equivalent of the enum.
|
||||
uint8_t IRRhossAc::convertMode(const stdAc::opmode_t mode) {
|
||||
switch (mode) {
|
||||
case stdAc::opmode_t::kCool:
|
||||
return kRhossModeCool;
|
||||
case stdAc::opmode_t::kHeat:
|
||||
return kRhossModeHeat;
|
||||
case stdAc::opmode_t::kDry:
|
||||
return kRhossModeDry;
|
||||
case stdAc::opmode_t::kFan:
|
||||
return kRhossModeFan;
|
||||
case stdAc::opmode_t::kAuto:
|
||||
return kRhossModeAuto;
|
||||
default:
|
||||
return kRhossDefaultMode;
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a stdAc::fanspeed_t enum into it's native speed.
|
||||
/// @param[in] speed The enum to be converted.
|
||||
/// @return The native equivalent of the enum.
|
||||
uint8_t IRRhossAc::convertFan(const stdAc::fanspeed_t speed) {
|
||||
switch (speed) {
|
||||
case stdAc::fanspeed_t::kMin:
|
||||
case stdAc::fanspeed_t::kLow:
|
||||
return kRhossFanMin;
|
||||
case stdAc::fanspeed_t::kMedium:
|
||||
return kRhossFanMed;
|
||||
case stdAc::fanspeed_t::kHigh:
|
||||
case stdAc::fanspeed_t::kMax:
|
||||
return kRhossFanMax;
|
||||
default:
|
||||
return kRhossDefaultFan;
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a native mode into its stdAc equivalent.
|
||||
/// @param[in] mode The native setting to be converted.
|
||||
/// @return The stdAc equivalent of the native setting.
|
||||
stdAc::opmode_t IRRhossAc::toCommonMode(const uint8_t mode) {
|
||||
switch (mode) {
|
||||
case kRhossModeCool: return stdAc::opmode_t::kCool;
|
||||
case kRhossModeHeat: return stdAc::opmode_t::kHeat;
|
||||
case kRhossModeDry: return stdAc::opmode_t::kDry;
|
||||
case kRhossModeFan: return stdAc::opmode_t::kFan;
|
||||
case kRhossModeAuto: return stdAc::opmode_t::kAuto;
|
||||
default: return stdAc::opmode_t::kAuto;
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a native fan speed into its stdAc equivalent.
|
||||
/// @param[in] speed The native setting to be converted.
|
||||
/// @return The stdAc equivalent of the native setting.
|
||||
stdAc::fanspeed_t IRRhossAc::toCommonFanSpeed(const uint8_t speed) {
|
||||
switch (speed) {
|
||||
case kRhossFanMax: return stdAc::fanspeed_t::kMax;
|
||||
case kRhossFanMed: return stdAc::fanspeed_t::kMedium;
|
||||
case kRhossFanMin: return stdAc::fanspeed_t::kMin;
|
||||
case kRhossFanAuto:
|
||||
default:
|
||||
return stdAc::fanspeed_t::kAuto;
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the current internal state into its stdAc::state_t equivalent.
|
||||
/// @return The stdAc equivalent of the native settings.
|
||||
stdAc::state_t IRRhossAc::toCommon(void) const {
|
||||
stdAc::state_t result;
|
||||
result.protocol = decode_type_t::RHOSS;
|
||||
result.power = getPower();
|
||||
result.mode = toCommonMode(_.Mode);
|
||||
result.celsius = true;
|
||||
result.degrees = _.Temp;
|
||||
result.fanspeed = toCommonFanSpeed(_.Fan);
|
||||
result.swingv = _.Swing ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff;
|
||||
// Not supported.
|
||||
result.model = -1;
|
||||
result.turbo = false;
|
||||
result.swingh = stdAc::swingh_t::kOff;
|
||||
result.light = false;
|
||||
result.filter = false;
|
||||
result.econo = false;
|
||||
result.quiet = false;
|
||||
result.clean = false;
|
||||
result.beep = false;
|
||||
result.sleep = -1;
|
||||
result.clock = -1;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Convert the current internal state into a human readable string.
|
||||
/// @return A human readable string.
|
||||
String IRRhossAc::toString(void) const {
|
||||
String result = "";
|
||||
result.reserve(70); // Reserve some heap for the string to reduce fragging.
|
||||
result += addBoolToString(getPower(), kPowerStr, false);
|
||||
result += addModeToString(getMode(), kRhossModeAuto, kRhossModeCool,
|
||||
kRhossModeHeat, kRhossModeDry, kRhossModeFan);
|
||||
result += addTempToString(getTemp());
|
||||
result += addFanToString(getFan(), kRhossFanMax, kRhossFanMin,
|
||||
kRhossFanAuto, kRhossFanAuto,
|
||||
kRhossFanMed);
|
||||
result += addBoolToString(getSwing(), kSwingVStr);
|
||||
return result;
|
||||
}
|
||||
145
src/ir_Rhoss.h
Normal file
145
src/ir_Rhoss.h
Normal file
@@ -0,0 +1,145 @@
|
||||
// Copyright 2021 Tom Rosenback
|
||||
|
||||
/// @file
|
||||
/// @brief Support for Rhoss A/C protocol
|
||||
// Supports:
|
||||
// Brand: Rhoss, Model: Idrowall MPCV 20-30-35-40
|
||||
|
||||
#ifndef IR_RHOSS_H_
|
||||
#define IR_RHOSS_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
|
||||
|
||||
|
||||
/// Native representation of a Rhoss A/C message.
|
||||
union RhossProtocol{
|
||||
uint8_t raw[kRhossStateLength]; // The state of the IR remote.
|
||||
struct {
|
||||
// Byte 0
|
||||
uint8_t :8; // Typically 0xAA
|
||||
// Byte 1
|
||||
uint8_t Temp :4;
|
||||
uint8_t :4; // Typically 0x0
|
||||
// Byte 2
|
||||
uint8_t :8; // Typically 0x60
|
||||
// Byte 3
|
||||
uint8_t :8; // Typically 0x0
|
||||
// Byte 4
|
||||
uint8_t Fan :2;
|
||||
uint8_t :2; // Typically 0x0
|
||||
uint8_t Mode :4;
|
||||
// Byte 5
|
||||
uint8_t Swing :1;
|
||||
uint8_t :5; // Typically 0x0
|
||||
uint8_t Power :2;
|
||||
// Byte 6
|
||||
uint8_t :8; // Typically 0x54
|
||||
// Byte 7
|
||||
uint8_t :8; // Typically 0x0
|
||||
// Byte 8
|
||||
uint8_t :8; // Typically 0x0
|
||||
// Byte 9
|
||||
uint8_t :8; // Typically 0x0
|
||||
// Byte 10
|
||||
uint8_t :8; // Typically 0x0
|
||||
// Byte 11
|
||||
uint8_t Sum :8;
|
||||
};
|
||||
};
|
||||
|
||||
// Constants
|
||||
|
||||
// Fan Control
|
||||
const uint8_t kRhossFanAuto = 0b00;
|
||||
const uint8_t kRhossFanMin = 0b01;
|
||||
const uint8_t kRhossFanMed = 0b10;
|
||||
const uint8_t kRhossFanMax = 0b11;
|
||||
// Modes
|
||||
const uint8_t kRhossModeHeat = 0b0001;
|
||||
const uint8_t kRhossModeCool = 0b0010;
|
||||
const uint8_t kRhossModeDry = 0b0011;
|
||||
const uint8_t kRhossModeFan = 0b0100;
|
||||
const uint8_t kRhossModeAuto = 0b0101;
|
||||
|
||||
// Temperature
|
||||
const uint8_t kRhossTempMin = 16; // Celsius
|
||||
const uint8_t kRhossTempMax = 30; // Celsius
|
||||
|
||||
// Power
|
||||
const uint8_t kRhossPowerOn = 0b10; // 0x2
|
||||
const uint8_t kRhossPowerOff = 0b01; // 0x1
|
||||
|
||||
// Swing
|
||||
const uint8_t kRhossSwingOn = 0b1; // 0x1
|
||||
const uint8_t kRhossSwingOff = 0b0; // 0x0
|
||||
|
||||
const uint8_t kRhossDefaultFan = kRhossFanAuto;
|
||||
const uint8_t kRhossDefaultMode = kRhossModeCool;
|
||||
const uint8_t kRhossDefaultTemp = 21; // Celsius
|
||||
const bool kRhossDefaultPower = false;
|
||||
const bool kRhossDefaultSwing = false;
|
||||
|
||||
// Classes
|
||||
|
||||
/// Class for handling detailed Rhoss A/C messages.
|
||||
class IRRhossAc {
|
||||
public:
|
||||
explicit IRRhossAc(const uint16_t pin, const bool inverted = false,
|
||||
const bool use_modulation = true);
|
||||
|
||||
void stateReset();
|
||||
#if SEND_RHOSS
|
||||
void send(const uint16_t repeat = kRhossDefaultRepeat);
|
||||
/// 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_RHOSS
|
||||
void begin();
|
||||
static uint8_t calcChecksum(const uint8_t state[],
|
||||
const uint16_t length = kRhossStateLength);
|
||||
static bool validChecksum(const uint8_t state[],
|
||||
const uint16_t length = kRhossStateLength);
|
||||
void setPower(const bool state);
|
||||
bool getPower(void) const;
|
||||
void on(void);
|
||||
void off(void);
|
||||
void setTemp(const uint8_t temp);
|
||||
uint8_t getTemp(void) const;
|
||||
void setFan(const uint8_t speed);
|
||||
uint8_t getFan(void) const;
|
||||
void setSwing(const bool state);
|
||||
uint8_t getSwing(void) const;
|
||||
void setMode(const uint8_t mode);
|
||||
uint8_t getMode(void) const;
|
||||
uint8_t* getRaw(void);
|
||||
void setRaw(const uint8_t state[]);
|
||||
static uint8_t convertMode(const stdAc::opmode_t mode);
|
||||
static 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(void) const;
|
||||
String toString(void) const;
|
||||
#ifndef UNIT_TEST
|
||||
|
||||
private:
|
||||
IRsend _irsend;
|
||||
#else
|
||||
/// @cond IGNORE
|
||||
IRsendTest _irsend;
|
||||
/// @endcond
|
||||
#endif
|
||||
RhossProtocol _;
|
||||
void checksum(void);
|
||||
};
|
||||
#endif // IR_RHOSS_H_
|
||||
@@ -748,6 +748,9 @@
|
||||
#ifndef D_STR_RCMM
|
||||
#define D_STR_RCMM "RCMM"
|
||||
#endif // D_STR_RCMM
|
||||
#ifndef D_STR_RHOSS
|
||||
#define D_STR_RHOSS "RHOSS"
|
||||
#endif // D_STR_RHOSS
|
||||
#ifndef D_STR_SAMSUNG
|
||||
#define D_STR_SAMSUNG "SAMSUNG"
|
||||
#endif // D_STR_SAMSUNG
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# make TARGET - makes the given target.
|
||||
# make run - makes everything and runs all the tests.
|
||||
# make run_tests - run all tests
|
||||
# make run_% - run specific test file (exclude _test.cpp)
|
||||
# make run-% - run specific test file (exclude _test.cpp)
|
||||
# replace % with given test file
|
||||
# make clean - removes all files generated by make.
|
||||
# make install-googletest - install the googletest code suite
|
||||
@@ -59,7 +59,7 @@ run : all
|
||||
|
||||
run_tests : run
|
||||
|
||||
run_% :
|
||||
run-% : all
|
||||
echo "RUNNING: $*"; \
|
||||
./$*
|
||||
|
||||
|
||||
396
test/ir_Rhoss_test.cpp
Normal file
396
test/ir_Rhoss_test.cpp
Normal file
@@ -0,0 +1,396 @@
|
||||
// Copyright 2021 Tom Rosenback
|
||||
|
||||
#include "IRac.h"
|
||||
#include "ir_Rhoss.h"
|
||||
#include "IRrecv.h"
|
||||
#include "IRrecv_test.h"
|
||||
#include "IRsend.h"
|
||||
#include "IRsend_test.h"
|
||||
#include "IRutils.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
TEST(TestUtils, Housekeeping) {
|
||||
ASSERT_EQ("RHOSS", typeToString(decode_type_t::RHOSS));
|
||||
ASSERT_EQ(decode_type_t::RHOSS, strToDecodeType("RHOSS"));
|
||||
ASSERT_TRUE(hasACState(decode_type_t::RHOSS));
|
||||
ASSERT_TRUE(IRac::isProtocolSupported(decode_type_t::RHOSS));
|
||||
}
|
||||
|
||||
// Test sending typical data only.
|
||||
TEST(TestSendRhoss, SendDataOnly) {
|
||||
IRsendTest irsend(kGpioUnused);
|
||||
irsend.begin();
|
||||
|
||||
uint8_t expectedState[kRhossStateLength] = {
|
||||
0xAA, 0x05, 0x60, 0x00, 0x50, 0x80, 0x54, 0x00, 0x00, 0x00, 0x00, 0x33 };
|
||||
|
||||
irsend.reset();
|
||||
irsend.sendRhoss(expectedState);
|
||||
|
||||
EXPECT_EQ(
|
||||
"f38000d50"
|
||||
"m3042s4248"
|
||||
"m648s457m648s1545m648s457m648s1545m648s457m648s1545m648s457m648s1545"
|
||||
"m648s1545m648s457m648s1545m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s1545m648s1545m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s1545m648s457m648s1545m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s1545"
|
||||
"m648s457m648s457m648s1545m648s457m648s1545m648s457m648s1545m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s1545m648s1545m648s457m648s457m648s1545m648s1545m648s457m648s457"
|
||||
"m648s457m648"
|
||||
"s100000",
|
||||
irsend.outputStr());
|
||||
}
|
||||
|
||||
// Test send typical data with repeats
|
||||
TEST(TestSendRhoss, SendWithRepeats) {
|
||||
IRsendTest irsend(kGpioUnused);
|
||||
irsend.begin();
|
||||
|
||||
irsend.reset();
|
||||
|
||||
uint8_t expectedState[kRhossStateLength] = {
|
||||
0xAA, 0x05, 0x60, 0x00, 0x50, 0x80, 0x54, 0x00, 0x00, 0x00, 0x00, 0x33 };
|
||||
|
||||
irsend.sendRhoss(expectedState, kRhossStateLength, 0); // 0 repeats.
|
||||
EXPECT_EQ(
|
||||
"f38000d50"
|
||||
"m3042s4248"
|
||||
"m648s457m648s1545m648s457m648s1545m648s457m648s1545m648s457m648s1545"
|
||||
"m648s1545m648s457m648s1545m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s1545m648s1545m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s1545m648s457m648s1545m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s1545"
|
||||
"m648s457m648s457m648s1545m648s457m648s1545m648s457m648s1545m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s1545m648s1545m648s457m648s457m648s1545m648s1545m648s457m648s457"
|
||||
"m648s457m648"
|
||||
"s100000",
|
||||
irsend.outputStr());
|
||||
|
||||
irsend.sendRhoss(expectedState, kRhossStateLength, 2); // 2 repeats.
|
||||
EXPECT_EQ(
|
||||
"f38000d50"
|
||||
"m3042s4248"
|
||||
"m648s457m648s1545m648s457m648s1545m648s457m648s1545m648s457m648s1545"
|
||||
"m648s1545m648s457m648s1545m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s1545m648s1545m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s1545m648s457m648s1545m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s1545"
|
||||
"m648s457m648s457m648s1545m648s457m648s1545m648s457m648s1545m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s1545m648s1545m648s457m648s457m648s1545m648s1545m648s457m648s457"
|
||||
"m648s457m648"
|
||||
"s100000"
|
||||
"m3042s4248"
|
||||
"m648s457m648s1545m648s457m648s1545m648s457m648s1545m648s457m648s1545"
|
||||
"m648s1545m648s457m648s1545m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s1545m648s1545m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s1545m648s457m648s1545m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s1545"
|
||||
"m648s457m648s457m648s1545m648s457m648s1545m648s457m648s1545m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s1545m648s1545m648s457m648s457m648s1545m648s1545m648s457m648s457"
|
||||
"m648s457m648"
|
||||
"s100000"
|
||||
"m3042s4248"
|
||||
"m648s457m648s1545m648s457m648s1545m648s457m648s1545m648s457m648s1545"
|
||||
"m648s1545m648s457m648s1545m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s1545m648s1545m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s1545m648s457m648s1545m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s1545"
|
||||
"m648s457m648s457m648s1545m648s457m648s1545m648s457m648s1545m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s457m648s457m648s457m648s457m648s457m648s457m648s457m648s457"
|
||||
"m648s1545m648s1545m648s457m648s457m648s1545m648s1545m648s457m648s457"
|
||||
"m648s457m648"
|
||||
"s100000",
|
||||
irsend.outputStr());
|
||||
}
|
||||
|
||||
// Test send raw data
|
||||
TEST(TestSendRhoss, RawData) {
|
||||
IRsendTest irsend(kGpioUnused);
|
||||
IRrecv irrecv(kGpioUnused);
|
||||
irsend.begin();
|
||||
irsend.reset();
|
||||
|
||||
// Power on, mode cool, temp 20, fan auto, swing off
|
||||
const uint16_t rawData[197] = {
|
||||
3044, 4248,
|
||||
648, 458, 650, 1540, 646, 458, 650, 1538,
|
||||
650, 458, 650, 1538, 650, 458, 650, 1540, // byte 0
|
||||
648, 458, 650, 458, 650, 1540, 646, 484,
|
||||
624, 456, 650, 456, 650, 456, 650, 456, // byte 1
|
||||
650, 456, 650, 456, 650, 456, 650, 456,
|
||||
650, 458, 650, 1540, 650, 1538, 650, 456, // byte 2
|
||||
650, 456, 650, 456, 650, 456, 650, 458,
|
||||
650, 456, 650, 456, 650, 456, 650, 458, // byte 3
|
||||
650, 458, 650, 456, 650, 458, 650, 458,
|
||||
650, 458, 650, 1538, 650, 458, 650, 458, // byte 4
|
||||
650, 458, 648, 458, 674, 434, 648, 458,
|
||||
672, 434, 648, 458, 650, 458, 648, 1540, // byte 5
|
||||
672, 434, 650, 458, 672, 1518, 644, 488,
|
||||
622, 1540, 644, 464, 672, 1516, 672, 434, // byte 6
|
||||
672, 434, 672, 434, 650, 458, 648, 458,
|
||||
672, 434, 674, 434, 672, 434, 650, 458, // byte 7
|
||||
672, 434, 648, 458, 650, 458, 672, 434,
|
||||
672, 436, 648, 458, 648, 456, 650, 458, // byte 8
|
||||
650, 458, 650, 456, 674, 434, 650, 458,
|
||||
650, 456, 650, 458, 674, 432, 650, 458, // byte 9
|
||||
650, 456, 650, 456, 650, 458, 648, 458,
|
||||
674, 432, 650, 456, 674, 434, 650, 458, // byte 10
|
||||
650, 458, 650, 1538, 650, 458, 650, 458,
|
||||
650, 456, 650, 458, 650, 456, 650, 458, // byte 11
|
||||
650, 456,
|
||||
650 }; // UNKNOWN 93E7BDB2
|
||||
|
||||
irsend.sendRaw(rawData, 197, 38);
|
||||
irsend.makeDecodeResult();
|
||||
|
||||
ASSERT_TRUE(irrecv.decode(&irsend.capture));
|
||||
ASSERT_EQ(RHOSS, irsend.capture.decode_type);
|
||||
EXPECT_EQ(kRhossBits, irsend.capture.bits);
|
||||
|
||||
uint8_t expected[kRhossStateLength] = {
|
||||
0xAA, 0x04, 0x60, 0x00, 0x20, 0x80, 0x54, 0x00, 0x00, 0x00, 0x00, 0x02 };
|
||||
EXPECT_STATE_EQ(expected, irsend.capture.state, kRhossBits);
|
||||
|
||||
EXPECT_EQ(
|
||||
"Power: On, Mode: 2 (Cool), Temp: 20C, Fan: 0 (Auto), Swing(V): Off",
|
||||
IRAcUtils::resultAcToString(&irsend.capture));
|
||||
|
||||
stdAc::state_t r, p;
|
||||
ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &r, &p));
|
||||
}
|
||||
|
||||
// Test synthetic decode
|
||||
TEST(TestDecodeRhoss, SyntheticSelfDecode) {
|
||||
IRsendTest irsend(kGpioUnused);
|
||||
IRrecv irrecv(0);
|
||||
IRRhossAc ac(0);
|
||||
|
||||
uint8_t expectedState[kRhossStateLength] = {
|
||||
0xAA, 0x05, 0x60, 0x00, 0x50, 0x80, 0x54, 0x00, 0x00, 0x00, 0x00, 0x33 };
|
||||
|
||||
irsend.begin();
|
||||
irsend.reset();
|
||||
irsend.sendRhoss(expectedState);
|
||||
irsend.makeDecodeResult();
|
||||
ASSERT_TRUE(irrecv.decode(&irsend.capture));
|
||||
EXPECT_EQ(RHOSS, irsend.capture.decode_type);
|
||||
EXPECT_EQ(kRhossBits, irsend.capture.bits);
|
||||
EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits);
|
||||
ac.setRaw(irsend.capture.state);
|
||||
EXPECT_EQ(
|
||||
"Power: On, Mode: 5 (Auto), Temp: 21C, Fan: 0 (Auto), Swing(V): Off",
|
||||
ac.toString());
|
||||
}
|
||||
|
||||
// Test strict decoding
|
||||
TEST(TestDecodeRhoss, StrictDecode) {
|
||||
IRsendTest irsend(kGpioUnused);
|
||||
IRrecv irrecv(0);
|
||||
IRRhossAc ac(0);
|
||||
|
||||
uint8_t expectedState[kRhossStateLength] = {
|
||||
0xAA, 0x05, 0x60, 0x00, 0x50, 0x80, 0x54, 0x00, 0x00, 0x00, 0x00, 0x33 };
|
||||
|
||||
irsend.begin();
|
||||
irsend.reset();
|
||||
irsend.sendRhoss(expectedState);
|
||||
irsend.makeDecodeResult();
|
||||
ASSERT_TRUE(
|
||||
irrecv.decodeRhoss(&irsend.capture,
|
||||
kStartOffset, kRhossBits, true));
|
||||
EXPECT_EQ(RHOSS, irsend.capture.decode_type);
|
||||
EXPECT_EQ(kRhossBits, irsend.capture.bits);
|
||||
EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits);
|
||||
ac.setRaw(irsend.capture.state);
|
||||
EXPECT_EQ(
|
||||
"Power: On, Mode: 5 (Auto), Temp: 21C, Fan: 0 (Auto), Swing(V): Off",
|
||||
ac.toString());
|
||||
}
|
||||
|
||||
// Tests for IRRhossAc class.
|
||||
|
||||
TEST(TestRhossAcClass, Power) {
|
||||
IRRhossAc ac(0);
|
||||
ac.begin();
|
||||
|
||||
ac.on();
|
||||
EXPECT_TRUE(ac.getPower());
|
||||
|
||||
ac.off();
|
||||
EXPECT_FALSE(ac.getPower());
|
||||
|
||||
ac.setPower(true);
|
||||
EXPECT_TRUE(ac.getPower());
|
||||
|
||||
ac.setPower(false);
|
||||
EXPECT_FALSE(ac.getPower());
|
||||
}
|
||||
|
||||
TEST(TestRhossAcClass, Temperature) {
|
||||
IRRhossAc ac(0);
|
||||
ac.begin();
|
||||
|
||||
ac.setTemp(0);
|
||||
EXPECT_EQ(kRhossTempMin, ac.getTemp());
|
||||
|
||||
ac.setTemp(255);
|
||||
EXPECT_EQ(kRhossTempMax, ac.getTemp());
|
||||
|
||||
ac.setTemp(kRhossTempMin);
|
||||
EXPECT_EQ(kRhossTempMin, ac.getTemp());
|
||||
|
||||
ac.setTemp(kRhossTempMax);
|
||||
EXPECT_EQ(kRhossTempMax, ac.getTemp());
|
||||
|
||||
ac.setTemp(kRhossTempMin - 1);
|
||||
EXPECT_EQ(kRhossTempMin, ac.getTemp());
|
||||
|
||||
ac.setTemp(kRhossTempMax + 1);
|
||||
EXPECT_EQ(kRhossTempMax, ac.getTemp());
|
||||
|
||||
ac.setTemp(17);
|
||||
EXPECT_EQ(17, ac.getTemp());
|
||||
|
||||
ac.setTemp(21);
|
||||
EXPECT_EQ(21, ac.getTemp());
|
||||
|
||||
ac.setTemp(25);
|
||||
EXPECT_EQ(25, ac.getTemp());
|
||||
|
||||
ac.setTemp(29);
|
||||
EXPECT_EQ(29, ac.getTemp());
|
||||
}
|
||||
|
||||
|
||||
TEST(TestRhossAcClass, OperatingMode) {
|
||||
IRRhossAc ac(0);
|
||||
ac.begin();
|
||||
|
||||
ac.setMode(kRhossModeAuto);
|
||||
EXPECT_EQ(kRhossModeAuto, ac.getMode());
|
||||
|
||||
ac.setMode(kRhossModeCool);
|
||||
EXPECT_EQ(kRhossModeCool, ac.getMode());
|
||||
|
||||
ac.setMode(kRhossModeHeat);
|
||||
EXPECT_EQ(kRhossModeHeat, ac.getMode());
|
||||
|
||||
ac.setMode(kRhossModeDry);
|
||||
EXPECT_EQ(kRhossModeDry, ac.getMode());
|
||||
|
||||
ac.setMode(kRhossModeFan);
|
||||
EXPECT_EQ(kRhossModeFan, ac.getMode());
|
||||
|
||||
ac.setMode(kRhossModeAuto + 1);
|
||||
EXPECT_EQ(kRhossDefaultMode, ac.getMode());
|
||||
|
||||
ac.setMode(255);
|
||||
EXPECT_EQ(kRhossDefaultMode, ac.getMode());
|
||||
}
|
||||
|
||||
TEST(TestRhossAcClass, FanSpeed) {
|
||||
IRRhossAc ac(0);
|
||||
ac.begin();
|
||||
|
||||
ac.setFan(0);
|
||||
EXPECT_EQ(kRhossFanAuto, ac.getFan());
|
||||
|
||||
ac.setFan(255);
|
||||
EXPECT_EQ(kRhossFanAuto, ac.getFan());
|
||||
|
||||
ac.setFan(kRhossFanMax);
|
||||
EXPECT_EQ(kRhossFanMax, ac.getFan());
|
||||
|
||||
ac.setFan(kRhossFanMax + 1);
|
||||
EXPECT_EQ(kRhossFanAuto, ac.getFan());
|
||||
|
||||
ac.setFan(kRhossFanMax - 1);
|
||||
EXPECT_EQ(kRhossFanMax - 1, ac.getFan());
|
||||
|
||||
ac.setFan(1);
|
||||
EXPECT_EQ(1, ac.getFan());
|
||||
|
||||
ac.setFan(1);
|
||||
EXPECT_EQ(1, ac.getFan());
|
||||
|
||||
ac.setFan(3);
|
||||
EXPECT_EQ(3, ac.getFan());
|
||||
}
|
||||
|
||||
TEST(TestRhossAcClass, Swing) {
|
||||
IRRhossAc ac(0);
|
||||
ac.begin();
|
||||
|
||||
ac.setSwing(false);
|
||||
EXPECT_FALSE(ac.getSwing());
|
||||
|
||||
ac.setSwing(true);
|
||||
EXPECT_TRUE(ac.getSwing());
|
||||
}
|
||||
|
||||
TEST(TestRhossAcClass, Checksums) {
|
||||
uint8_t state[kRhossStateLength] = {
|
||||
0xAA, 0x05, 0x60, 0x00, 0x50, 0x80, 0x54, 0x00, 0x00, 0x00, 0x00, 0x33 };
|
||||
|
||||
ASSERT_EQ(0x33, IRRhossAc::calcChecksum(state));
|
||||
EXPECT_TRUE(IRRhossAc::validChecksum(state));
|
||||
// Change the array so the checksum is invalid.
|
||||
state[0] ^= 0xFF;
|
||||
EXPECT_FALSE(IRRhossAc::validChecksum(state));
|
||||
// Restore the previous change, and change another byte.
|
||||
state[0] ^= 0xFF;
|
||||
state[4] ^= 0xFF;
|
||||
EXPECT_FALSE(IRRhossAc::validChecksum(state));
|
||||
state[4] ^= 0xFF;
|
||||
EXPECT_TRUE(IRRhossAc::validChecksum(state));
|
||||
|
||||
// Additional known good states.
|
||||
uint8_t knownGood1[kRhossStateLength] = {
|
||||
0xAA, 0x06, 0x60, 0x00, 0x50, 0x80, 0x54, 0x00, 0x00, 0x00, 0x00, 0x34 };
|
||||
EXPECT_TRUE(IRRhossAc::validChecksum(knownGood1));
|
||||
ASSERT_EQ(0x34, IRRhossAc::calcChecksum(knownGood1));
|
||||
|
||||
uint8_t knownGood2[kRhossStateLength] = {
|
||||
0xAA, 0x07, 0x60, 0x00, 0x50, 0x80, 0x54, 0x00, 0x00, 0x00, 0x00, 0x35 };
|
||||
EXPECT_TRUE(IRRhossAc::validChecksum(knownGood2));
|
||||
ASSERT_EQ(0x35, IRRhossAc::calcChecksum(knownGood2));
|
||||
|
||||
uint8_t knownGood3[kRhossStateLength] = {
|
||||
0xAA, 0x07, 0x60, 0x00, 0x53, 0x80, 0x54, 0x00, 0x00, 0x00, 0x00, 0x38 };
|
||||
EXPECT_TRUE(IRRhossAc::validChecksum(knownGood3));
|
||||
ASSERT_EQ(0x38, IRRhossAc::calcChecksum(knownGood3));
|
||||
|
||||
// Validate calculation of checksum,
|
||||
// same as knownGood3 except for the checksum.
|
||||
uint8_t knownBad[kRhossStateLength] = {
|
||||
0xAA, 0x07, 0x60, 0x00, 0x53, 0x80, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
EXPECT_FALSE(IRRhossAc::validChecksum(knownBad));
|
||||
IRRhossAc ac(0);
|
||||
ac.setRaw(knownBad);
|
||||
EXPECT_STATE_EQ(knownGood3, ac.getRaw(), kRhossBits);
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
# make [all] - makes everything.
|
||||
# make TARGET - makes the given target.
|
||||
# make run_tests - makes everything and runs all test
|
||||
# make run_% - run specific test file (exclude .py)
|
||||
# make run-% - run specific test file (exclude .py)
|
||||
# replace % with given test file
|
||||
# make clean - removes all files generated by make.
|
||||
|
||||
@@ -41,7 +41,7 @@ run_tests : all
|
||||
echo "PASS: \o/ \o/ All unit tests passed. \o/ \o/"; \
|
||||
fi
|
||||
|
||||
run_% : all
|
||||
run-% : all
|
||||
echo "RUNNING: $*"; \
|
||||
python3 ./$*.py;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user