From a3cffc2433ab46b5cbc8c195467b576f1d14b597 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 4 May 2019 00:39:22 +1000 Subject: [PATCH 1/4] Progress checkin of Goodweather A/C support. --- examples/IRMQTTServer/IRMQTTServer.ino | 8 + examples/IRrecvDumpV2/IRrecvDumpV2.ino | 8 + src/IRrecv.cpp | 4 + src/IRrecv.h | 5 + src/IRremoteESP8266.h | 8 +- src/IRsend.h | 5 + src/IRutils.cpp | 5 + src/ir_Goodweather.cpp | 396 +++++++++++++++++++++++++ src/ir_Goodweather.h | 132 +++++++++ test/Makefile | 14 +- test/ir_Goodweather_test.cpp | 189 ++++++++++++ tools/Makefile | 5 +- 12 files changed, 775 insertions(+), 4 deletions(-) create mode 100644 src/ir_Goodweather.cpp create mode 100644 src/ir_Goodweather.h create mode 100644 test/ir_Goodweather_test.cpp diff --git a/examples/IRMQTTServer/IRMQTTServer.ino b/examples/IRMQTTServer/IRMQTTServer.ino index a27c0cec9..b6776e1f6 100644 --- a/examples/IRMQTTServer/IRMQTTServer.ino +++ b/examples/IRMQTTServer/IRMQTTServer.ino @@ -591,6 +591,7 @@ void handleRoot(void) { "" "" "" + "" "" "" "" @@ -2811,6 +2812,13 @@ bool sendIRCode(IRsend *irsend, int const ir_type, irsend->sendLegoPf(code, bits, repeat); break; #endif +#if SEND_GOODWEATHER + case GOODWEATHER: // 63 + if (bits == 0) bits = kGoodweatherBits; + repeat = std::max(repeat, kGoodweatherMinRepeat); + irsend->sendGoodweather(code, bits, repeat); + break; +#endif // SEND_GOODWEATHER default: // If we got here, we didn't know how to send it. success = false; diff --git a/examples/IRrecvDumpV2/IRrecvDumpV2.ino b/examples/IRrecvDumpV2/IRrecvDumpV2.ino index d76a3b68e..fb715df03 100644 --- a/examples/IRrecvDumpV2/IRrecvDumpV2.ino +++ b/examples/IRrecvDumpV2/IRrecvDumpV2.ino @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -182,6 +183,13 @@ void dumpACInfo(decode_results *results) { description = ac.toString(); } #endif // DECODE_TOSHIBA_AC +#if DECODE_GOODWEATHER + if (results->decode_type == GOODWEATHER) { + IRGoodweatherAc ac(0); + ac.setRaw(results->value); // Goodweather uses value instead of state. + description = ac.toString(); + } +#endif // DECODE_GOODWEATHER #if DECODE_GREE if (results->decode_type == GREE) { IRGreeAC ac(0); diff --git a/src/IRrecv.cpp b/src/IRrecv.cpp index 6ad4937b0..d072f9a7b 100644 --- a/src/IRrecv.cpp +++ b/src/IRrecv.cpp @@ -527,6 +527,10 @@ bool IRrecv::decode(decode_results *results, irparams_t *save) { DPRINTLN("Attempting SHARP_AC decode"); if (decodeSharpAc(results)) return true; #endif +#if DECODE_GOODWEATHER + DPRINTLN("Attempting GOODWEATHER decode"); + if (decodeGoodweather(results)) return true; +#endif // DECODE_GOODWEATHER #if DECODE_HASH // decodeHash returns a hash on any input. // Thus, it needs to be last in the list. diff --git a/src/IRrecv.h b/src/IRrecv.h index f6f40ad0d..b4e97c303 100644 --- a/src/IRrecv.h +++ b/src/IRrecv.h @@ -307,6 +307,11 @@ class IRrecv { bool decodeCarrierAC(decode_results *results, uint16_t nbits = kCarrierAcBits, bool strict = true); #endif +#if DECODE_GOODWEATHER + bool decodeGoodweather(decode_results *results, + const uint16_t nbits = kGoodweatherBits, + const bool strict = true); +#endif // DECODE_GOODWEATHER #if DECODE_GREE bool decodeGree(decode_results *results, uint16_t nbits = kGreeBits, bool strict = true); diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h index ce45f74be..d26e63ab8 100644 --- a/src/IRremoteESP8266.h +++ b/src/IRremoteESP8266.h @@ -141,6 +141,9 @@ #define DECODE_GLOBALCACHE false // Not written. #define SEND_GLOBALCACHE true +#define DECODE_GOODWEATHER true +#define SEND_GOODWEATHER true + #define DECODE_GREE true #define SEND_GREE true @@ -317,8 +320,9 @@ enum decode_type_t { MITSUBISHI_HEAVY_152, // 60 DAIKIN216, SHARP_AC, + GOODWEATHER, // Add new entries before this one, and update it to point to the last entry. - kLastDecodeType = SHARP_AC, + kLastDecodeType = GOODWEATHER, }; // Message lengths & required repeat values @@ -358,6 +362,8 @@ const uint16_t kFujitsuAcBits = kFujitsuAcStateLength * 8; const uint16_t kFujitsuAcMinBits = (kFujitsuAcStateLengthShort - 1) * 8; const uint16_t kGicableBits = 16; const uint16_t kGicableMinRepeat = kSingleRepeat; +const uint16_t kGoodweatherBits = 48; +const uint16_t kGoodweatherMinRepeat = 1; const uint16_t kGreeStateLength = 8; const uint16_t kGreeBits = kGreeStateLength * 8; const uint16_t kGreeDefaultRepeat = kNoRepeat; diff --git a/src/IRsend.h b/src/IRsend.h index 53374333e..9eb0556f7 100644 --- a/src/IRsend.h +++ b/src/IRsend.h @@ -307,6 +307,11 @@ class IRsend { void sendGree(uint8_t data[], uint16_t nbytes = kGreeStateLength, uint16_t repeat = kGreeDefaultRepeat); #endif +#if SEND_GOODWEATHER + void sendGoodweather(const uint64_t data, + const uint16_t nbits = kGoodweatherBits, + const uint16_t repeat = kGoodweatherMinRepeat); +#endif // SEND_GOODWEATHER #if SEND_PRONTO void sendPronto(uint16_t data[], uint16_t len, uint16_t repeat = kNoRepeat); #endif diff --git a/src/IRutils.cpp b/src/IRutils.cpp index cdc105e62..2476e83db 100644 --- a/src/IRutils.cpp +++ b/src/IRutils.cpp @@ -120,6 +120,8 @@ decode_type_t strToDecodeType(const char * const str) { return decode_type_t::GICABLE; else if (!strcmp(str, "GLOBALCACHE")) return decode_type_t::GLOBALCACHE; + else if (!strcmp(str, "GOODWEATHER")) + return decode_type_t::GOODWEATHER; else if (!strcmp(str, "GREE")) return decode_type_t::GREE; else if (!strcmp(str, "HAIER_AC")) @@ -344,6 +346,9 @@ std::string typeToString(const decode_type_t protocol, const bool isRepeat) { case GLOBALCACHE: result = F("GLOBALCACHE"); break; + case GOODWEATHER: + result = F("GOODWEATHER"); + break; case GREE: result = F("GREE"); break; diff --git a/src/ir_Goodweather.cpp b/src/ir_Goodweather.cpp new file mode 100644 index 000000000..8f4886231 --- /dev/null +++ b/src/ir_Goodweather.cpp @@ -0,0 +1,396 @@ +// Copyright 2019 ribeirodanielf +// Copyright 2019 David Conran +// +// Code to emulate Goodweather protocol compatible HVAC devices. +// Should be compatible with: +// * ZH/JT-03 remote control +// + +#include "ir_Goodweather.h" +#include +#ifndef ARDUINO +#include +#endif +#include "IRrecv.h" +#include "IRremoteESP8266.h" +#include "IRsend.h" +#include "IRutils.h" + +#if SEND_GOODWEATHER +// Send a Goodweather message. +// +// Args: +// data: The raw message to be sent. +// nbits: Nr. of bits of data in the message. (Default is kGoodweatherBits) +// repeat: Nr. of times the message is to be repeated. (Default = 1). +// +// Status: ALPHA / Untested. +// +// Ref: +// https://github.com/markszabo/IRremoteESP8266/issues/697 +void IRsend::sendGoodweather(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + if (nbits != kGoodweatherBits) + return; // Wrong nr. of bits to send a proper message. + // Set IR carrier frequency + enableIROut(38); + + for (uint16_t r = 0; r <= repeat; r++) { + // Header + mark(kGoodweatherHdrMark); + space(kGoodweatherHdrSpace); + + // Data + for (int16_t i = 0; i < nbits; i += 8) { + uint16_t chunk = (data >> i) & 0xFF; // Grab a byte at a time. + chunk = (~chunk) << 8 | chunk; // Prepend a inverted copy of the byte. + sendData(kGoodweatherBitMark, kGoodweatherOneSpace, + kGoodweatherBitMark, kGoodweatherZeroSpace, + chunk, 16, false); + } + // Footer + mark(kGoodweatherBitMark); + space(kGoodweatherHdrSpace); + } +} +#endif // SEND_GOODWEATHER + +IRGoodweatherAc::IRGoodweatherAc(uint16_t pin) : _irsend(pin) { stateReset(); } + +void IRGoodweatherAc::stateReset(void) { +} + +void IRGoodweatherAc::begin(void) { _irsend.begin(); } + +#if SEND_GOODWEATHER +void IRGoodweatherAc::send(const uint16_t repeat) { + _irsend.sendGoodweather(remote, kGoodweatherBits, repeat); +} +#endif // SEND_GOODWEATHER + +uint64_t IRGoodweatherAc::getRaw(void) { return remote; } + +void IRGoodweatherAc::setRaw(const uint64_t state) { remote = state; } + +void IRGoodweatherAc::on(void) { remote |= kGoodweatherBitPower; } + +void IRGoodweatherAc::off(void) { remote &= ~kGoodweatherBitPower; } + +void IRGoodweatherAc::setPower(const bool on) { + if (on) + this->on(); + else + this->off(); +} + +bool IRGoodweatherAc::getPower(void) { return remote && kGoodweatherBitPower; } + +// Set the temp. in deg C +void IRGoodweatherAc::setTemp(const uint8_t temp) { + uint8_t new_temp = std::max((uint8_t)kGoodweatherTempMin, temp); + new_temp = std::min((uint8_t)kGoodweatherTempMax, new_temp); + remote |= (uint64_t)(new_temp - kGoodweatherTempMin) << kGoodweatherBitTemp; +} + +// Return the set temp. in deg C +uint8_t IRGoodweatherAc::getTemp(void) { + return ((remote & kGoodweatherTempMask) >> kGoodweatherBitTemp) + + kGoodweatherTempMin; +} + +// Set the speed of the fan +void IRGoodweatherAc::setFan(const uint8_t speed) { + switch (speed) { + case kGoodweatherFanAuto: + case kGoodweatherFanLow: + case kGoodweatherFanMed: + case kGoodweatherFanHigh: + remote &= ~kGoodweatherFanMask; + remote |= ((uint64_t)speed << kGoodweatherBitFan); + break; + default: + this->setFan(kGoodweatherFanAuto); + } +} + +uint8_t IRGoodweatherAc::getFan() { + return (remote & kGoodweatherFanMask) >> kGoodweatherBitFan; +} + +void IRGoodweatherAc::setMode(const uint8_t mode) { + switch (mode) { + case kGoodweatherAuto: + case kGoodweatherDry: + case kGoodweatherCool: + case kGoodweatherFan: + case kGoodweatherHeat: + remote &= ~kGoodweatherModeMask; + remote |= (uint64_t)mode << kGoodweatherBitMode; + break; + default: + // If we get an unexpected mode, default to AUTO. + this->setMode(kGoodweatherAuto); + } +} + +uint8_t IRGoodweatherAc::getMode() { + return (remote & kGoodweatherModeMask) >> kGoodweatherBitMode; +} + +void IRGoodweatherAc::setLight(const bool toggle) { + if (toggle) + remote |= kGoodweatherLightMask; + else + remote &= ~kGoodweatherLightMask; +} + +bool IRGoodweatherAc::getLight() { return remote & kGoodweatherLightMask; } + +void IRGoodweatherAc::setSleep(const bool toggle) { + if (toggle) + remote |= kGoodweatherSleepMask; + else + remote &= ~kGoodweatherSleepMask; +} + +bool IRGoodweatherAc::getSleep() { return remote & kGoodweatherSleepMask; } + +void IRGoodweatherAc::setTurbo(const bool toggle) { + if (toggle) + remote |= kGoodweatherTurboMask; + else + remote &= ~kGoodweatherTurboMask; +} + +bool IRGoodweatherAc::getTurbo() { return remote & kGoodweatherTurboMask; } + +void IRGoodweatherAc::setSwing(const uint8_t speed) { + switch (speed) { + case kGoodweatherSwingOff: + case kGoodweatherSwingSlow: + case kGoodweatherSwingFast: + remote &= ~kGoodweatherSwingMask; + remote |= ((uint64_t)speed << kGoodweatherBitSwing); + break; + default: + this->setSwing(kGoodweatherSwingOff); + } +} + +uint8_t IRGoodweatherAc::getSwing() { + return (remote & kGoodweatherSwingMask) >> kGoodweatherBitSwing; +} + +// Convert a standard A/C mode into its native mode. +uint8_t IRGoodweatherAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: + return kGoodweatherCool; + case stdAc::opmode_t::kHeat: + return kGoodweatherHeat; + case stdAc::opmode_t::kDry: + return kGoodweatherDry; + case stdAc::opmode_t::kFan: + return kGoodweatherFan; + default: + return kGoodweatherAuto; + } +} + +// Convert a standard A/C Fan speed into its native fan speed. +uint8_t IRGoodweatherAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: + return kGoodweatherFanLow; + case stdAc::fanspeed_t::kMedium: + return kGoodweatherFanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: + return kGoodweatherFanHigh; + default: + return kGoodweatherFanAuto; + } +} + +// Convert a standard A/C Vertical Swing into its native version. +uint8_t IRGoodweatherAc::convertSwingV(const stdAc::swingv_t swingv) { + switch (swingv) { + case stdAc::swingv_t::kHighest: + case stdAc::swingv_t::kHigh: + case stdAc::swingv_t::kMiddle: + return kGoodweatherSwingFast; + case stdAc::swingv_t::kLow: + case stdAc::swingv_t::kLowest: + case stdAc::swingv_t::kAuto: + return kGoodweatherSwingSlow; + default: + return kGoodweatherSwingOff; + } +} + +// Convert the internal state into a human readable string. +#ifdef ARDUINO +String IRGoodweatherAc::toString() { + String result = ""; +#else +std::string IRGoodweatherAc::toString() { + std::string result = ""; +#endif // ARDUINO + result.reserve(150); // Reserve some heap for the string to reduce fragging. + result += F("Power: "); + if (this->getPower()) + result += F("On"); + else + return result += F("Off"); // If it's off, there is no other info. + result += F(", Mode: "); + result += uint64ToString(this->getMode()); + switch (this->getMode()) { + case kGoodweatherAuto: + result += F(" (AUTO)"); + break; + case kGoodweatherCool: + result += F(" (COOL)"); + break; + case kGoodweatherHeat: + result += F(" (HEAT)"); + break; + case kGoodweatherDry: + result += F(" (DRY)"); + break; + case kGoodweatherFan: + result += F(" (FAN)"); + break; + default: + result += F(" (UNKNOWN)"); + } + result += F(", Temp: "); + result += uint64ToString(this->getTemp()); + result += F("C, Fan: "); + result += uint64ToString(this->getFan()); + switch (this->getFan()) { + case kGoodweatherFanAuto: + result += F(" (AUTO)"); + break; + case kGoodweatherFanHigh: + result += F(" (HIGH)"); + break; + case kGoodweatherFanMed: + result += F(" (MED)"); + break; + case kGoodweatherFanLow: + result += F(" (LOW)"); + break; + } + result += F(", Turbo: "); + if (this->getTurbo()) + result += F("Toggle"); + else + result += F("-"); + result += F(", Light: "); + if (this->getLight()) + result += F("Toggle"); + else + result += F("-"); + result += F(", Sleep: "); + if (this->getSleep()) + result += F("Toggle"); + else + result += F("-"); + result += F(", Swing: "); + result += uint64ToString(this->getSwing()); + switch (this->getSwing()) { + case kGoodweatherSwingFast: + result += F(" (Fast)"); + break; + case kGoodweatherSwingSlow: + result += F(" (Slow)"); + break; + case kGoodweatherSwingOff: + result += F(" (Off)"); + break; + default: + result += F(" (UNKNOWN)"); + } + return result; +} + +#if DECODE_GOODWEATHER +// Decode the supplied Goodweather 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 kGoodweatherBits. +// 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. +bool IRrecv::decodeGoodweather(decode_results* results, + const uint16_t nbits, + const bool strict) { + if (results->rawlen < 2 * (2 * nbits + kHeader + kFooter) - 1) + return false; // Can't possibly be a valid Goodweather message. + if (strict && nbits != kGoodweatherBits) + return false; // Not strictly a Goodweather message. + + uint64_t dataSoFar = 0; + uint16_t dataBitsSoFar = 0; + uint16_t offset = kStartOffset; + match_result_t data_result; + + // Header + if (!matchMark(results->rawbuf[offset++], kGoodweatherHdrMark)) return false; + if (!matchSpace(results->rawbuf[offset++], kGoodweatherHdrSpace)) + return false; + + // Data + for (; offset <= results->rawlen - 32 && dataBitsSoFar < nbits; + dataBitsSoFar += 8) { + DPRINT("DEBUG: Attempting Byte #"); + DPRINTLN(dataBitsSoFar / 8); + // Read in a byte at a time. + // Normal first. + data_result = matchData(&(results->rawbuf[offset]), 8, + kGoodweatherBitMark, kGoodweatherOneSpace, + kGoodweatherBitMark, kGoodweatherZeroSpace, + kTolerance, kMarkExcess, false); + if (data_result.success == false) return false; + DPRINTLN("DEBUG: Normal byte read okay."); + offset += data_result.used; + uint8_t data = (uint8_t)data_result.data; + // Then inverted. + data_result = matchData(&(results->rawbuf[offset]), 8, + kGoodweatherBitMark, kGoodweatherOneSpace, + kGoodweatherBitMark, kGoodweatherZeroSpace, + kTolerance, kMarkExcess, false); + if (data_result.success == false) return false; + DPRINTLN("DEBUG: Inverted byte read okay."); + offset += data_result.used; + uint8_t inverted = (uint8_t)data_result.data; + DPRINT("DEBUG: data = "); + DPRINTLN((uint16_t)data); + DPRINT("DEBUG: inverted = "); + DPRINTLN((uint16_t)inverted); + if (data != (inverted ^ 0xFF)) return false; // Data integrity failed. + dataSoFar |= (uint64_t)data << dataBitsSoFar; + } + + // Footer. + if (!matchMark(results->rawbuf[offset++], kGoodweatherBitMark)) return false; + if (offset <= results->rawlen && + !matchAtLeast(results->rawbuf[offset], kGoodweatherHdrSpace)) + return false; + + // Compliance + if (strict && (dataBitsSoFar != kGoodweatherBits)) return false; + + // Success + results->decode_type = decode_type_t::GOODWEATHER; + results->bits = dataBitsSoFar; + results->value = dataSoFar; + results->address = 0; + results->command = 0; + return true; +} +#endif // DECODE_GOODWEATHER diff --git a/src/ir_Goodweather.h b/src/ir_Goodweather.h new file mode 100644 index 000000000..5cd07387a --- /dev/null +++ b/src/ir_Goodweather.h @@ -0,0 +1,132 @@ +// Goodweather A/C +// +// Copyright 2019 ribeirodanielf +// Copyright 2019 David Conran + +#ifndef IR_GOODWEATHER_H_ +#define IR_GOODWEATHER_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include +#else +#include +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +// Supports: +// ZH/JT-03 remote controller +// Ref: +// https://github.com/markszabo/IRremoteESP8266/issues/697 + +// Constants + +// Timing +const uint16_t kGoodweatherTick = 620; +const uint16_t kGoodweatherBitMarkTicks = 1; +const uint16_t kGoodweatherBitMark = kGoodweatherBitMarkTicks * + kGoodweatherTick; +const uint16_t kGoodweatherOneSpaceTicks = 1; +const uint16_t kGoodweatherOneSpace = kGoodweatherOneSpaceTicks * + kGoodweatherTick; +const uint16_t kGoodweatherZeroSpaceTicks = 3; +const uint16_t kGoodweatherZeroSpace = kGoodweatherZeroSpaceTicks * + kGoodweatherTick; +const uint16_t kGoodweatherHdrMarkTicks = 11; +const uint16_t kGoodweatherHdrMark = kGoodweatherHdrMarkTicks * + kGoodweatherTick; +const uint16_t kGoodweatherHdrSpaceTicks = 11; +const uint16_t kGoodweatherHdrSpace = kGoodweatherHdrSpaceTicks * + kGoodweatherTick; + +// Masks +const uint8_t kGoodweatherBitLight = 8; +const uint64_t kGoodweatherLightMask = 0x1ULL << kGoodweatherBitLight; +const uint8_t kGoodweatherBitTurbo = 11; +const uint64_t kGoodweatherTurboMask = 0x1ULL << kGoodweatherBitTurbo; +const uint8_t kGoodweatherBitSleep = 24; +const uint64_t kGoodweatherSleepMask = 0x1ULL << kGoodweatherBitSleep; +const uint8_t kGoodweatherBitPower = kGoodweatherBitSleep + 1; // 25 +const uint64_t kGoodweatherPowerMask = 0x1ULL << kGoodweatherBitPower; +const uint8_t kGoodweatherBitSwing = kGoodweatherBitPower + 1; // 26 +const uint64_t kGoodweatherSwingMask = 0x3ULL << kGoodweatherBitSwing; +const uint8_t kGoodweatherBitFan = kGoodweatherBitSwing + 3; // 29 +const uint64_t kGoodweatherFanMask = 0x3ULL << kGoodweatherBitFan; +const uint8_t kGoodweatherBitTemp = kGoodweatherBitFan + 3; // 32 +const uint64_t kGoodweatherTempMask = 0xFULL << kGoodweatherBitTemp; +const uint8_t kGoodweatherBitMode = kGoodweatherBitFan + 8; // 37 +const uint64_t kGoodweatherModeMask = 0x7ULL << kGoodweatherBitMode; + +// Modes +const uint8_t kGoodweatherAuto = 0b000; +const uint8_t kGoodweatherCool = 0b001; +const uint8_t kGoodweatherDry = 0b010; +const uint8_t kGoodweatherFan = 0b011; +const uint8_t kGoodweatherHeat = 0b100; +const uint8_t kGoodweatherSwingFast = 0b00; +const uint8_t kGoodweatherSwingSlow = 0b01; +const uint8_t kGoodweatherSwingOff = 0b10; +// Fan Control +const uint8_t kGoodweatherFanAuto = 0b00; +const uint8_t kGoodweatherFanHigh = 0b01; +const uint8_t kGoodweatherFanMed = 0b10; +const uint8_t kGoodweatherFanLow = 0b11; + +// Temperature +const uint8_t kGoodweatherTempMin = 16; // Celsius +const uint8_t kGoodweatherTempMax = 31; // Celsius + + +// Classes +class IRGoodweatherAc { + public: + explicit IRGoodweatherAc(uint16_t pin); + + void stateReset(void); +#if SEND_GOODWEATHER + void send(const uint16_t repeat = kGoodweatherMinRepeat); +#endif // SEND_GOODWEATHER + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void); + void setTemp(const uint8_t temp); + uint8_t getTemp(void); + void setFan(const uint8_t speed); + uint8_t getFan(void); + void setMode(const uint8_t mode); + uint8_t getMode(); + void setSwing(const uint8_t speed); + uint8_t getSwing(void); + void setSleep(const bool toggle); + bool getSleep(void); + void setTurbo(const bool toggle); + bool getTurbo(void); + void setLight(const bool toggle); + bool getLight(void); + uint64_t getRaw(void); + void setRaw(const uint64_t state); + uint8_t convertMode(const stdAc::opmode_t mode); + uint8_t convertFan(const stdAc::fanspeed_t speed); + uint8_t convertSwingV(const stdAc::swingv_t swingv); +#ifdef ARDUINO + String toString(); +#else + std::string toString(); +#endif +#ifndef UNIT_TEST + + private: + IRsend _irsend; +#else + IRsendTest _irsend; +#endif + uint64_t remote; // The state of the IR remote in IR code form. +}; +#endif // IR_GOODWEATHER_H_ diff --git a/test/Makefile b/test/Makefile index 78b42a4f6..14a5acc5f 100644 --- a/test/Makefile +++ b/test/Makefile @@ -38,7 +38,7 @@ TESTS = IRutils_test IRsend_test ir_NEC_test ir_GlobalCache_test \ ir_Carrier_test ir_Haier_test ir_Hitachi_test ir_GICable_test \ ir_Whirlpool_test ir_Lutron_test ir_Electra_test ir_Pioneer_test \ ir_MWM_test ir_Vestel_test ir_Teco_test ir_Tcl_test ir_Lego_test IRac_test \ - ir_MitsubishiHeavy_test ir_Trotec_test ir_Argo_test + ir_MitsubishiHeavy_test ir_Trotec_test ir_Argo_test ir_Goodweather_test # All Google Test headers. Usually you shouldn't change this # definition. @@ -83,7 +83,7 @@ PROTOCOLS = ir_NEC.o ir_Sony.o ir_Samsung.o ir_JVC.o ir_RCMM.o ir_RC5_RC6.o \ ir_Midea.o ir_Magiquest.o ir_Lasertag.o ir_Carrier.o ir_Haier.o \ ir_Hitachi.o ir_GICable.o ir_Whirlpool.o ir_Lutron.o ir_Electra.o \ ir_Pioneer.o ir_MWM.o ir_Vestel.o ir_Teco.o ir_Tcl.o ir_Lego.o ir_Argo.o \ - ir_Trotec.o ir_MitsubishiHeavy.o + ir_Trotec.o ir_MitsubishiHeavy.o ir_Goodweather.o # All the IR Protocol header files. PROTOCOLS_H = $(USER_DIR)/ir_Argo.h \ @@ -94,6 +94,7 @@ PROTOCOLS_H = $(USER_DIR)/ir_Argo.h \ $(USER_DIR)/ir_Midea.h \ $(USER_DIR)/ir_Toshiba.h \ $(USER_DIR)/ir_Daikin.h \ + $(USER_DIR)/ir_Goodweather.h \ $(USER_DIR)/ir_Kelvinator.h \ $(USER_DIR)/ir_Mitsubishi.h \ $(USER_DIR)/ir_MitsubishiHeavy.h \ @@ -576,3 +577,12 @@ ir_Trotec_test.o : ir_Trotec_test.cpp $(COMMON_TEST_DEPS) $(GTEST_HEADERS) ir_Trotec_test : $(COMMON_OBJ) ir_Trotec_test.o $(CXX) $(CPPFLAGS) $(CXXFLAGS) -lpthread $^ -o $@ + +ir_Goodweather.o : $(USER_DIR)/ir_Goodweather.cpp $(COMMON_DEPS) + $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(INCLUDES) -c $(USER_DIR)/ir_Goodweather.cpp + +ir_Goodweather_test.o : ir_Goodweather_test.cpp $(COMMON_TEST_DEPS) $(GTEST_HEADERS) + $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(INCLUDES) -c ir_Goodweather_test.cpp + +ir_Goodweather_test : $(COMMON_OBJ) ir_Goodweather_test.o + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -lpthread $^ -o $@ diff --git a/test/ir_Goodweather_test.cpp b/test/ir_Goodweather_test.cpp new file mode 100644 index 000000000..7c22c31e8 --- /dev/null +++ b/test/ir_Goodweather_test.cpp @@ -0,0 +1,189 @@ +// Copyright 2019 David Conran + +#include "ir_Goodweather.h" +#include "IRrecv.h" +#include "IRrecv_test.h" +#include "IRsend.h" +#include "IRsend_test.h" +#include "gtest/gtest.h" + +TEST(TestIRUtils, Goodweather) { + ASSERT_EQ("GOODWEATHER", typeToString(decode_type_t::GOODWEATHER)); + ASSERT_EQ(decode_type_t::GOODWEATHER, strToDecodeType("GOODWEATHER")); + ASSERT_FALSE(hasACState(decode_type_t::GOODWEATHER)); +} + +// Tests for sendGoodweather(). + +// Test sending typical data only. +TEST(TestSendGoodweather, SendDataOnly) { + IRsendTest irsend(4); + irsend.begin(); + + irsend.reset(); + irsend.sendGoodweather(0x0, kGoodweatherBits, 0); + EXPECT_EQ( + "f38000d50" + "m6820s6820" + "m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860" + "m620s620m620s620m620s620m620s620m620s620m620s620m620s620m620s620" + "m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860" + "m620s620m620s620m620s620m620s620m620s620m620s620m620s620m620s620" + "m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860" + "m620s620m620s620m620s620m620s620m620s620m620s620m620s620m620s620" + "m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860" + "m620s620m620s620m620s620m620s620m620s620m620s620m620s620m620s620" + "m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860" + "m620s620m620s620m620s620m620s620m620s620m620s620m620s620m620s620" + "m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860" + "m620s620m620s620m620s620m620s620m620s620m620s620m620s620m620s620" + "m620s6820", + irsend.outputStr()); + + irsend.reset(); +} +/* +// Test sending with different repeats. +TEST(TestSendGoodweather, SendWithRepeats) { + IRsendTest irsend(4); + irsend.begin(); + + irsend.reset(); + irsend.sendGoodweather(0xAA55AA, kGoodweatherBits, 1); // 1 repeat. + EXPECT_EQ( + "f38000d50" + "m4480s4480" + "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" + "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" + "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" + "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" + "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" + "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" + "m560s5040" + "m4480s4480" + "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" + "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" + "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" + "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" + "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" + "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" + "m560s5040", + irsend.outputStr()); + irsend.sendGoodweather(0xAA55AA, kGoodweatherBits, 2); // 2 repeats. + EXPECT_EQ( + "f38000d50" + "m4480s4480" + "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" + "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" + "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" + "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" + "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" + "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" + "m560s5040" + "m4480s4480" + "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" + "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" + "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" + "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" + "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" + "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" + "m560s5040" + "m4480s4480" + "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" + "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" + "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" + "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" + "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" + "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" + "m560s5040", + irsend.outputStr()); +} + +// Test sending an atypical data size. +TEST(TestSendGoodweather, SendUnusualSize) { + IRsendTest irsend(4); + irsend.begin(); + + irsend.reset(); + irsend.sendGoodweather(0x0, 8); + EXPECT_EQ( + "f38000d50" + "m4480s4480" + "m560s560m560s560m560s560m560s560m560s560m560s560m560s560m560s560" + "m560s1680m560s1680m560s1680m560s1680m560s1680m560s1680m560s1680m560s1680" + "m560s5040" + "m4480s4480" + "m560s560m560s560m560s560m560s560m560s560m560s560m560s560m560s560" + "m560s1680m560s1680m560s1680m560s1680m560s1680m560s1680m560s1680m560s1680" + "m560s5040", + irsend.outputStr()); + + irsend.reset(); + irsend.sendGoodweather(0x1234567890ABCDEF, 64); + EXPECT_EQ( + "f38000d50" + "m4480s4480" + "m560s560m560s560m560s560m560s1680m560s560m560s560m560s1680m560s560" + "m560s1680m560s1680m560s1680m560s560m560s1680m560s1680m560s560m560s1680" + "m560s560m560s560m560s1680m560s1680m560s560m560s1680m560s560m560s560" + "m560s1680m560s1680m560s560m560s560m560s1680m560s560m560s1680m560s1680" + "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s1680m560s560" + "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s560m560s1680" + "m560s560m560s1680m560s1680m560s1680m560s1680m560s560m560s560m560s560" + "m560s1680m560s560m560s560m560s560m560s560m560s1680m560s1680m560s1680" + "m560s1680m560s560m560s560m560s1680m560s560m560s560m560s560m560s560" + "m560s560m560s1680m560s1680m560s560m560s1680m560s1680m560s1680m560s1680" + "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s1680" + "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s560" + "m560s1680m560s1680m560s560m560s560m560s1680m560s1680m560s560m560s1680" + "m560s560m560s560m560s1680m560s1680m560s560m560s560m560s1680m560s560" + "m560s1680m560s1680m560s1680m560s560m560s1680m560s1680m560s1680m560s1680" + "m560s560m560s560m560s560m560s1680m560s560m560s560m560s560m560s560" + "m560s5040" + "m4480s4480" + "m560s560m560s560m560s560m560s1680m560s560m560s560m560s1680m560s560" + "m560s1680m560s1680m560s1680m560s560m560s1680m560s1680m560s560m560s1680" + "m560s560m560s560m560s1680m560s1680m560s560m560s1680m560s560m560s560" + "m560s1680m560s1680m560s560m560s560m560s1680m560s560m560s1680m560s1680" + "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s1680m560s560" + "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s560m560s1680" + "m560s560m560s1680m560s1680m560s1680m560s1680m560s560m560s560m560s560" + "m560s1680m560s560m560s560m560s560m560s560m560s1680m560s1680m560s1680" + "m560s1680m560s560m560s560m560s1680m560s560m560s560m560s560m560s560" + "m560s560m560s1680m560s1680m560s560m560s1680m560s1680m560s1680m560s1680" + "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s1680" + "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s560" + "m560s1680m560s1680m560s560m560s560m560s1680m560s1680m560s560m560s1680" + "m560s560m560s560m560s1680m560s1680m560s560m560s560m560s1680m560s560" + "m560s1680m560s1680m560s1680m560s560m560s1680m560s1680m560s1680m560s1680" + "m560s560m560s560m560s560m560s1680m560s560m560s560m560s560m560s560" + "m560s5040", + irsend.outputStr()); + + // Bit sizes must be a multiple of 8. + irsend.reset(); + irsend.sendGoodweather(0x0, 17); + EXPECT_EQ("", irsend.outputStr()); +} +*/ + +// Tests for decodeGoodweather(). + +// Decode normal Goodweather messages. +TEST(TestDecodeGoodweather, SyntheticDecode) { + IRsendTest irsend(0); + IRrecv irrecv(0); + irsend.begin(); + + // Normal Goodweather 48-bit message. + irsend.reset(); + irsend.sendGoodweather(0x1234567890AB); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(decode_type_t::GOODWEATHER, irsend.capture.decode_type); + EXPECT_EQ(kGoodweatherBits, irsend.capture.bits); + EXPECT_EQ(0x1234567890AB, irsend.capture.value); + EXPECT_EQ(0x0, irsend.capture.address); + EXPECT_EQ(0x0, irsend.capture.command); + EXPECT_FALSE(irsend.capture.repeat); +} diff --git a/tools/Makefile b/tools/Makefile index ef2377c3d..8100e0d72 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -50,7 +50,7 @@ PROTOCOLS = ir_NEC.o ir_Sony.o ir_Samsung.o ir_JVC.o ir_RCMM.o ir_RC5_RC6.o \ ir_Magiquest.o ir_Lasertag.o ir_Carrier.o ir_Haier.o ir_Hitachi.o \ ir_GICable.o ir_Whirlpool.o ir_Lutron.o ir_Electra.o ir_Pioneer.o \ ir_MWM.o ir_Vestel.o ir_Teco.o ir_Tcl.o ir_Lego.o \ - ir_MitsubishiHeavy.o + ir_MitsubishiHeavy.o ir_Goodweather.o # Common object files COMMON_OBJ = IRutils.o IRtimer.o IRsend.o IRrecv.o $(PROTOCOLS) @@ -211,3 +211,6 @@ ir_Tcl.o : $(USER_DIR)/ir_Tcl.cpp $(USER_DIR)/ir_Tcl.h $(GTEST_HEADERS) ir_Lego.o : $(USER_DIR)/ir_Lego.cpp $(GTEST_HEADERS) $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $(USER_DIR)/ir_Lego.cpp + +ir_Goodweather.o : $(USER_DIR)/ir_Goodweather.cpp $(USER_DIR)/ir_Goodweather.h $(GTEST_HEADERS) + $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(INCLUDES) -c $(USER_DIR)/ir_Goodweather.cpp From eb5b74fae0bbd4cd016e22c98e0ae6a23b5d0a5d Mon Sep 17 00:00:00 2001 From: David Date: Tue, 14 May 2019 10:52:22 +1000 Subject: [PATCH 2/4] Make changes based on real data for Goodweather protocol. Ref #697 --- src/IRremoteESP8266.h | 2 +- src/ir_Goodweather.cpp | 9 +- src/ir_Goodweather.h | 25 +--- test/ir_Goodweather_test.cpp | 243 +++++++++++++++-------------------- 4 files changed, 118 insertions(+), 161 deletions(-) diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h index d26e63ab8..8f8005d20 100644 --- a/src/IRremoteESP8266.h +++ b/src/IRremoteESP8266.h @@ -363,7 +363,7 @@ const uint16_t kFujitsuAcMinBits = (kFujitsuAcStateLengthShort - 1) * 8; const uint16_t kGicableBits = 16; const uint16_t kGicableMinRepeat = kSingleRepeat; const uint16_t kGoodweatherBits = 48; -const uint16_t kGoodweatherMinRepeat = 1; +const uint16_t kGoodweatherMinRepeat = kNoRepeat; const uint16_t kGreeStateLength = 8; const uint16_t kGreeBits = kGreeStateLength * 8; const uint16_t kGreeDefaultRepeat = kNoRepeat; diff --git a/src/ir_Goodweather.cpp b/src/ir_Goodweather.cpp index 8f4886231..f8386896f 100644 --- a/src/ir_Goodweather.cpp +++ b/src/ir_Goodweather.cpp @@ -22,7 +22,7 @@ // Args: // data: The raw message to be sent. // nbits: Nr. of bits of data in the message. (Default is kGoodweatherBits) -// repeat: Nr. of times the message is to be repeated. (Default = 1). +// repeat: Nr. of times the message is to be repeated. (Default = 0). // // Status: ALPHA / Untested. // @@ -51,6 +51,8 @@ void IRsend::sendGoodweather(const uint64_t data, const uint16_t nbits, // Footer mark(kGoodweatherBitMark); space(kGoodweatherHdrSpace); + mark(kGoodweatherBitMark); + space(kDefaultMessageGap); } } #endif // SEND_GOODWEATHER @@ -329,7 +331,7 @@ std::string IRGoodweatherAc::toString() { bool IRrecv::decodeGoodweather(decode_results* results, const uint16_t nbits, const bool strict) { - if (results->rawlen < 2 * (2 * nbits + kHeader + kFooter) - 1) + if (results->rawlen < 2 * (2 * nbits) + kHeader + 2 * kFooter - 1) return false; // Can't possibly be a valid Goodweather message. if (strict && nbits != kGoodweatherBits) return false; // Not strictly a Goodweather message. @@ -378,6 +380,9 @@ bool IRrecv::decodeGoodweather(decode_results* results, // Footer. if (!matchMark(results->rawbuf[offset++], kGoodweatherBitMark)) return false; + if (!matchSpace(results->rawbuf[offset++], kGoodweatherHdrSpace)) + return false; + if (!matchMark(results->rawbuf[offset++], kGoodweatherBitMark)) return false; if (offset <= results->rawlen && !matchAtLeast(results->rawbuf[offset], kGoodweatherHdrSpace)) return false; diff --git a/src/ir_Goodweather.h b/src/ir_Goodweather.h index 5cd07387a..b3974daa7 100644 --- a/src/ir_Goodweather.h +++ b/src/ir_Goodweather.h @@ -27,29 +27,18 @@ // Constants // Timing -const uint16_t kGoodweatherTick = 620; -const uint16_t kGoodweatherBitMarkTicks = 1; -const uint16_t kGoodweatherBitMark = kGoodweatherBitMarkTicks * - kGoodweatherTick; -const uint16_t kGoodweatherOneSpaceTicks = 1; -const uint16_t kGoodweatherOneSpace = kGoodweatherOneSpaceTicks * - kGoodweatherTick; -const uint16_t kGoodweatherZeroSpaceTicks = 3; -const uint16_t kGoodweatherZeroSpace = kGoodweatherZeroSpaceTicks * - kGoodweatherTick; -const uint16_t kGoodweatherHdrMarkTicks = 11; -const uint16_t kGoodweatherHdrMark = kGoodweatherHdrMarkTicks * - kGoodweatherTick; -const uint16_t kGoodweatherHdrSpaceTicks = 11; -const uint16_t kGoodweatherHdrSpace = kGoodweatherHdrSpaceTicks * - kGoodweatherTick; +const uint16_t kGoodweatherBitMark = 640; +const uint16_t kGoodweatherOneSpace = 580; +const uint16_t kGoodweatherZeroSpace = 1600; +const uint16_t kGoodweatherHdrMark = 6800; +const uint16_t kGoodweatherHdrSpace = 6800; // Masks const uint8_t kGoodweatherBitLight = 8; const uint64_t kGoodweatherLightMask = 0x1ULL << kGoodweatherBitLight; -const uint8_t kGoodweatherBitTurbo = 11; +const uint8_t kGoodweatherBitTurbo = kGoodweatherBitLight + 3; // 11 const uint64_t kGoodweatherTurboMask = 0x1ULL << kGoodweatherBitTurbo; -const uint8_t kGoodweatherBitSleep = 24; +const uint8_t kGoodweatherBitSleep = kGoodweatherBitTurbo + 13; // 24 const uint64_t kGoodweatherSleepMask = 0x1ULL << kGoodweatherBitSleep; const uint8_t kGoodweatherBitPower = kGoodweatherBitSleep + 1; // 25 const uint64_t kGoodweatherPowerMask = 0x1ULL << kGoodweatherBitPower; diff --git a/test/ir_Goodweather_test.cpp b/test/ir_Goodweather_test.cpp index 7c22c31e8..68036259e 100644 --- a/test/ir_Goodweather_test.cpp +++ b/test/ir_Goodweather_test.cpp @@ -17,173 +17,136 @@ TEST(TestIRUtils, Goodweather) { // Test sending typical data only. TEST(TestSendGoodweather, SendDataOnly) { - IRsendTest irsend(4); + IRsendTest irsend(0); irsend.begin(); irsend.reset(); - irsend.sendGoodweather(0x0, kGoodweatherBits, 0); + irsend.sendGoodweather(0x0); EXPECT_EQ( "f38000d50" - "m6820s6820" - "m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860" - "m620s620m620s620m620s620m620s620m620s620m620s620m620s620m620s620" - "m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860" - "m620s620m620s620m620s620m620s620m620s620m620s620m620s620m620s620" - "m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860" - "m620s620m620s620m620s620m620s620m620s620m620s620m620s620m620s620" - "m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860" - "m620s620m620s620m620s620m620s620m620s620m620s620m620s620m620s620" - "m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860" - "m620s620m620s620m620s620m620s620m620s620m620s620m620s620m620s620" - "m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860m620s1860" - "m620s620m620s620m620s620m620s620m620s620m620s620m620s620m620s620" - "m620s6820", + "m6800s6800" + "m640s1600m640s1600m640s1600m640s1600m640s1600m640s1600m640s1600m640s1600" + "m640s580m640s580m640s580m640s580m640s580m640s580m640s580m640s580" + "m640s1600m640s1600m640s1600m640s1600m640s1600m640s1600m640s1600m640s1600" + "m640s580m640s580m640s580m640s580m640s580m640s580m640s580m640s580" + "m640s1600m640s1600m640s1600m640s1600m640s1600m640s1600m640s1600m640s1600" + "m640s580m640s580m640s580m640s580m640s580m640s580m640s580m640s580" + "m640s1600m640s1600m640s1600m640s1600m640s1600m640s1600m640s1600m640s1600" + "m640s580m640s580m640s580m640s580m640s580m640s580m640s580m640s580" + "m640s1600m640s1600m640s1600m640s1600m640s1600m640s1600m640s1600m640s1600" + "m640s580m640s580m640s580m640s580m640s580m640s580m640s580m640s580" + "m640s1600m640s1600m640s1600m640s1600m640s1600m640s1600m640s1600m640s1600" + "m640s580m640s580m640s580m640s580m640s580m640s580m640s580m640s580" + "m640s6800m640s100000", irsend.outputStr()); irsend.reset(); } -/* -// Test sending with different repeats. -TEST(TestSendGoodweather, SendWithRepeats) { - IRsendTest irsend(4); - irsend.begin(); - irsend.reset(); - irsend.sendGoodweather(0xAA55AA, kGoodweatherBits, 1); // 1 repeat. - EXPECT_EQ( - "f38000d50" - "m4480s4480" - "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" - "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" - "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" - "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" - "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" - "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" - "m560s5040" - "m4480s4480" - "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" - "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" - "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" - "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" - "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" - "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" - "m560s5040", - irsend.outputStr()); - irsend.sendGoodweather(0xAA55AA, kGoodweatherBits, 2); // 2 repeats. - EXPECT_EQ( - "f38000d50" - "m4480s4480" - "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" - "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" - "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" - "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" - "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" - "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" - "m560s5040" - "m4480s4480" - "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" - "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" - "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" - "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" - "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" - "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" - "m560s5040" - "m4480s4480" - "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" - "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" - "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" - "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" - "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560" - "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680" - "m560s5040", - irsend.outputStr()); -} +// Tests for decodeGoodweather(). -// Test sending an atypical data size. -TEST(TestSendGoodweather, SendUnusualSize) { - IRsendTest irsend(4); +// Decode normal Goodweather messages. +TEST(TestDecodeGoodweather, SyntheticDecode) { + IRsendTest irsend(0); + IRrecv irrecv(0); irsend.begin(); + // Normal (made-up value) Goodweather 48-bit message. irsend.reset(); - irsend.sendGoodweather(0x0, 8); - EXPECT_EQ( - "f38000d50" - "m4480s4480" - "m560s560m560s560m560s560m560s560m560s560m560s560m560s560m560s560" - "m560s1680m560s1680m560s1680m560s1680m560s1680m560s1680m560s1680m560s1680" - "m560s5040" - "m4480s4480" - "m560s560m560s560m560s560m560s560m560s560m560s560m560s560m560s560" - "m560s1680m560s1680m560s1680m560s1680m560s1680m560s1680m560s1680m560s1680" - "m560s5040", - irsend.outputStr()); - - irsend.reset(); - irsend.sendGoodweather(0x1234567890ABCDEF, 64); - EXPECT_EQ( - "f38000d50" - "m4480s4480" - "m560s560m560s560m560s560m560s1680m560s560m560s560m560s1680m560s560" - "m560s1680m560s1680m560s1680m560s560m560s1680m560s1680m560s560m560s1680" - "m560s560m560s560m560s1680m560s1680m560s560m560s1680m560s560m560s560" - "m560s1680m560s1680m560s560m560s560m560s1680m560s560m560s1680m560s1680" - "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s1680m560s560" - "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s560m560s1680" - "m560s560m560s1680m560s1680m560s1680m560s1680m560s560m560s560m560s560" - "m560s1680m560s560m560s560m560s560m560s560m560s1680m560s1680m560s1680" - "m560s1680m560s560m560s560m560s1680m560s560m560s560m560s560m560s560" - "m560s560m560s1680m560s1680m560s560m560s1680m560s1680m560s1680m560s1680" - "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s1680" - "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s560" - "m560s1680m560s1680m560s560m560s560m560s1680m560s1680m560s560m560s1680" - "m560s560m560s560m560s1680m560s1680m560s560m560s560m560s1680m560s560" - "m560s1680m560s1680m560s1680m560s560m560s1680m560s1680m560s1680m560s1680" - "m560s560m560s560m560s560m560s1680m560s560m560s560m560s560m560s560" - "m560s5040" - "m4480s4480" - "m560s560m560s560m560s560m560s1680m560s560m560s560m560s1680m560s560" - "m560s1680m560s1680m560s1680m560s560m560s1680m560s1680m560s560m560s1680" - "m560s560m560s560m560s1680m560s1680m560s560m560s1680m560s560m560s560" - "m560s1680m560s1680m560s560m560s560m560s1680m560s560m560s1680m560s1680" - "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s1680m560s560" - "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s560m560s1680" - "m560s560m560s1680m560s1680m560s1680m560s1680m560s560m560s560m560s560" - "m560s1680m560s560m560s560m560s560m560s560m560s1680m560s1680m560s1680" - "m560s1680m560s560m560s560m560s1680m560s560m560s560m560s560m560s560" - "m560s560m560s1680m560s1680m560s560m560s1680m560s1680m560s1680m560s1680" - "m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s1680" - "m560s560m560s1680m560s560m560s1680m560s560m560s1680m560s560m560s560" - "m560s1680m560s1680m560s560m560s560m560s1680m560s1680m560s560m560s1680" - "m560s560m560s560m560s1680m560s1680m560s560m560s560m560s1680m560s560" - "m560s1680m560s1680m560s1680m560s560m560s1680m560s1680m560s1680m560s1680" - "m560s560m560s560m560s560m560s1680m560s560m560s560m560s560m560s560" - "m560s5040", - irsend.outputStr()); - - // Bit sizes must be a multiple of 8. + irsend.sendGoodweather(0x1234567890AB); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(decode_type_t::GOODWEATHER, irsend.capture.decode_type); + EXPECT_EQ(kGoodweatherBits, irsend.capture.bits); + EXPECT_EQ(0x1234567890AB, irsend.capture.value); + EXPECT_EQ(0x0, irsend.capture.address); + EXPECT_EQ(0x0, irsend.capture.command); + EXPECT_FALSE(irsend.capture.repeat); + // Normal (Real) Goodweather 48-bit message. irsend.reset(); - irsend.sendGoodweather(0x0, 17); - EXPECT_EQ("", irsend.outputStr()); + irsend.sendGoodweather(0xD5276A030000); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(decode_type_t::GOODWEATHER, irsend.capture.decode_type); + EXPECT_EQ(kGoodweatherBits, irsend.capture.bits); + EXPECT_EQ(0xD5276A030000, irsend.capture.value); + EXPECT_EQ(0x0, irsend.capture.address); + EXPECT_EQ(0x0, irsend.capture.command); + EXPECT_FALSE(irsend.capture.repeat); } -*/ -// Tests for decodeGoodweather(). - -// Decode normal Goodweather messages. -TEST(TestDecodeGoodweather, SyntheticDecode) { +// Decode a real example of a Goodweather message. +// https://github.com/markszabo/IRremoteESP8266/issues/697#issuecomment-490209819 +TEST(TestDecodeGoodweather, RealExampleDecode) { IRsendTest irsend(0); IRrecv irrecv(0); + IRGoodweatherAc ac(0); irsend.begin(); + ac.begin(); - // Normal Goodweather 48-bit message. irsend.reset(); - irsend.sendGoodweather(0x1234567890AB); + // Raw Goodweather 48-bit message. + uint16_t rawData_1[197] = { + 6828, 6828, 732, 1780, 652, 1830, 652, 1806, 678, 1830, 652, 1806, 678, + 1830, 652, 1830, 652, 1834, 706, 518, 734, 508, 734, 514, 734, 510, 732, + 510, 732, 510, 732, 510, 732, 514, 732, 1776, 706, 1780, 628, 1854, 628, + 1832, 654, 1832, 654, 1856, 628, 1832, 634, 1876, 680, 536, 708, 536, 708, + 536, 706, 538, 706, 538, 706, 538, 706, 536, 680, 564, 680, 1828, 708, + 1758, 680, 1804, 680, 1828, 708, 1778, 732, 1754, 732, 1754, 732, 1756, + 732, 490, 658, 586, 658, 586, 658, 586, 658, 586, 658, 584, 658, 586, 658, + 586, 660, 1850, 704, 520, 658, 1828, 658, 1826, 658, 1826, 658, 586, 660, + 584, 684, 1826, 730, 490, 686, 1824, 660, 560, 710, 532, 710, 534, 712, + 1776, 712, 1774, 686, 560, 712, 1774, 712, 1798, 730, 492, 712, 1798, 684, + 1798, 678, 568, 730, 1756, 686, 1796, 686, 532, 712, 532, 712, 1796, 728, + 494, 712, 532, 738, 1772, 730, 492, 712, 532, 738, 506, 738, 1772, 660, + 582, 728, 1736, 712, 558, 710, 1750, 710, 558, 710, 510, 738, 1748, 738, + 508, 736, 1772, 684, 534, 736, 1772, 704, 518, 738, 1772, 660, 1824, 678, + 6770, 684}; // COOLIX 4624AB + irsend.sendRaw(rawData_1, 197, 38000); irsend.makeDecodeResult(); ASSERT_TRUE(irrecv.decode(&irsend.capture)); EXPECT_EQ(decode_type_t::GOODWEATHER, irsend.capture.decode_type); EXPECT_EQ(kGoodweatherBits, irsend.capture.bits); - EXPECT_EQ(0x1234567890AB, irsend.capture.value); + EXPECT_EQ(0xD52462000000, irsend.capture.value); + EXPECT_EQ(0x0, irsend.capture.address); + EXPECT_EQ(0x0, irsend.capture.command); + EXPECT_FALSE(irsend.capture.repeat); + ac.setRaw(irsend.capture.value); + EXPECT_EQ( + "Power: On, Mode: 1 (COOL), Temp: 20C, Fan: 3 (LOW), " + "Turbo: -, Light: -, Sleep: -, Swing: 0 (Fast)", + ac.toString()); + + uint16_t rawData_2[197] = { + 6190, 7296, 696, 1496, 634, 1562, 642, 1582, 640, 1564, 564, 1598, 638, + 1558, 646, 1560, 588, 1616, 618, 520, 620, 494, 622, 494, 646, 494, 620, + 496, 644, 494, 590, 528, 642, 494, 642, 1544, 638, 1584, 618, 1564, 804, + 1394, 620, 1564, 640, 1558, 644, 1586, 562, 1616, 620, 492, 672, 470, 622, + 494, 646, 494, 622, 494, 646, 494, 620, 498, 644, 492, 596, 520, 644, 494, + 592, 1596, 612, 1584, 642, 1560, 614, 1612, 594, 1584, 620, 1558, 646, + 1556, 644, 1562, 618, 520, 620, 494, 620, 494, 646, 494, 568, 548, 644, + 494, 616, 1570, 638, 494, 670, 1534, 568, 550, 646, 1556, 616, 526, 618, + 492, 672, 1532, 568, 550, 646, 1558, 640, 500, 618, 1560, 668, 470, 642, + 1548, 658, 1536, 642, 520, 588, 504, 644, 492, 644, 478, 642, 1582, 618, + 1586, 590, 506, 640, 1556, 646, 1584, 562, 1616, 620, 1558, 646, 1556, + 670, 454, 638, 492, 648, 1558, 642, 478, 644, 492, 590, 530, 858, 1342, + 642, 496, 618, 1564, 642, 492, 642, 1548, 636, 492, 648, 494, 622, 1562, + 642, 492, 644, 1562, 618, 520, 620, 1558, 644, 476, 640, 1558, 646, 1558, + 612, 7382, 594}; // UNKNOWN 71DD9105 + + irsend.reset(); + irsend.sendRaw(rawData_2, 197, 38000); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(decode_type_t::GOODWEATHER, irsend.capture.decode_type); + EXPECT_EQ(kGoodweatherBits, irsend.capture.bits); + EXPECT_EQ(0xD5276A030000, irsend.capture.value); EXPECT_EQ(0x0, irsend.capture.address); EXPECT_EQ(0x0, irsend.capture.command); EXPECT_FALSE(irsend.capture.repeat); + ac.setRaw(irsend.capture.value); + EXPECT_EQ( + "Power: On, Mode: 1 (COOL), Temp: 23C, Fan: 3 (LOW), " + "Turbo: -, Light: -, Sleep: -, Swing: 2 (Off)", + ac.toString()); } From 9a7c635c8d0fd7c1f9405c7e91db8cafb07878b4 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 18 May 2019 01:24:43 +1000 Subject: [PATCH 3/4] Add set/getCommand() to Goodweather A/C More unit tests based on supplied data. For #697 --- src/ir_Goodweather.cpp | 53 +++++++++++++ src/ir_Goodweather.h | 22 +++++- test/ir_Goodweather_test.cpp | 144 +++++++++++++++++++++++++++++++++-- 3 files changed, 210 insertions(+), 9 deletions(-) diff --git a/src/ir_Goodweather.cpp b/src/ir_Goodweather.cpp index f8386896f..d897dac80 100644 --- a/src/ir_Goodweather.cpp +++ b/src/ir_Goodweather.cpp @@ -183,6 +183,17 @@ uint8_t IRGoodweatherAc::getSwing() { return (remote & kGoodweatherSwingMask) >> kGoodweatherBitSwing; } +void IRGoodweatherAc::setCommand(const uint8_t cmd) { + if (cmd <= kGoodweatherCmdLight) { + remote &= ~kGoodweatherCommandMask; + remote |= (cmd << kGoodweatherBitCommand); + } +} + +uint8_t IRGoodweatherAc::getCommand() { + return (remote & kGoodweatherCommandMask) >> kGoodweatherBitCommand; +} + // Convert a standard A/C mode into its native mode. uint8_t IRGoodweatherAc::convertMode(const stdAc::opmode_t mode) { switch (mode) { @@ -314,6 +325,48 @@ std::string IRGoodweatherAc::toString() { default: result += F(" (UNKNOWN)"); } + result += F(", Command: "); + result += uint64ToString(this->getCommand()); + switch (this->getCommand()) { + case kGoodweatherCmdPower: + result += F(" (Power)"); + break; + case kGoodweatherCmdMode: + result += F(" (Mode)"); + break; + case kGoodweatherCmdUpTemp: + result += F(" (Temp Up)"); + break; + case kGoodweatherCmdDownTemp: + result += F(" (Temp Down)"); + break; + case kGoodweatherCmdSwing: + result += F(" (Swing)"); + break; + case kGoodweatherCmdFan: + result += F(" (Fan)"); + break; + case kGoodweatherCmdTimer: + result += F(" (Timer)"); + break; + case kGoodweatherCmdAirFlow: + result += F(" (Air Flow)"); + break; + case kGoodweatherCmdHold: + result += F(" (Hold)"); + break; + case kGoodweatherCmdSleep: + result += F(" (Sleep)"); + break; + case kGoodweatherCmdTurbo: + result += F(" (Turbo)"); + break; + case kGoodweatherCmdLight: + result += F(" (Light)"); + break; + default: + result += F(" (UNKNOWN)"); + } return result; } diff --git a/src/ir_Goodweather.h b/src/ir_Goodweather.h index b3974daa7..aac07a85a 100644 --- a/src/ir_Goodweather.h +++ b/src/ir_Goodweather.h @@ -38,7 +38,9 @@ const uint8_t kGoodweatherBitLight = 8; const uint64_t kGoodweatherLightMask = 0x1ULL << kGoodweatherBitLight; const uint8_t kGoodweatherBitTurbo = kGoodweatherBitLight + 3; // 11 const uint64_t kGoodweatherTurboMask = 0x1ULL << kGoodweatherBitTurbo; -const uint8_t kGoodweatherBitSleep = kGoodweatherBitTurbo + 13; // 24 +const uint8_t kGoodweatherBitCommand = kGoodweatherBitTurbo + 5; // 16 +const uint64_t kGoodweatherCommandMask = 0xFULL << kGoodweatherBitCommand; +const uint8_t kGoodweatherBitSleep = kGoodweatherBitCommand + 8; // 24 const uint64_t kGoodweatherSleepMask = 0x1ULL << kGoodweatherBitSleep; const uint8_t kGoodweatherBitPower = kGoodweatherBitSleep + 1; // 25 const uint64_t kGoodweatherPowerMask = 0x1ULL << kGoodweatherBitPower; @@ -48,7 +50,7 @@ const uint8_t kGoodweatherBitFan = kGoodweatherBitSwing + 3; // 29 const uint64_t kGoodweatherFanMask = 0x3ULL << kGoodweatherBitFan; const uint8_t kGoodweatherBitTemp = kGoodweatherBitFan + 3; // 32 const uint64_t kGoodweatherTempMask = 0xFULL << kGoodweatherBitTemp; -const uint8_t kGoodweatherBitMode = kGoodweatherBitFan + 8; // 37 +const uint8_t kGoodweatherBitMode = kGoodweatherBitTemp + 5; // 37 const uint64_t kGoodweatherModeMask = 0x7ULL << kGoodweatherBitMode; // Modes @@ -65,10 +67,22 @@ const uint8_t kGoodweatherFanAuto = 0b00; const uint8_t kGoodweatherFanHigh = 0b01; const uint8_t kGoodweatherFanMed = 0b10; const uint8_t kGoodweatherFanLow = 0b11; - // Temperature const uint8_t kGoodweatherTempMin = 16; // Celsius const uint8_t kGoodweatherTempMax = 31; // Celsius +// Commands +const uint8_t kGoodweatherCmdPower = 0x00; +const uint8_t kGoodweatherCmdMode = 0x01; +const uint8_t kGoodweatherCmdUpTemp = 0x02; +const uint8_t kGoodweatherCmdDownTemp = 0x03; +const uint8_t kGoodweatherCmdSwing = 0x04; +const uint8_t kGoodweatherCmdFan = 0x05; +const uint8_t kGoodweatherCmdTimer = 0x06; +const uint8_t kGoodweatherCmdAirFlow = 0x07; +const uint8_t kGoodweatherCmdHold = 0x08; +const uint8_t kGoodweatherCmdSleep = 0x09; +const uint8_t kGoodweatherCmdTurbo = 0x0A; +const uint8_t kGoodweatherCmdLight = 0x0B; // Classes @@ -99,6 +113,8 @@ class IRGoodweatherAc { bool getTurbo(void); void setLight(const bool toggle); bool getLight(void); + void setCommand(const uint8_t cmd); + uint8_t getCommand(void); uint64_t getRaw(void); void setRaw(const uint64_t state); uint8_t convertMode(const stdAc::opmode_t mode); diff --git a/test/ir_Goodweather_test.cpp b/test/ir_Goodweather_test.cpp index 68036259e..9826f873a 100644 --- a/test/ir_Goodweather_test.cpp +++ b/test/ir_Goodweather_test.cpp @@ -86,7 +86,7 @@ TEST(TestDecodeGoodweather, RealExampleDecode) { irsend.reset(); // Raw Goodweather 48-bit message. - uint16_t rawData_1[197] = { + uint16_t rawData_4624AB[197] = { 6828, 6828, 732, 1780, 652, 1830, 652, 1806, 678, 1830, 652, 1806, 678, 1830, 652, 1830, 652, 1834, 706, 518, 734, 508, 734, 514, 734, 510, 732, 510, 732, 510, 732, 510, 732, 514, 732, 1776, 706, 1780, 628, 1854, 628, @@ -102,7 +102,7 @@ TEST(TestDecodeGoodweather, RealExampleDecode) { 582, 728, 1736, 712, 558, 710, 1750, 710, 558, 710, 510, 738, 1748, 738, 508, 736, 1772, 684, 534, 736, 1772, 704, 518, 738, 1772, 660, 1824, 678, 6770, 684}; // COOLIX 4624AB - irsend.sendRaw(rawData_1, 197, 38000); + irsend.sendRaw(rawData_4624AB, 197, 38000); irsend.makeDecodeResult(); ASSERT_TRUE(irrecv.decode(&irsend.capture)); EXPECT_EQ(decode_type_t::GOODWEATHER, irsend.capture.decode_type); @@ -114,10 +114,109 @@ TEST(TestDecodeGoodweather, RealExampleDecode) { ac.setRaw(irsend.capture.value); EXPECT_EQ( "Power: On, Mode: 1 (COOL), Temp: 20C, Fan: 3 (LOW), " - "Turbo: -, Light: -, Sleep: -, Swing: 0 (Fast)", + "Turbo: -, Light: -, Sleep: -, Swing: 0 (Fast), Command: 0 (Power)", ac.toString()); - uint16_t rawData_2[197] = { +uint16_t rawData_FAD2BE31[197] = { + 6142, 7348, 570, 1612, 638, 1562, 620, 1584, 670, 1538, 566, 1638, 564, + 1610, 618, 1582, 642, 1542, 638, 498, 622, 518, 618, 496, 622, 518, 596, + 522, 596, 542, 618, 498, 618, 520, 594, 1590, 614, 1586, 618, 1588, 640, + 1592, 538, 1614, 612, 1584, 620, 1584, 616, 1592, 564, 546, 596, 540, 596, + 520, 620, 520, 594, 524, 618, 522, 650, 466, 616, 522, 670, 1532, 618, 1568, + 590, 1610, 618, 1612, 640, 1530, 594, 1586, 618, 1616, 590, 1586, 640, 472, + 618, 520, 672, 446, 618, 520, 646, 474, 616, 520, 622, 500, 614, 518, 624, + 1612, 560, 1616, 590, 1584, 620, 520, 646, 1540, 612, 518, 622, 516, 596, + 1586, 618, 518, 622, 498, 616, 520, 622, 1582, 616, 498, 620, 1582, 622, + 1586, 586, 528, 616, 1582, 622, 498, 616, 518, 624, 1582, 614, 1592, 568, + 544, 620, 1580, 648, 1542, 610, 520, 622, 1586, 666, 1536, 592, 518, 600, + 542, 594, 1592, 590, 544, 620, 498, 616, 518, 622, 1580, 620, 496, 620, + 1586, 618, 502, 610, 1584, 620, 518, 672, 446, 620, 1612, 592, 504, 608, + 1586, 618, 518, 646, 1540, 612, 520, 600, 1604, 622, 1582, 596, 7382, 566}; + // UNKNOWN FAD2BE31 + + irsend.reset(); + irsend.sendRaw(rawData_FAD2BE31, 197, 38000); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(decode_type_t::GOODWEATHER, irsend.capture.decode_type); + EXPECT_EQ(kGoodweatherBits, irsend.capture.bits); + EXPECT_EQ(0xD52668000000, irsend.capture.value); + EXPECT_EQ(0x0, irsend.capture.address); + EXPECT_EQ(0x0, irsend.capture.command); + EXPECT_FALSE(irsend.capture.repeat); + ac.setRaw(irsend.capture.value); + EXPECT_EQ( + "Power: On, Mode: 1 (COOL), Temp: 22C, Fan: 3 (LOW), Turbo: -, " + "Light: -, Sleep: -, Swing: 2 (Off), Command: 0 (Power)", + ac.toString()); + + uint16_t rawData_5453D3AD[197] = { + 6190, 7298, 668, 1542, 614, 1590, 590, 1582, 620, 1584, 566, 1624, 632, + 1592, 616, 1588, 638, 1538, 594, 520, 620, 520, 594, 522, 620, 520, 586, + 530, 618, 520, 640, 480, 616, 520, 642, 1544, 612, 1588, 622, 1576, 668, + 1540, 564, 1640, 592, 1582, 646, 1558, 670, 1536, 594, 518, 622, 520, 594, + 522, 620, 520, 566, 552, 618, 520, 614, 504, 618, 518, 666, 1520, 610, + 1586, 618, 1612, 636, 1568, 564, 1590, 614, 1584, 620, 1582, 666, 1542, + 614, 526, 590, 520, 596, 520, 622, 520, 566, 550, 620, 520, 588, 530, 618, + 520, 668, 1536, 594, 520, 646, 1558, 668, 452, 616, 1584, 642, 498, 566, + 550, 618, 1582, 668, 454, 612, 1582, 646, 496, 594, 1614, 666, 450, 662, + 1536, 584, 1600, 612, 520, 642, 1590, 588, 502, 616, 520, 588, 1600, 612, + 1586, 616, 520, 612, 1574, 610, 1584, 644, 496, 564, 1620, 636, 1562, 640, + 524, 560, 530, 616, 1582, 644, 498, 620, 494, 670, 472, 622, 1558, 616, + 520, 642, 1564, 594, 518, 646, 1558, 668, 454, 638, 494, 668, 1538, 616, + 498, 642, 1558, 670, 454, 636, 1560, 642, 496, 614, 1592, 616, 1584, 620, + 7350, 668}; // UNKNOWN 5453D3AD + + irsend.reset(); + irsend.sendRaw(rawData_5453D3AD, 197, 38000); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(decode_type_t::GOODWEATHER, irsend.capture.decode_type); + EXPECT_EQ(kGoodweatherBits, irsend.capture.bits); + EXPECT_EQ(0xD5266A000000, irsend.capture.value); + EXPECT_EQ(0x0, irsend.capture.address); + EXPECT_EQ(0x0, irsend.capture.command); + EXPECT_FALSE(irsend.capture.repeat); + ac.setRaw(irsend.capture.value); + EXPECT_EQ( + "Power: On, Mode: 1 (COOL), Temp: 22C, Fan: 3 (LOW), Turbo: -, " + "Light: -, Sleep: -, Swing: 2 (Off), Command: 0 (Power)", + ac.toString()); + + uint16_t rawData_B2354FBB[197] = { + 6192, 7298, 592, 1616, 618, 1584, 620, 1558, 668, 1520, 636, 1562, 642, + 1584, 590, 1614, 542, 1634, 622, 494, 668, 454, 638, 494, 670, 454, 638, + 492, 646, 480, 636, 494, 672, 470, 622, 1560, 642, 1556, 672, 1534, 614, + 1572, 636, 1584, 622, 1582, 644, 1534, 596, 1586, 642, 494, 666, 454, 640, + 494, 668, 452, 640, 494, 668, 454, 638, 494, 670, 470, 620, 1562, 666, + 470, 644, 1546, 634, 1584, 618, 1584, 644, 1534, 640, 1548, 636, 1560, + 644, 520, 542, 1618, 638, 494, 670, 454, 636, 496, 670, 454, 634, 494, + 672, 470, 620, 1564, 640, 496, 642, 1562, 616, 520, 622, 1558, 668, 450, + 640, 494, 694, 1536, 566, 524, 644, 1558, 666, 456, 638, 1558, 644, 520, + 572, 1588, 638, 1558, 644, 494, 590, 1596, 638, 1584, 620, 1584, 644, 454, + 638, 1556, 672, 472, 620, 1562, 640, 1558, 646, 494, 644, 470, 646, 496, + 566, 1618, 638, 494, 668, 1534, 646, 468, 674, 468, 568, 550, 670, 1530, + 670, 454, 638, 1560, 644, 494, 622, 1582, 620, 494, 646, 496, 620, 1560, + 644, 494, 668, 1522, 610, 518, 674, 1532, 614, 504, 640, 1584, 642, 1562, + 616, 7332, 594}; // UNKNOWN B2354FBB + + irsend.reset(); + irsend.sendRaw(rawData_B2354FBB, 197, 38000); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(decode_type_t::GOODWEATHER, irsend.capture.decode_type); + EXPECT_EQ(kGoodweatherBits, irsend.capture.bits); + EXPECT_EQ(0xD5286A020000, irsend.capture.value); + EXPECT_EQ(0x0, irsend.capture.address); + EXPECT_EQ(0x0, irsend.capture.command); + EXPECT_FALSE(irsend.capture.repeat); + ac.setRaw(irsend.capture.value); + EXPECT_EQ( + "Power: On, Mode: 1 (COOL), Temp: 24C, Fan: 3 (LOW), Turbo: -, " + "Light: -, Sleep: -, Swing: 2 (Off), Command: 2 (Temp Up)", + ac.toString()); + + uint16_t rawData_71DD9105[197] = { 6190, 7296, 696, 1496, 634, 1562, 642, 1582, 640, 1564, 564, 1598, 638, 1558, 646, 1560, 588, 1616, 618, 520, 620, 494, 622, 494, 646, 494, 620, 496, 644, 494, 590, 528, 642, 494, 642, 1544, 638, 1584, 618, 1564, 804, @@ -135,7 +234,7 @@ TEST(TestDecodeGoodweather, RealExampleDecode) { 612, 7382, 594}; // UNKNOWN 71DD9105 irsend.reset(); - irsend.sendRaw(rawData_2, 197, 38000); + irsend.sendRaw(rawData_71DD9105, 197, 38000); irsend.makeDecodeResult(); ASSERT_TRUE(irrecv.decode(&irsend.capture)); EXPECT_EQ(decode_type_t::GOODWEATHER, irsend.capture.decode_type); @@ -147,6 +246,39 @@ TEST(TestDecodeGoodweather, RealExampleDecode) { ac.setRaw(irsend.capture.value); EXPECT_EQ( "Power: On, Mode: 1 (COOL), Temp: 23C, Fan: 3 (LOW), " - "Turbo: -, Light: -, Sleep: -, Swing: 2 (Off)", + "Turbo: -, Light: -, Sleep: -, Swing: 2 (Off), Command: 3 (Temp Down)", + ac.toString()); + + uint16_t rawData_C4F9E573[199] = { + 6186, 7296, 648, 1558, 670, 1542, 612, 1584, 618, 1560, 668, 1534, 622, + 1566, 638, 1558, 646, 1584, 590, 506, 640, 492, 642, 480, 640, 494, 644, + 478, 640, 494, 668, 454, 614, 516, 648, 1560, 566, 1638, 618, 1584, 620, + 1556, 672, 1534, 620, 1564, 640, 1584, 618, 1586, 564, 528, 670, 468, 640, + 478, 642, 494, 644, 478, 640, 492, 670, 454, 638, 494, 670, 1560, 542, + 1636, 644, 468, 670, 1534, 620, 1586, 618, 1558, 646, 1556, 670, 1534, + 622, 492, 648, 494, 620, 1562, 642, 496, 642, 476, 642, 494, 696, 426, + 642, 492, 646, 1558, 568, 548, 644, 494, 642, 1564, 618, 1584, 620, 494, + 568, 548, 644, 1558, 644, 480, 636, 1584, 620, 1584, 644, 456, 634, 494, + 672, 1560, 540, 1638, 618, 494, 728, 1476, 592, 524, 646, 492, 616, 1572, + 638, 1560, 644, 492, 668, 1520, 638, 1562, 642, 494, 588, 1598, 638, 1560, + 642, 494, 622, 498, 642, 1556, 646, 478, 638, 492, 646, 494, 620, 1584, + 618, 522, 616, 1546, 612, 516, 648, 1556, 644, 476, 668, 468, 670, 1534, + 620, 494, 648, 1556, 670, 452, 640, 1558, 644, 496, 646, 1536, 616, 1582, + 646, 7326, 616, 41598, 230}; // UNKNOWN C4F9E573 + + irsend.reset(); + irsend.sendRaw(rawData_C4F9E573, 199, 38000); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(decode_type_t::GOODWEATHER, irsend.capture.decode_type); + EXPECT_EQ(kGoodweatherBits, irsend.capture.bits); + EXPECT_EQ(0xD52666040000, irsend.capture.value); + EXPECT_EQ(0x0, irsend.capture.address); + EXPECT_EQ(0x0, irsend.capture.command); + EXPECT_FALSE(irsend.capture.repeat); + ac.setRaw(irsend.capture.value); + EXPECT_EQ( + "Power: On, Mode: 1 (COOL), Temp: 22C, Fan: 3 (LOW), Turbo: -, Light: -, " + "Sleep: -, Swing: 1 (Slow), Command: 4 (Swing)", ac.toString()); } From 6d459d669faa6f49ebcb19a3ea8ee02d485d486c Mon Sep 17 00:00:00 2001 From: David Date: Mon, 20 May 2019 13:55:04 +1000 Subject: [PATCH 4/4] Improve Goodweather Support. - Fix power detection. - Add unit tests for most of the class methods. - Add common A/C support & update IRMQTTServer. For #697 --- examples/IRMQTTServer/IRMQTTServer.ino | 8 + src/IRac.cpp | 40 +++++ src/IRac.h | 10 ++ src/ir_Goodweather.cpp | 91 +++++++--- src/ir_Goodweather.h | 3 + test/IRac_test.cpp | 28 +++ test/ir_Goodweather_test.cpp | 229 ++++++++++++++++++++++++- test/ir_Tcl_test.cpp | 1 - 8 files changed, 385 insertions(+), 25 deletions(-) diff --git a/examples/IRMQTTServer/IRMQTTServer.ino b/examples/IRMQTTServer/IRMQTTServer.ino index b6776e1f6..2fbea02a5 100644 --- a/examples/IRMQTTServer/IRMQTTServer.ino +++ b/examples/IRMQTTServer/IRMQTTServer.ino @@ -3124,6 +3124,14 @@ bool decodeCommonAc(const decode_results *decode) { break; } #endif // DECODE_FUJITSU_AC +#if DECODE_GOODWEATHER + case decode_type_t::GOODWEATHER: { + IRGoodweatherAc ac(IR_LED); + ac.setRaw(decode->value); // Uses value instead of state. + state = ac.toCommon(); + break; + } +#endif // DECODE_GOODWEATHER #if DECODE_GREE case decode_type_t::GREE: { IRGreeAC ac(IR_LED); diff --git a/src/IRac.cpp b/src/IRac.cpp index 1b1f61a00..cc88f9be5 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -58,6 +58,9 @@ bool IRac::isProtocolSupported(const decode_type_t protocol) { #if SEND_FUJITSU_AC case decode_type_t::FUJITSU_AC: #endif +#if SEND_GOODWEATHER + case decode_type_t::GOODWEATHER: +#endif #if SEND_GREE case decode_type_t::GREE: #endif @@ -283,6 +286,34 @@ void IRac::fujitsu(IRFujitsuAC *ac, const fujitsu_ac_remote_model_t model, } #endif // SEND_FUJITSU_AC +#if SEND_GOODWEATHER +void IRac::goodweather(IRGoodweatherAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const bool turbo, const bool light, + const int16_t sleep) { + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwing(swingv == stdAc::swingv_t::kOff ? kGoodweatherSwingOff + : kGoodweatherSwingSlow); + ac->setTurbo(turbo); + ac->setLight(light); + // No Clean setting available. + ac->setSleep(sleep >= 0); // Sleep on this A/C is either on or off. + // No Horizontal Swing setting available. + // No Econo setting available. + // No Filter setting available. + // No Beep setting available. + // No Quiet setting available. + // No Clock setting available. + ac->setPower(on); + ac->send(); +} +#endif // SEND_GOODWEATHER + #if SEND_GREE void IRac::gree(IRGreeAC *ac, const bool on, const stdAc::opmode_t mode, const float degrees, @@ -831,6 +862,15 @@ bool IRac::sendAc(const decode_type_t vendor, const int16_t model, break; } #endif // SEND_FUJITSU_AC +#if SEND_GOODWEATHER + case GOODWEATHER: + { + IRGoodweatherAc ac(_pin); + ac.begin(); + goodweather(&ac, on, mode, degC, fan, swingv, turbo, light, sleep); + break; + } +#endif // SEND_GOODWEATHER #if SEND_GREE case GREE: { diff --git a/src/IRac.h b/src/IRac.h index 4b3a64ccb..b507ae458 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -14,6 +14,7 @@ #include "ir_Coolix.h" #include "ir_Daikin.h" #include "ir_Fujitsu.h" +#include "ir_Goodweather.h" #include "ir_Gree.h" #include "ir_Haier.h" #include "ir_Hitachi.h" @@ -106,6 +107,15 @@ void daikin216(IRDaikin216 *ac, const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, const bool quiet); #endif // SEND_FUJITSU_AC +#if SEND_GOODWEATHER + void goodweather(IRGoodweatherAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, + const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const bool turbo, const bool light, + const int16_t sleep = -1); +#endif // SEND_GOODWEATHER #if SEND_GREE void gree(IRGreeAC *ac, const bool on, const stdAc::opmode_t mode, const float degrees, diff --git a/src/ir_Goodweather.cpp b/src/ir_Goodweather.cpp index d897dac80..015adeb87 100644 --- a/src/ir_Goodweather.cpp +++ b/src/ir_Goodweather.cpp @@ -74,23 +74,27 @@ uint64_t IRGoodweatherAc::getRaw(void) { return remote; } void IRGoodweatherAc::setRaw(const uint64_t state) { remote = state; } -void IRGoodweatherAc::on(void) { remote |= kGoodweatherBitPower; } +void IRGoodweatherAc::on(void) { this->setPower(true); } -void IRGoodweatherAc::off(void) { remote &= ~kGoodweatherBitPower; } +void IRGoodweatherAc::off(void) { this->setPower(false); } void IRGoodweatherAc::setPower(const bool on) { + this->setCommand(kGoodweatherCmdPower); if (on) - this->on(); + remote |= kGoodweatherPowerMask; else - this->off(); + remote &= ~kGoodweatherPowerMask; } -bool IRGoodweatherAc::getPower(void) { return remote && kGoodweatherBitPower; } +bool IRGoodweatherAc::getPower(void) { return remote & kGoodweatherPowerMask; } // Set the temp. in deg C void IRGoodweatherAc::setTemp(const uint8_t temp) { - uint8_t new_temp = std::max((uint8_t)kGoodweatherTempMin, temp); - new_temp = std::min((uint8_t)kGoodweatherTempMax, new_temp); + uint8_t new_temp = std::max(kGoodweatherTempMin, temp); + new_temp = std::min(kGoodweatherTempMax, new_temp); + if (new_temp > this->getTemp()) this->setCommand(kGoodweatherCmdUpTemp); + if (new_temp < this->getTemp()) this->setCommand(kGoodweatherCmdDownTemp); + remote &= ~kGoodweatherTempMask; remote |= (uint64_t)(new_temp - kGoodweatherTempMin) << kGoodweatherBitTemp; } @@ -107,6 +111,7 @@ void IRGoodweatherAc::setFan(const uint8_t speed) { case kGoodweatherFanLow: case kGoodweatherFanMed: case kGoodweatherFanHigh: + this->setCommand(kGoodweatherCmdFan); remote &= ~kGoodweatherFanMask; remote |= ((uint64_t)speed << kGoodweatherBitFan); break; @@ -126,6 +131,7 @@ void IRGoodweatherAc::setMode(const uint8_t mode) { case kGoodweatherCool: case kGoodweatherFan: case kGoodweatherHeat: + this->setCommand(kGoodweatherCmdMode); remote &= ~kGoodweatherModeMask; remote |= (uint64_t)mode << kGoodweatherBitMode; break; @@ -140,6 +146,7 @@ uint8_t IRGoodweatherAc::getMode() { } void IRGoodweatherAc::setLight(const bool toggle) { + this->setCommand(kGoodweatherCmdLight); if (toggle) remote |= kGoodweatherLightMask; else @@ -149,6 +156,7 @@ void IRGoodweatherAc::setLight(const bool toggle) { bool IRGoodweatherAc::getLight() { return remote & kGoodweatherLightMask; } void IRGoodweatherAc::setSleep(const bool toggle) { + this->setCommand(kGoodweatherCmdSleep); if (toggle) remote |= kGoodweatherSleepMask; else @@ -158,6 +166,7 @@ void IRGoodweatherAc::setSleep(const bool toggle) { bool IRGoodweatherAc::getSleep() { return remote & kGoodweatherSleepMask; } void IRGoodweatherAc::setTurbo(const bool toggle) { + this->setCommand(kGoodweatherCmdTurbo); if (toggle) remote |= kGoodweatherTurboMask; else @@ -171,6 +180,7 @@ void IRGoodweatherAc::setSwing(const uint8_t speed) { case kGoodweatherSwingOff: case kGoodweatherSwingSlow: case kGoodweatherSwingFast: + this->setCommand(kGoodweatherCmdSwing); remote &= ~kGoodweatherSwingMask; remote |= ((uint64_t)speed << kGoodweatherBitSwing); break; @@ -242,6 +252,53 @@ uint8_t IRGoodweatherAc::convertSwingV(const stdAc::swingv_t swingv) { } } +// Convert a native mode to it's common equivalent. +stdAc::opmode_t IRGoodweatherAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kGoodweatherCool: return stdAc::opmode_t::kCool; + case kGoodweatherHeat: return stdAc::opmode_t::kHeat; + case kGoodweatherDry: return stdAc::opmode_t::kDry; + case kGoodweatherFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +// Convert a native fan speed to it's common equivalent. +stdAc::fanspeed_t IRGoodweatherAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kGoodweatherFanHigh: return stdAc::fanspeed_t::kMax; + case kGoodweatherFanMed: return stdAc::fanspeed_t::kMedium; + case kGoodweatherFanLow: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +// Convert the A/C state to it's common equivalent. +stdAc::state_t IRGoodweatherAc::toCommon(void) { + stdAc::state_t result; + result.protocol = decode_type_t::GOODWEATHER; + 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->getSwing() == kGoodweatherSwingOff ? + stdAc::swingv_t::kOff : stdAc::swingv_t::kAuto; + result.turbo = this->getTurbo(); + result.light = this->getLight(); + result.sleep = this->getSleep() ? 0: -1; + // Not supported. + result.model = -1; + result.swingh = stdAc::swingh_t::kOff; + result.quiet = false; + result.econo = false; + result.filter = false; + result.clean = false; + result.beep = false; + result.clock = -1; + return result; +} + // Convert the internal state into a human readable string. #ifdef ARDUINO String IRGoodweatherAc::toString() { @@ -252,10 +309,7 @@ std::string IRGoodweatherAc::toString() { #endif // ARDUINO result.reserve(150); // Reserve some heap for the string to reduce fragging. result += F("Power: "); - if (this->getPower()) - result += F("On"); - else - return result += F("Off"); // If it's off, there is no other info. + result += this->getPower() ? F("On") : F("Off"); result += F(", Mode: "); result += uint64ToString(this->getMode()); switch (this->getMode()) { @@ -296,20 +350,11 @@ std::string IRGoodweatherAc::toString() { break; } result += F(", Turbo: "); - if (this->getTurbo()) - result += F("Toggle"); - else - result += F("-"); + result += this->getTurbo() ? F("Toggle") : F("-"); result += F(", Light: "); - if (this->getLight()) - result += F("Toggle"); - else - result += F("-"); + result += this->getLight() ? F("Toggle") : F("-"); result += F(", Sleep: "); - if (this->getSleep()) - result += F("Toggle"); - else - result += F("-"); + result += this->getSleep() ? F("Toggle") : F("-"); result += F(", Swing: "); result += uint64ToString(this->getSwing()); switch (this->getSwing()) { diff --git a/src/ir_Goodweather.h b/src/ir_Goodweather.h index aac07a85a..03a70e3a2 100644 --- a/src/ir_Goodweather.h +++ b/src/ir_Goodweather.h @@ -120,6 +120,9 @@ class IRGoodweatherAc { uint8_t convertMode(const stdAc::opmode_t mode); uint8_t convertFan(const stdAc::fanspeed_t speed); uint8_t convertSwingV(const stdAc::swingv_t swingv); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void); #ifdef ARDUINO String toString(); #else diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index 0702bd59f..33c143e23 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -3,6 +3,7 @@ #include "ir_Argo.h" #include "ir_Daikin.h" #include "ir_Fujitsu.h" +#include "ir_Goodweather.h" #include "ir_Gree.h" #include "ir_Haier.h" #include "ir_Hitachi.h" @@ -211,6 +212,33 @@ TEST(TestIRac, Fujitsu) { ASSERT_EQ(expected, ac.toString()); } +TEST(TestIRac, Goodweather) { + IRGoodweatherAc ac(0); + IRac irac(0); + IRrecv capture(0); + char expected[] = + "Power: On, Mode: 1 (COOL), Temp: 19C, Fan: 2 (MED), Turbo: Toggle, " + "Light: Toggle, Sleep: Toggle, Swing: 1 (Slow), Command: 0 (Power)"; + + ac.begin(); + irac.goodweather(&ac, + true, // Power + stdAc::opmode_t::kCool, // Mode + 19, // Celsius + stdAc::fanspeed_t::kMedium, // Fan speed + stdAc::swingv_t::kHigh, // Veritcal swing + true, // Turbo + true, // Light + 8 * 60 + 0); // Sleep time + ASSERT_EQ(expected, ac.toString()); + ac._irsend.makeDecodeResult(); + EXPECT_TRUE(capture.decode(&ac._irsend.capture)); + ASSERT_EQ(GOODWEATHER, ac._irsend.capture.decode_type); + ASSERT_EQ(kGoodweatherBits, ac._irsend.capture.bits); + ac.setRaw(ac._irsend.capture.value); + ASSERT_EQ(expected, ac.toString()); +} + TEST(TestIRac, Gree) { IRGreeAC ac(0); IRac irac(0); diff --git a/test/ir_Goodweather_test.cpp b/test/ir_Goodweather_test.cpp index 9826f873a..5185eb4b1 100644 --- a/test/ir_Goodweather_test.cpp +++ b/test/ir_Goodweather_test.cpp @@ -146,7 +146,7 @@ uint16_t rawData_FAD2BE31[197] = { EXPECT_FALSE(irsend.capture.repeat); ac.setRaw(irsend.capture.value); EXPECT_EQ( - "Power: On, Mode: 1 (COOL), Temp: 22C, Fan: 3 (LOW), Turbo: -, " + "Power: Off, Mode: 1 (COOL), Temp: 22C, Fan: 3 (LOW), Turbo: -, " "Light: -, Sleep: -, Swing: 2 (Off), Command: 0 (Power)", ac.toString()); @@ -282,3 +282,230 @@ uint16_t rawData_FAD2BE31[197] = { "Sleep: -, Swing: 1 (Slow), Command: 4 (Swing)", ac.toString()); } + + +TEST(TestGoodweatherAcClass, toCommon) { + IRGoodweatherAc ac(0); + ac.setPower(true); + ac.setMode(kGoodweatherCool); + ac.setTemp(20); + ac.setFan(kGoodweatherFanHigh); + ac.setSwing(kGoodweatherSwingFast); + ac.setTurbo(true); + ac.setLight(true); + ac.setSleep(true); + // Now test it. + ASSERT_EQ(decode_type_t::GOODWEATHER, ac.toCommon().protocol); + ASSERT_TRUE(ac.toCommon().power); + ASSERT_TRUE(ac.toCommon().celsius); + ASSERT_EQ(20, ac.toCommon().degrees); + ASSERT_EQ(stdAc::opmode_t::kCool, ac.toCommon().mode); + ASSERT_EQ(stdAc::fanspeed_t::kMax, ac.toCommon().fanspeed); + ASSERT_EQ(stdAc::swingv_t::kAuto, ac.toCommon().swingv); + ASSERT_TRUE(ac.toCommon().turbo); + ASSERT_TRUE(ac.toCommon().light); + ASSERT_EQ(0, ac.toCommon().sleep); + // Unsupported. + ASSERT_EQ(-1, ac.toCommon().model); + ASSERT_EQ(stdAc::swingh_t::kOff, ac.toCommon().swingh); + ASSERT_FALSE(ac.toCommon().quiet); + ASSERT_FALSE(ac.toCommon().econo); + ASSERT_FALSE(ac.toCommon().filter); + ASSERT_FALSE(ac.toCommon().clean); + ASSERT_FALSE(ac.toCommon().beep); + ASSERT_EQ(-1, ac.toCommon().clock); +} + + +TEST(TestGoodweatherAcClass, Temperature) { + IRGoodweatherAc ac(0); + + ac.setTemp(kGoodweatherTempMin); + EXPECT_EQ(kGoodweatherTempMin, ac.getTemp()); + + ac.setTemp(kGoodweatherTempMin + 1); + EXPECT_EQ(kGoodweatherTempMin + 1, ac.getTemp()); + + ac.setTemp(kGoodweatherTempMax); + EXPECT_EQ(kGoodweatherTempMax, ac.getTemp()); + + ac.setTemp(kGoodweatherTempMin - 1); + EXPECT_EQ(kGoodweatherTempMin, ac.getTemp()); + + ac.setTemp(kGoodweatherTempMax + 1); + EXPECT_EQ(kGoodweatherTempMax, ac.getTemp()); + + ac.setTemp(23); + EXPECT_EQ(23, ac.getTemp()); + + ac.setTemp(27); + EXPECT_EQ(27, ac.getTemp()); + + ac.setTemp(22); + EXPECT_EQ(22, ac.getTemp()); + + ac.setTemp(25); + EXPECT_EQ(25, ac.getTemp()); + + ac.setTemp(0); + EXPECT_EQ(kGoodweatherTempMin, ac.getTemp()); + + ac.setTemp(255); + EXPECT_EQ(kGoodweatherTempMax, ac.getTemp()); +} + +TEST(TestGoodweatherAcClass, OperatingMode) { + IRGoodweatherAc ac(0); + ac.begin(); + + ac.setMode(kGoodweatherAuto); + EXPECT_EQ(kGoodweatherAuto, ac.getMode()); + + ac.setMode(kGoodweatherCool); + EXPECT_EQ(kGoodweatherCool, ac.getMode()); + + ac.setMode(kGoodweatherHeat); + EXPECT_EQ(kGoodweatherHeat, ac.getMode()); + + ac.setMode(kGoodweatherFan); // Should set fan speed to High. + EXPECT_EQ(kGoodweatherFan, ac.getMode()); + + ac.setMode(kGoodweatherDry); + EXPECT_EQ(kGoodweatherDry, ac.getMode()); + + ac.setMode(kGoodweatherHeat + 1); + EXPECT_EQ(kGoodweatherAuto, ac.getMode()); + + ac.setMode(kGoodweatherCool); + EXPECT_EQ(kGoodweatherCool, ac.getMode()); + + ac.setMode(kGoodweatherAuto - 1); + EXPECT_EQ(kGoodweatherAuto, ac.getMode()); + + ac.setMode(kGoodweatherCool); + ac.setMode(255); + EXPECT_EQ(kGoodweatherAuto, ac.getMode()); + + ac.setMode(kGoodweatherCool); + ac.setMode(0); + EXPECT_EQ(kGoodweatherAuto, ac.getMode()); +} + +TEST(TestGoodweatherAcClass, Power) { + IRGoodweatherAc ac(0); + ac.begin(); + + ac.setPower(true); + EXPECT_TRUE(ac.getPower()); + + ac.setPower(false); + EXPECT_EQ(false, ac.getPower()); + + ac.setPower(true); + EXPECT_TRUE(ac.getPower()); + + ac.off(); + EXPECT_EQ(false, ac.getPower()); + + ac.on(); + EXPECT_TRUE(ac.getPower()); +} + +TEST(TestGoodweatherAcClass, Light) { + IRGoodweatherAc ac(0); + ac.begin(); + + ac.setLight(true); + EXPECT_TRUE(ac.getLight()); + ac.setLight(false); + EXPECT_EQ(false, ac.getLight()); + ac.setLight(true); + EXPECT_TRUE(ac.getLight()); +} + +TEST(TestGoodweatherAcClass, Turbo) { + IRGoodweatherAc ac(0); + ac.begin(); + + ac.setTurbo(true); + EXPECT_TRUE(ac.getTurbo()); + ac.setTurbo(false); + EXPECT_EQ(false, ac.getTurbo()); + ac.setTurbo(true); + EXPECT_TRUE(ac.getTurbo()); +} + +TEST(TestGoodweatherAcClass, Sleep) { + IRGoodweatherAc ac(0); + ac.begin(); + + ac.setSleep(true); + EXPECT_TRUE(ac.getSleep()); + ac.setSleep(false); + EXPECT_EQ(false, ac.getSleep()); + ac.setSleep(true); + EXPECT_TRUE(ac.getSleep()); +} + +TEST(TestGoodweatherAcClass, FanSpeed) { + IRGoodweatherAc ac(0); + ac.begin(); + + // Unexpected value should default to Auto. + ac.setFan(255); + EXPECT_EQ(kGoodweatherFanAuto, ac.getFan()); + + ac.setFan(kGoodweatherFanLow); + EXPECT_EQ(kGoodweatherFanLow, ac.getFan()); + ac.setFan(kGoodweatherFanMed); + EXPECT_EQ(kGoodweatherFanMed, ac.getFan()); + ac.setFan(kGoodweatherFanHigh); + EXPECT_EQ(kGoodweatherFanHigh, ac.getFan()); + ac.setFan(kGoodweatherFanAuto); + EXPECT_EQ(kGoodweatherFanAuto, ac.getFan()); + + // Beyond Low should default to Auto. + ac.setFan(kGoodweatherFanLow + 1); + EXPECT_EQ(kGoodweatherFanAuto, ac.getFan()); +} + + +TEST(TestGoodweatherAcClass, SwingSpeed) { + IRGoodweatherAc ac(0); + ac.begin(); + + // Unexpected value should default to Off. + ac.setSwing(255); + EXPECT_EQ(kGoodweatherSwingOff, ac.getSwing()); + + ac.setSwing(kGoodweatherSwingSlow); + EXPECT_EQ(kGoodweatherSwingSlow, ac.getSwing()); + ac.setSwing(kGoodweatherSwingOff); + EXPECT_EQ(kGoodweatherSwingOff, ac.getSwing()); + ac.setSwing(kGoodweatherSwingFast); + EXPECT_EQ(kGoodweatherSwingFast, ac.getSwing()); + + // Beyond Off should default to Off. + ac.setSwing(kGoodweatherSwingOff + 1); + EXPECT_EQ(kGoodweatherSwingOff, ac.getSwing()); +} + +TEST(TestGoodweatherAcClass, Command) { + IRGoodweatherAc ac(0); + ac.begin(); + + ac.setCommand(kGoodweatherCmdMode); + EXPECT_EQ(kGoodweatherCmdMode, ac.getCommand()); + // Unexpected value should not change anything. + ac.setCommand(255); + EXPECT_EQ(kGoodweatherCmdMode, ac.getCommand()); + + ac.setCommand(kGoodweatherCmdPower); + EXPECT_EQ(kGoodweatherCmdPower, ac.getCommand()); + ac.setCommand(kGoodweatherCmdLight); + EXPECT_EQ(kGoodweatherCmdLight, ac.getCommand()); + + // Beyond Light should be ignored. + ac.setCommand(kGoodweatherCmdLight + 1); + EXPECT_EQ(kGoodweatherCmdLight, ac.getCommand()); +} diff --git a/test/ir_Tcl_test.cpp b/test/ir_Tcl_test.cpp index 187f566d8..1ed5203a2 100644 --- a/test/ir_Tcl_test.cpp +++ b/test/ir_Tcl_test.cpp @@ -246,7 +246,6 @@ TEST(TestTcl112AcClass, Power) { ac.toString()); } - TEST(TestTcl112AcClass, Checksum) { uint8_t temp16C[kTcl112AcStateLength] = { 0x23, 0xCB, 0x26, 0x01, 0x00, 0x24, 0x03,