From 7603a5bf251416efe9887c0031cda0a1f70b024b Mon Sep 17 00:00:00 2001 From: pasna Date: Fri, 26 Jul 2019 09:13:18 +0700 Subject: [PATCH] making some change on Daikin176bits to work with IRMQTTServer (#826) * modified fan setting Daikin176 to match remote * Fix Fan/SwingH problem. * Set Fan/Dry mode temp to 17C * Fix another bit masking bug in setMode(). * Add unit test coverage for temp, mode, power, and swing. * Add unit tests to verify against known-good remote data and try to reconstruct the messages. * Use/enforce only two values for fan speed. * Use the new common routines in `toString()`. * Add the fan speed and swing to the default/reset state. * Simplify some code. * Handle mode setting better. * remove Auto and Heat modes. Not supported. * Default to cool mode. * Handle internal message formation for modes better. * Better handle modes for common a/c stuff --- keywords.txt | 39 +++++++ src/IRac.cpp | 2 +- src/IRsend.cpp | 6 +- src/ir_Daikin.cpp | 208 ++++++++++++++++++------------------ src/ir_Daikin.h | 18 +++- test/IRac_test.cpp | 22 ++++ test/ir_Daikin_test.cpp | 228 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 406 insertions(+), 117 deletions(-) diff --git a/keywords.txt b/keywords.txt index 84244f714..a313b0502 100644 --- a/keywords.txt +++ b/keywords.txt @@ -23,6 +23,7 @@ IRArgoAC KEYWORD1 IRCoolixAC KEYWORD1 IRDaikin160 KEYWORD1 +IRDaikin176 KEYWORD1 IRDaikin2 KEYWORD1 IRDaikin216 KEYWORD1 IRDaikinESP KEYWORD1 @@ -109,6 +110,7 @@ copyIrParams KEYWORD2 countBits KEYWORD2 daikin KEYWORD2 daikin160 KEYWORD2 +daikin176 KEYWORD2 daikin2 KEYWORD2 daikin216 KEYWORD2 decode KEYWORD2 @@ -119,6 +121,7 @@ decodeCarrierAC KEYWORD2 decodeDISH KEYWORD2 decodeDaikin KEYWORD2 decodeDaikin160 KEYWORD2 +decodeDaikin176 KEYWORD2 decodeDaikin2 KEYWORD2 decodeDaikin216 KEYWORD2 decodeDenon KEYWORD2 @@ -343,6 +346,7 @@ sendCarrierAC KEYWORD2 sendDISH KEYWORD2 sendDaikin KEYWORD2 sendDaikin160 KEYWORD2 +sendDaikin176 KEYWORD2 sendDaikin2 KEYWORD2 sendDaikin216 KEYWORD2 sendData KEYWORD2 @@ -554,6 +558,7 @@ COOLIX LITERAL1 COOLIX_BITS LITERAL1 DAIKIN LITERAL1 DAIKIN160 LITERAL1 +DAIKIN176 LITERAL1 DAIKIN2 LITERAL1 DAIKIN216 LITERAL1 DAIKIN_AUTO LITERAL1 @@ -575,6 +580,7 @@ DECODE_CARRIER_AC LITERAL1 DECODE_COOLIX LITERAL1 DECODE_DAIKIN LITERAL1 DECODE_DAIKIN160 LITERAL1 +DECODE_DAIKIN176 LITERAL1 DECODE_DAIKIN2 LITERAL1 DECODE_DAIKIN216 LITERAL1 DECODE_DENON LITERAL1 @@ -861,6 +867,7 @@ SEND_CARRIER_AC LITERAL1 SEND_COOLIX LITERAL1 SEND_DAIKIN LITERAL1 SEND_DAIKIN160 LITERAL1 +SEND_DAIKIN176 LITERAL1 SEND_DAIKIN2 LITERAL1 SEND_DAIKIN216 LITERAL1 SEND_DENON LITERAL1 @@ -1094,6 +1101,38 @@ kDaikin160SwingVLow LITERAL1 kDaikin160SwingVLowest LITERAL1 kDaikin160SwingVMiddle LITERAL1 kDaikin160ZeroSpace LITERAL1 +kDaikin176MinTemp LITERAL1 +kDaikin176Cool LITERAL1 +kDaikin176BitMark LITERAL1 +kDaikin176Bits LITERAL1 +kDaikin176ByteFan LITERAL1 +kDaikin176ByteMode LITERAL1 +kDaikin176BytePower LITERAL1 +kDaikin176ByteSwingH LITERAL1 +kDaikin176ByteSwingV LITERAL1 +kDaikin176ByteTemp LITERAL1 +kDaikin176DefaultRepeat LITERAL1 +kDaikin176Freq LITERAL1 +kDaikin176Gap LITERAL1 +kDaikin176HdrMark LITERAL1 +kDaikin176HdrSpace LITERAL1 +kDaikin176MaskFan LITERAL1 +kDaikin176MaskMode LITERAL1 +kDaikin176MaskSwingH LITERAL1 +kDaikin176MaskSwingV LITERAL1 +kDaikin176MaskTemp LITERAL1 +kDaikin176OneSpace LITERAL1 +kDaikin176Section1Length LITERAL1 +kDaikin176Section2Length LITERAL1 +kDaikin176Sections LITERAL1 +kDaikin176StateLength LITERAL1 +kDaikin176ZeroSpace LITERAL1 +kDaikin176SwingVHigh LITERAL1 +kDaikin176SwingVLow LITERAL1 +kDaikin176SwingVAuto LITERAL1 +kDaikin176SwingHAuto LITERAL1 +kDaikin176SwingHOff LITERAL1 +kDaikin176FanMax LITERAL1 kDaikin216BitMark LITERAL1 kDaikin216Bits LITERAL1 kDaikin216ByteFan LITERAL1 diff --git a/src/IRac.cpp b/src/IRac.cpp index 0a965924e..f7b8dd354 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -1009,7 +1009,7 @@ bool IRac::sendAc(const decode_type_t vendor, const int16_t model, #if SEND_DAIKIN176 case DAIKIN176: { - IRDaikin176 ac(_pin); + IRDaikin176 ac(_pin, _inverted, _modulation); daikin176(&ac, on, mode, degC, fan, swingh); break; } diff --git a/src/IRsend.cpp b/src/IRsend.cpp index 8a6c3e442..6e36c7809 100644 --- a/src/IRsend.cpp +++ b/src/IRsend.cpp @@ -585,7 +585,7 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) { case DAIKIN160: return kDaikin160Bits; case DAIKIN176: - return kDaikin176Bits; + return kDaikin176Bits; case DAIKIN2: return kDaikin2Bits; case DAIKIN216: @@ -863,8 +863,8 @@ bool IRsend::send(const decode_type_t type, const unsigned char *state, #endif // SEND_DAIKIN160 #if SEND_DAIKIN176 case DAIKIN176: - sendDaikin176(state, nbytes); - break; + sendDaikin176(state, nbytes); + break; #endif // SEND_DAIKIN176 #if SEND_DAIKIN2 case DAIKIN2: diff --git a/src/ir_Daikin.cpp b/src/ir_Daikin.cpp index ccec8baa9..c8b0f050f 100644 --- a/src/ir_Daikin.cpp +++ b/src/ir_Daikin.cpp @@ -2060,7 +2060,9 @@ void IRsend::sendDaikin176(const unsigned char data[], const uint16_t nbytes, // // Supported Remotes: Daikin BRC4C153 remote // -IRDaikin176::IRDaikin176(uint16_t pin) : _irsend(pin) { stateReset(); } +IRDaikin176::IRDaikin176(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } void IRDaikin176::begin() { _irsend.begin(); } @@ -2104,10 +2106,12 @@ void IRDaikin176::stateReset() { remote_state[8] = 0xDA; remote_state[9] = 0x17; remote_state[10] = 0x18; - remote_state[12] = 0x03; + remote_state[12] = 0x73; remote_state[14] = 0x20; + remote_state[18] = 0x16; // Fan speed and swing remote_state[20] = 0x20; // remote_state[21] is a checksum byte, it will be set by checksum(). + _saved_temp = getTemp(); } uint8_t *IRDaikin176::getRaw() { @@ -2118,6 +2122,7 @@ uint8_t *IRDaikin176::getRaw() { void IRDaikin176::setRaw(const uint8_t new_code[]) { for (uint8_t i = 0; i < kDaikin176StateLength; i++) remote_state[i] = new_code[i]; + _saved_temp = getTemp(); } #if SEND_DAIKIN176 @@ -2127,19 +2132,16 @@ void IRDaikin176::send(const uint16_t repeat) { } #endif // SEND_DAIKIN176 -void IRDaikin176::on() { - remote_state[kDaikin176BytePower] |= kDaikinBitPower; -} +void IRDaikin176::on() { setPower(true); } -void IRDaikin176::off() { - remote_state[kDaikin176BytePower] &= ~kDaikinBitPower; -} +void IRDaikin176::off() { setPower(false); } void IRDaikin176::setPower(const bool state) { + remote_state[kDaikin176ByteModeButton] = 0; if (state) - on(); + remote_state[kDaikin176BytePower] |= kDaikinBitPower; else - off(); + remote_state[kDaikin176BytePower] &= ~kDaikinBitPower; } bool IRDaikin176::getPower() { @@ -2151,42 +2153,60 @@ uint8_t IRDaikin176::getMode() { } void IRDaikin176::setMode(const uint8_t mode) { + uint8_t altmode = 0; switch (mode) { - case kDaikinAuto: - case kDaikin176Cool: - case kDaikinHeat: - case kDaikinFan: - case kDaikinDry: - remote_state[kDaikin176ByteMode] &= kDaikin176MaskMode; - remote_state[kDaikin176ByteMode] |= (mode << 4); - break; - default: - this->setMode(kDaikinAuto); + case kDaikinFan: altmode = 0; break; + case kDaikinDry: altmode = 7; break; + case kDaikin176Cool: altmode = 2; break; + default: this->setMode(kDaikin176Cool); return; } + // Set the mode. + remote_state[kDaikin176ByteMode] &= ~kDaikin176MaskMode; + remote_state[kDaikin176ByteMode] |= (mode << 4); + // Set the altmode + remote_state[kDaikin176BytePower] &= ~kDaikin176MaskMode; + remote_state[kDaikin176BytePower] |= (altmode << 4); + setTemp(_saved_temp); + // Needs to happen after setTemp() as it will clear it. + remote_state[kDaikin176ByteModeButton] = kDaikin176ModeButton; } // Convert a standard A/C mode into its native mode. uint8_t IRDaikin176::convertMode(const stdAc::opmode_t mode) { switch (mode) { - case stdAc::opmode_t::kCool: - return kDaikin176Cool; - case stdAc::opmode_t::kHeat: - return kDaikinHeat; case stdAc::opmode_t::kDry: return kDaikinDry; + case stdAc::opmode_t::kHeat: // Heat not supported, but fan is the closest. case stdAc::opmode_t::kFan: return kDaikinFan; default: - return kDaikinAuto; + return kDaikin176Cool; + } +} + +// Convert a native mode to it's common equivalent. +stdAc::opmode_t IRDaikin176::toCommonMode(const uint8_t mode) { + switch (mode) { + case kDaikinDry: return stdAc::opmode_t::kDry; + case kDaikinHeat: // There is no heat mode, but fan is the closest. + case kDaikinFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kCool; } } // Set the temp in deg C void IRDaikin176::setTemp(const uint8_t temp) { - uint8_t degrees = std::max(temp, kDaikinMinTemp); - degrees = std::min(degrees, kDaikinMaxTemp) * 2 - 18; + uint8_t degrees = std::min(kDaikinMaxTemp, std::max(temp, kDaikinMinTemp)); + _saved_temp = degrees; + switch (getMode()) { + case kDaikinDry: + case kDaikinFan: + degrees = kDaikin176DryFanTemp; + } + degrees = degrees * 2 - 18; remote_state[kDaikin176ByteTemp] &= ~kDaikin176MaskTemp; remote_state[kDaikin176ByteTemp] |= degrees; + remote_state[kDaikin176ByteModeButton] = 0; } uint8_t IRDaikin176::getTemp(void) { @@ -2195,45 +2215,43 @@ uint8_t IRDaikin176::getTemp(void) { // Set the speed of the fan, 1 for Min or 3 for Max void IRDaikin176::setFan(const uint8_t fan) { - uint8_t fanset; - if (fan == kDaikinFanQuiet || fan == kDaikinFanAuto) - fanset = fan; - else if (fan < kDaikinFanMin || fan > kDaikinFanMax) - fanset = kDaikinFanAuto; - else - fanset = 2 + fan; - // Set the fan speed bits, leave *lower* 4 bits alone - remote_state[kDaikin176ByteFan] &= ~kDaikin176MaskFan; - remote_state[kDaikin176ByteFan] |= (fanset << 4); + switch (fan) { + case kDaikinFanMin: + case kDaikin176FanMax: + remote_state[kDaikin176ByteFan] &= ~kDaikin176MaskFan; + remote_state[kDaikin176ByteFan] |= (fan << 4); + remote_state[kDaikin176ByteModeButton] = 0; + break; + default: + setFan(kDaikin176FanMax); + } } -uint8_t IRDaikin176::getFan() { - uint8_t fan = remote_state[kDaikin176ByteFan] >> 4; - return fan; -} +uint8_t IRDaikin176::getFan() { return remote_state[kDaikin176ByteFan] >> 4; } // Convert a standard A/C Fan speed into its native fan speed. uint8_t IRDaikin176::convertFan(const stdAc::fanspeed_t speed) { - switch (speed) { - case stdAc::fanspeed_t::kMin: return kDaikinFanMin; - case stdAc::fanspeed_t::kLow: return kDaikinFanMin + 1; - case stdAc::fanspeed_t::kMedium: return kDaikinFanMin + 2; - case stdAc::fanspeed_t::kHigh: return kDaikinFanMax - 1; - case stdAc::fanspeed_t::kMax: return kDaikinFanMax; + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: + return kDaikinFanMin; default: - return kDaikinFanAuto; + return kDaikin176FanMax; } } void IRDaikin176::setSwingHorizontal(const uint8_t position) { switch (position) { - case kDaikin176SwingHSwing: - remote_state[kDaikin176ByteSwingH] &= kDaikin176MaskSwingH; - remote_state[kDaikin176ByteSwingH] |= position; - break; - default: setSwingHorizontal(kDaikin176SwingHAuto); + case kDaikin176SwingHOff: + case kDaikin176SwingHAuto: + remote_state[kDaikin176ByteSwingH] &= ~kDaikin176MaskSwingH; + remote_state[kDaikin176ByteSwingH] |= position; + break; + default: + setSwingHorizontal(kDaikin176SwingHAuto); } } + uint8_t IRDaikin176::getSwingHorizontal() { return remote_state[kDaikin176ByteSwingH] & kDaikin176MaskSwingH; } @@ -2242,7 +2260,9 @@ uint8_t IRDaikin176::getSwingHorizontal() { uint8_t IRDaikin176::convertSwingH(const stdAc::swingh_t position) { switch (position) { case stdAc::swingh_t::kOff: - return kDaikin176SwingHSwing; + return kDaikin176SwingHOff; + case stdAc::swingh_t::kAuto: + return kDaikin176SwingHAuto; default: return kDaikin176SwingHAuto; } @@ -2250,21 +2270,29 @@ uint8_t IRDaikin176::convertSwingH(const stdAc::swingh_t position) { // Convert a native horizontal swing to it's common equivalent. stdAc::swingh_t IRDaikin176::toCommonSwingH(const uint8_t setting) { switch (setting) { - case kDaikin176SwingHSwing: return stdAc::swingh_t::kOff; - default: return stdAc::swingh_t::kAuto; + case kDaikin176SwingHOff: return stdAc::swingh_t::kOff; + case kDaikin176SwingHAuto: return stdAc::swingh_t::kAuto; + default: + return stdAc::swingh_t::kAuto; } } +// Convert a native fan speed to it's common equivalent. +stdAc::fanspeed_t IRDaikin176::toCommonFanSpeed(const uint8_t speed) { + return (speed == kDaikinFanMin) ? stdAc::fanspeed_t::kMin + : stdAc::fanspeed_t::kMax; +} + // Convert the A/C state to it's common equivalent. stdAc::state_t IRDaikin176::toCommon(void) { stdAc::state_t result; result.protocol = decode_type_t::DAIKIN176; result.model = -1; // No models used. result.power = this->getPower(); - result.mode = IRDaikinESP::toCommonMode(this->getMode()); + result.mode = IRDaikin176::toCommonMode(this->getMode()); result.celsius = true; result.degrees = this->getTemp(); - result.fanspeed = IRDaikinESP::toCommonFanSpeed(this->getFan()); + result.fanspeed = this->toCommonFanSpeed(this->getFan()); result.swingh = this->toCommonSwingH(this->getSwingHorizontal()); // Not supported. @@ -2284,60 +2312,24 @@ stdAc::state_t IRDaikin176::toCommon(void) { // Convert the internal state into a human readable string. String IRDaikin176::toString() { String result = ""; - result.reserve(120); // Reserve some heap for the string to reduce fragging. - result += F("Power: "); - if (this->getPower()) - result += F("On"); - else - result += F("Off"); - result += F(", Mode: "); - result += uint64ToString(this->getMode()); - switch (getMode()) { - case kDaikinAuto: - result += F(" (AUTO)"); - break; - case kDaikinCool + 4: - result += F(" (COOL)"); - break; - case kDaikinHeat: - result += F(" (HEAT)"); - break; - case kDaikinDry: - result += F(" (DRY)"); - break; - case kDaikinFan: - 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 kDaikinFanAuto: - result += F(" (AUTO)"); - break; - case kDaikinFanQuiet: - result += F(" (QUIET)"); - break; - case kDaikinFanMin: - result += F(" (MIN)"); - break; - case kDaikinFanMax - 2: - result += F(" (MAX)"); - break; - } + result.reserve(80); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(getPower(), F("Power"), false); + result += addModeToString(getMode(), kDaikinAuto, kDaikin176Cool, kDaikinHeat, + kDaikinDry, kDaikinFan); + result += addTempToString(getTemp()); + result += addFanToString(getFan(), kDaikin176FanMax, kDaikinFanMin, + kDaikinFanMin, kDaikinFanMin, kDaikinFanMin); result += F(", Swing (H): "); result += uint64ToString(getSwingHorizontal()); switch (getSwingHorizontal()) { case kDaikin176SwingHAuto: - result += F(" (Auto)"); - break; - case kDaikin176SwingHSwing: - result += F(" (Off)"); - break; + result += F(" (Auto)"); + break; + case kDaikin176SwingHOff: + result += F(" (Off)"); + break; + default: + result += F(" (UNKNOWN)"); } return result; } diff --git a/src/ir_Daikin.h b/src/ir_Daikin.h index fdfc8b6ee..a431c6263 100644 --- a/src/ir_Daikin.h +++ b/src/ir_Daikin.h @@ -263,18 +263,22 @@ const uint16_t kDaikin176Sections = 2; const uint16_t kDaikin176Section1Length = 7; const uint16_t kDaikin176Section2Length = kDaikin176StateLength - kDaikin176Section1Length; -const uint8_t kDaikin176Cool = 0b111; +const uint8_t kDaikin176Cool = 0b111; // 7 const uint8_t kDaikin176BytePower = 14; const uint8_t kDaikin176ByteMode = 12; -const uint8_t kDaikin176MaskMode = 0b01110011; +const uint8_t kDaikin176MaskMode = 0b01110000; +const uint8_t kDaikin176ByteModeButton = 13; +const uint8_t kDaikin176ModeButton = 0b00000100; const uint8_t kDaikin176ByteTemp = 17; const uint8_t kDaikin176MaskTemp = 0b01111110; +const uint8_t kDaikin176DryFanTemp = 17; // Dry/Fan mode is always 17 Celsius. const uint8_t kDaikin176ByteFan = 18; const uint8_t kDaikin176MaskFan = 0b11110000; +const uint8_t kDaikin176FanMax = 3; const uint8_t kDaikin176ByteSwingH = 18; const uint8_t kDaikin176MaskSwingH = 0b00001111; const uint8_t kDaikin176SwingHAuto = 0x5; -const uint8_t kDaikin176SwingHSwing = 0x6; +const uint8_t kDaikin176SwingHOff = 0x6; // Another variant of the protocol for the Daikin BRC52B63 remote. // Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/827 @@ -569,7 +573,8 @@ class IRDaikin160 { // Class to emulate a Daikin BRC4C153 remote. class IRDaikin176 { public: - explicit IRDaikin176(uint16_t pin); + explicit IRDaikin176(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); #if SEND_DAIKIN176 void send(const uint16_t repeat = kDaikin176DefaultRepeat); @@ -593,8 +598,10 @@ class IRDaikin176 { uint8_t getFan(void); static uint8_t convertFan(const stdAc::fanspeed_t speed); void setSwingHorizontal(const uint8_t position); - uint8_t getSwingHorizontal(); + uint8_t getSwingHorizontal(void); static uint8_t convertSwingH(const stdAc::swingh_t position); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); static stdAc::swingh_t toCommonSwingH(const uint8_t setting); stdAc::state_t toCommon(void); String toString(void); @@ -608,6 +615,7 @@ class IRDaikin176 { #endif // # of bytes per command uint8_t remote_state[kDaikin176StateLength]; + uint8_t _saved_temp; void stateReset(); void checksum(); }; diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index f0c6d11c3..1378f8b0f 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -167,6 +167,28 @@ TEST(TestIRac, Daikin160) { ASSERT_EQ(expected, IRAcUtils::resultAcToString(&ac._irsend.capture)); } +TEST(TestIRac, Daikin176) { + IRDaikin176 ac(0); + IRac irac(0); + IRrecv capture(0); + char expected[] = + "Power: On, Mode: 7 (COOL), Temp: 26C, Fan: 1 (Low), Swing (H): 5 (Auto)"; + + ac.begin(); + irac.daikin176(&ac, + true, // Power + stdAc::opmode_t::kCool, // Mode + 26, // Celsius + stdAc::fanspeed_t::kLow, // Fan speed + stdAc::swingh_t::kAuto); // Horizontal swing + ASSERT_EQ(expected, ac.toString()); + ac._irsend.makeDecodeResult(); + EXPECT_TRUE(capture.decode(&ac._irsend.capture)); + ASSERT_EQ(DAIKIN176, ac._irsend.capture.decode_type); + ASSERT_EQ(kDaikin176Bits, ac._irsend.capture.bits); + ASSERT_EQ(expected, IRAcUtils::resultAcToString(&ac._irsend.capture)); +} + TEST(TestIRac, Daikin2) { IRDaikin2 ac(0); IRac irac(0); diff --git a/test/ir_Daikin_test.cpp b/test/ir_Daikin_test.cpp index 6bfa53132..cc48ea809 100644 --- a/test/ir_Daikin_test.cpp +++ b/test/ir_Daikin_test.cpp @@ -2299,6 +2299,234 @@ TEST(TestDaikin160Class, HumanReadable) { ac.toString()); } +TEST(TestDaikin176Class, FanControl) { + IRDaikin176 ac(0); + + EXPECT_EQ( + "Power: Off, Mode: 7 (COOL), Temp: 9C, Fan: 1 (Low), Swing (H): 6 (Off)", + ac.toString()); + ac.setFan(kDaikinFanMin); + ac.setPower(true); + EXPECT_EQ( + "Power: On, Mode: 7 (COOL), Temp: 9C, Fan: 1 (Low), Swing (H): 6 (Off)", + ac.toString()); + ac.setFan(kDaikinFanMin + 1); + EXPECT_EQ( + "Power: On, Mode: 7 (COOL), Temp: 9C, Fan: 3 (High), Swing (H): 6 (Off)", + ac.toString()); + ac.setFan(kDaikin176FanMax); + EXPECT_EQ( + "Power: On, Mode: 7 (COOL), Temp: 9C, Fan: 3 (High), Swing (H): 6 (Off)", + ac.toString()); + + // Real state from remote + // https://github.com/crankyoldgit/IRremoteESP8266/pull/826#issuecomment-513168270 + uint8_t state[kDaikin176StateLength] = { + 0x11, 0xDA, 0x17, 0x18, 0x04, 0x00, 0x1E, + 0x11, 0xDA, 0x17, 0x18, 0x00, 0x73, 0x00, 0x21, 0x00, 0x00, 0x22, 0x35, + 0x00, 0x20, 0x25}; + ac.setRaw(state); + EXPECT_EQ( + "Power: On, Mode: 7 (COOL), Temp: 26C, Fan: 3 (High), " + "Swing (H): 5 (Auto)", + ac.toString()); +} + +TEST(TestDaikin176Class, convertFan) { + EXPECT_EQ(kDaikinFanMin, IRDaikin176::convertFan(stdAc::fanspeed_t::kMin)); + EXPECT_EQ(kDaikinFanMin, IRDaikin176::convertFan(stdAc::fanspeed_t::kLow)); + EXPECT_EQ(kDaikin176FanMax, + IRDaikin176::convertFan(stdAc::fanspeed_t::kMedium)); + EXPECT_EQ(kDaikin176FanMax, + IRDaikin176::convertFan(stdAc::fanspeed_t::kHigh)); + EXPECT_EQ(kDaikin176FanMax, IRDaikin176::convertFan(stdAc::fanspeed_t::kMax)); + EXPECT_EQ(kDaikin176FanMax, + IRDaikin176::convertFan(stdAc::fanspeed_t::kAuto)); +} + +TEST(TestDaikin176Class, SimulateIRacDaikin176) { + IRDaikin176 ac(0); + + ac.setPower(true); + ac.setMode(ac.convertMode(stdAc::opmode_t::kCool)); + ac.setTemp(26); + ac.setFan(ac.convertFan(stdAc::fanspeed_t::kMax)); + ac.setSwingHorizontal(kDaikin176SwingHOff); + EXPECT_EQ( + "Power: On, Mode: 7 (COOL), Temp: 26C, Fan: 3 (High), Swing (H): 6 (Off)", + ac.toString()); + ac.setSwingHorizontal(ac.convertSwingH(stdAc::swingh_t::kAuto)); + EXPECT_EQ( + "Power: On, Mode: 7 (COOL), Temp: 26C, Fan: 3 (High), " + "Swing (H): 5 (Auto)", + ac.toString()); +} + +TEST(TestDaikin176Class, OperatingMode) { + IRDaikin176 ac(0); + ac.begin(); + + ac.setMode(kDaikinAuto); + EXPECT_EQ(kDaikin176Cool, ac.getMode()); + + ac.setMode(kDaikin176Cool); + EXPECT_EQ(kDaikin176Cool, ac.getMode()); + + ac.setMode(kDaikinDry); + EXPECT_EQ(kDaikinDry, ac.getMode()); + + ac.setMode(kDaikinHeat); + EXPECT_EQ(kDaikin176Cool, ac.getMode()); + + ac.setMode(kDaikinFan); + EXPECT_EQ(kDaikinFan, ac.getMode()); + + ac.setMode(kDaikin176Cool + 1); + EXPECT_EQ(kDaikin176Cool, ac.getMode()); + + ac.setMode(kDaikinAuto + 1); + EXPECT_EQ(kDaikin176Cool, ac.getMode()); + + ac.setMode(255); + EXPECT_EQ(kDaikin176Cool, ac.getMode()); +} + +TEST(TestDaikin176Class, Temperature) { + IRDaikin176 ac(0); + ac.begin(); + ac.setMode(kDaikinAuto); + ac.setTemp(0); + EXPECT_EQ(kDaikinMinTemp, ac.getTemp()); + + ac.setTemp(255); + EXPECT_EQ(kDaikinMaxTemp, ac.getTemp()); + + ac.setTemp(kDaikinMinTemp); + EXPECT_EQ(kDaikinMinTemp, ac.getTemp()); + + ac.setTemp(kDaikinMaxTemp); + EXPECT_EQ(kDaikinMaxTemp, ac.getTemp()); + + ac.setTemp(kDaikinMinTemp - 1); + EXPECT_EQ(kDaikinMinTemp, ac.getTemp()); + + ac.setTemp(kDaikinMaxTemp + 1); + EXPECT_EQ(kDaikinMaxTemp, ac.getTemp()); + + ac.setTemp(kDaikinMinTemp + 1); + EXPECT_EQ(kDaikinMinTemp + 1, 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()); + + // Temp should be locked to kDaikin176DryFanTemp when in Dry or Fan Mode. + ac.setMode(kDaikinFan); + EXPECT_EQ(kDaikin176DryFanTemp, ac.getTemp()); + ac.setMode(kDaikin176Cool); + EXPECT_EQ(29, ac.getTemp()); + ac.setMode(kDaikinDry); + EXPECT_EQ(kDaikinDry, ac.getMode()); + EXPECT_EQ(kDaikin176DryFanTemp, ac.getTemp()); + ac.setMode(kDaikin176Cool); + EXPECT_EQ(29, ac.getTemp()); + ac.setMode(kDaikinFan); + ac.setTemp(25); + EXPECT_EQ(kDaikin176DryFanTemp, ac.getTemp()); + ac.setMode(kDaikinHeat); + EXPECT_EQ(25, ac.getTemp()); +} + +TEST(TestDaikin176Class, Power) { + IRDaikin176 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(TestDaikin176Class, VaneSwing) { + IRDaikin176 ac(0); + ac.begin(); + + ac.setSwingHorizontal(kDaikin176SwingHAuto); + EXPECT_EQ(kDaikin176SwingHAuto, ac.getSwingHorizontal()); + + ac.setSwingHorizontal(0); + EXPECT_EQ(kDaikin176SwingHAuto, ac.getSwingHorizontal()); + + ac.setSwingHorizontal(kDaikin176SwingHOff); + EXPECT_EQ(kDaikin176SwingHOff, ac.getSwingHorizontal()); + + ac.setSwingHorizontal(255); + EXPECT_EQ(kDaikin176SwingHAuto, ac.getSwingHorizontal()); + + EXPECT_EQ(kDaikin176SwingHAuto, + IRDaikin176::convertSwingH(stdAc::swingh_t::kAuto)); + EXPECT_EQ(kDaikin176SwingHOff, + IRDaikin176::convertSwingH(stdAc::swingh_t::kOff)); + EXPECT_EQ(kDaikin176SwingHAuto, + IRDaikin176::convertSwingH(stdAc::swingh_t::kLeft)); +} + +TEST(TestDaikin176Class, ReconstructKnownStates) { + IRDaikin176 ac(0); + ac.begin(); + // Data from: + // https://github.com/crankyoldgit/IRremoteESP8266/pull/826#issuecomment-513531138 + + // Power: On, Mode: 7 (COOL), Temp: 25C, Fan: 3 (MAX), Swing (H): 5 (Auto) + // 11DA171804001E11DA17180073002100002035002023 + uint8_t on_cool_25_max_auto[22] = { + 0x11, 0xDA, 0x17, 0x18, 0x04, 0x00, 0x1E, + 0x11, 0xDA, 0x17, 0x18, 0x00, 0x73, 0x00, 0x21, 0x00, 0x00, 0x20, 0x35, + 0x00, 0x20, 0x23}; + // Power: On, Mode: 6 (FAN), Temp: 17C, Fan: 3 (MAX), Swing (H): 5 (Auto) + // 11DA171804001E11DA171800630401000010350020E7 + uint8_t on_fan_17_max_auto[22] = { + 0x11, 0xDA, 0x17, 0x18, 0x04, 0x00, 0x1E, + 0x11, 0xDA, 0x17, 0x18, 0x00, 0x63, 0x04, 0x01, 0x00, 0x00, 0x10, 0x35, + 0x00, 0x20, 0xE7}; + // Power: On, Mode: 2 (DRY), Temp: 17C, Fan: 3 (MAX), Swing (H): 5 (Auto) + // 11DA171804001E11DA17180023047100001035002017 + uint8_t on_dry_17_max_auto[22] = { + 0x11, 0xDA, 0x17, 0x18, 0x04, 0x00, 0x1E, + 0x11, 0xDA, 0x17, 0x18, 0x00, 0x23, 0x04, 0x71, 0x00, 0x00, 0x10, 0x35, + 0x00, 0x20, 0x17}; + // Power: On, Mode: 7 (COOL), Temp: 25C, Fan: 3 (MAX), Swing (H): 5 (Auto) + // 11DA171804001E11DA17180073042100002035002027 + uint8_t on_cool_25_max_auto_v2[22] = { + 0x11, 0xDA, 0x17, 0x18, 0x04, 0x00, 0x1E, + 0x11, 0xDA, 0x17, 0x18, 0x00, 0x73, 0x04, 0x21, 0x00, 0x00, 0x20, 0x35, + 0x00, 0x20, 0x27}; + ac.setMode(kDaikin176Cool); + ac.setPower(true); + ac.setTemp(25); + ac.setFan(kDaikin176FanMax); + ac.setSwingHorizontal(true); + EXPECT_STATE_EQ(on_cool_25_max_auto, ac.getRaw(), kDaikin176Bits); + ac.setMode(kDaikinFan); + EXPECT_STATE_EQ(on_fan_17_max_auto, ac.getRaw(), kDaikin176Bits); + ac.setMode(kDaikinDry); + EXPECT_STATE_EQ(on_dry_17_max_auto, ac.getRaw(), kDaikin176Bits); + ac.setMode(kDaikin176Cool); + EXPECT_STATE_EQ(on_cool_25_max_auto_v2, ac.getRaw(), kDaikin176Bits); +} + // Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/827 // Data from: // https://docs.google.com/spreadsheets/d/1-YJnHyzy6bId5QmjTEZuw8_wSufESoIl-L_VEF-o8lM/edit?usp=sharing