diff --git a/src/IRac.cpp b/src/IRac.cpp index da59af1b0..21678939b 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -4128,6 +4128,15 @@ String resultAcToString(const decode_results * const result) { return ac.toString(); } #endif // DECODE_COOLIX +#if DECODE_COOLIX48 + case decode_type_t::COOLIX48: { + IRCoolixAC ac(kGpioUnused); + ac.on(); + // Coolix uses value instead of state. + ac.setRawFromCoolix48(result->value); + return ac.toString(); + } +#endif // DECODE_COOLIX #if DECODE_CORONA_AC case decode_type_t::CORONA_AC: { IRCoronaAc ac(kGpioUnused); diff --git a/src/ir_Coolix.cpp b/src/ir_Coolix.cpp index 6769ebb79..bbc3dbbc2 100644 --- a/src/ir_Coolix.cpp +++ b/src/ir_Coolix.cpp @@ -99,6 +99,8 @@ void IRCoolixAC::stateReset(void) { cleanFlag = false; sleepFlag = false; swingFlag = false; + tempLowF = false; + tempHighF = false; } /// Set up hardware to be able to send a message. @@ -112,8 +114,34 @@ void IRCoolixAC::send(const uint16_t repeat) { // Typically repeat is `kCoolixDefaultRepeat` which is `1`, so this allows // it to be 0 normally for this command, and allows additional repeats if // requested rather always 0 for that command. - _irsend.sendCOOLIX(getRaw(), kCoolixBits, repeat - (getSwingVStep() && - repeat > 0) ? 1 : 0); + const uint16_t repeat_override = + repeat - (getSwingVStep() && repeat > 0) ? 1 : 0; + + if (tempHighF || tempLowF) { + uint64_t coolix24_raw = getRaw(); + // We need to sneak our temperature range into a parity bit. + uint64_t coolix48_raw = 0; + + // First create our parity. + uint64_t parity = getRaw() ^ UINT32_MAX; + + // Interleave raw data and parity into output, in order. + coolix48_raw |= ((coolix24_raw >> 16) & 0xFF) << 40; + coolix48_raw |= ((parity >> 16) & 0xFF) << 32; + coolix48_raw |= ((coolix24_raw >> 8) & 0xFF) << 24; + coolix48_raw |= ((parity >> 8) & 0xFF) << 16; + coolix48_raw |= (coolix24_raw & 0xFF) << 8; + coolix48_raw |= parity & 0xFF; + + // Mangle parity as necessary to represent Fahrenheit range. + coolix48_raw |= static_cast(tempLowF ? 1 : 3) << (36); + + // Send as Coolix48. + _irsend.sendCoolix48(coolix48_raw, kCoolix48Bits, repeat_override); + } else { + _irsend.sendCOOLIX(getRaw(), kCoolixBits, repeat_override); + } + // make sure to remove special state from the internal state // after command has being transmitted. recoverSavedState(); @@ -140,6 +168,27 @@ void IRCoolixAC::setRaw(const uint32_t new_code) { _.raw = new_code; } +// Set internal state from a COOLIX48 read, handling F data hidden in parity. +void IRCoolixAC::setRawFromCoolix48(const uint64_t data) { + // Assume parity bits as in coolix24, let's strip them. + CoolixProtocol coolix24; + coolix24.raw = 0; + coolix24.raw |= ((data >> 8) & 0xff); + coolix24.raw |= ((data >> 24) & 0xff) << 8; + coolix24.raw |= ((data >> 40) & 0xff) << 16; + + // Delegate to coolix24 code. + setRaw(coolix24.raw); + + // Handle Fahrenheit range. + uint8_t fRange = (data >> 36) & 3; + if (fRange == 1) { + setTempFRange(false); + } else if (fRange == 3) { + setTempFRange(true); + } +} + /// Is the current state is a special state? /// @return true, if it is. false if it isn't. bool IRCoolixAC::isSpecialState(void) const { @@ -217,19 +266,53 @@ void IRCoolixAC::setTempRaw(const uint8_t code) { _.Temp = code; } /// @return The native temperature value. uint8_t IRCoolixAC::getTempRaw(void) const { return _.Temp; } +/// Set the fahrenheit temperature range. +/// @param[in] high True if setting high F range, false if low F range. +void IRCoolixAC::setTempFRange(const bool high) { + tempLowF = !high; + tempHighF = high; +} + +// Clear the fahrenheit temperature range bits. +void IRCoolixAC::clearTempFRange() { + tempLowF = false; + tempHighF = false; +} + /// Set the temperature. /// @param[in] desired The temperature in degrees celsius. void IRCoolixAC::setTemp(const uint8_t desired) { // Range check. + if (desired >= kCoolixTempLowFMin && + desired <= kCoolixTempLowFMax) { + setTempRaw(kCoolixTempMapLowF[desired - kCoolixTempLowFMin]); + setTempFRange(false); + return; + } + if (desired >= kCoolixTempHighFMin && + desired <= kCoolixTempHighFMax) { + setTempRaw(kCoolixTempMapHighF[desired - kCoolixTempHighFMin]); + setTempFRange(true); + return; + } uint8_t temp = std::min(desired, kCoolixTempMax); temp = std::max(temp, kCoolixTempMin); setTempRaw(kCoolixTempMap[temp - kCoolixTempMin]); + clearTempFRange(); } /// Get the current temperature setting. /// @return The current setting for temp. in degrees celsius. uint8_t IRCoolixAC::getTemp(void) const { const uint8_t code = getTempRaw(); + if (tempLowF) { + for (uint8_t i = 0; i < kCoolixTempLowFRange; i++) + if (kCoolixTempMapLowF[i] == code) return kCoolixTempLowFMin + i; + } + if (tempHighF) { + for (uint8_t i = 0; i < kCoolixTempHighFRange; i++) + if (kCoolixTempMapHighF[i] == code) return kCoolixTempHighFMin + i; + } for (uint8_t i = 0; i < kCoolixTempRange; i++) if (kCoolixTempMap[i] == code) return kCoolixTempMin + i; return kCoolixTempMax; // Not a temp we expected. @@ -613,7 +696,9 @@ String IRCoolixAC::toString(void) const { } result += ')'; // Fan mode doesn't have a temperature. - if (getMode() != kCoolixFan) result += addTempToString(getTemp()); + bool celcius = true; + if (tempHighF || tempLowF) celcius = false; + if (getMode() != kCoolixFan) result += addTempToString(getTemp(), celcius); result += addBoolToString(getZoneFollow(), kZoneFollowStr); result += addLabeledString( (getSensorTemp() == kCoolixSensorTempIgnoreCode) diff --git a/src/ir_Coolix.h b/src/ir_Coolix.h index 5879dff2e..fcdbb2622 100644 --- a/src/ir_Coolix.h +++ b/src/ir_Coolix.h @@ -79,6 +79,42 @@ const uint8_t kCoolixTempMap[kCoolixTempRange] = { 0b1010, // 29C 0b1011 // 30C }; +const uint8_t kCoolixTempLowFMin = 63; +const uint8_t kCoolixTempLowFMax = 75; +const uint8_t kCoolixTempLowFRange = + kCoolixTempLowFMax - kCoolixTempLowFMin + 1; +const uint8_t kCoolixTempHighFMin = kCoolixTempLowFMax + 1; +const uint8_t kCoolixTempHighFMax = 86; +const uint8_t kCoolixTempHighFRange = + kCoolixTempHighFMax - kCoolixTempHighFMin + 1; +const uint8_t kCoolixTempMapLowF[kCoolixTempLowFRange] = { + 0b1100, // 63F + 0b0010, // 64F + 0b1010, // 65F + 0b0110, // 66F + 0b1110, // 67F + 0b0001, // 68F + 0b1001, // 69F + 0b0101, // 70F + 0b1101, // 71F + 0b0011, // 72F + 0b1011, // 73F + 0b0111, // 74F + 0b1111 // 75F +}; +const uint8_t kCoolixTempMapHighF[kCoolixTempHighFRange] = { + 0b0000, // 76F + 0b1000, // 77F + 0b0100, // 78F + 0b1100, // 79F + 0b0010, // 80F + 0b1010, // 81F + 0b0110, // 82F + 0b1110, // 83F + 0b0001, // 84F + 0b1001, // 85F + 0b0101 // 86F +}; const uint8_t kCoolixSensorTempMax = 30; // Celsius const uint8_t kCoolixSensorTempIgnoreCode = 0b11111; // 0x1F / 31 (DEC) // kCoolixSensorTempMask = 0b000000000000111100000000; // 0xF00 @@ -136,6 +172,8 @@ class IRCoolixAC { void off(void); void setPower(const bool on); bool getPower(void) const; + void setTempFRange(const bool high = false); + void clearTempFRange(); void setTemp(const uint8_t temp); uint8_t getTemp(void) const; void setSensorTemp(const uint8_t temp); @@ -160,6 +198,8 @@ class IRCoolixAC { bool getZoneFollow(void) const; uint32_t getRaw(void) const; void setRaw(const uint32_t new_code); + // Convert from Coolix48 with Fahrenheit handling. + void setRawFromCoolix48(const uint64_t new_code); static uint8_t convertMode(const stdAc::opmode_t mode); static uint8_t convertFan(const stdAc::fanspeed_t speed); static stdAc::opmode_t toCommonMode(const uint8_t mode); @@ -186,6 +226,8 @@ class IRCoolixAC { bool cleanFlag; bool sleepFlag; bool swingFlag; + bool tempLowF; // Indicates low-range F temperatures. + bool tempHighF; // Indicates high-range F temperatures. uint8_t savedFan; void setTempRaw(const uint8_t code); diff --git a/test/ir_Coolix_test.cpp b/test/ir_Coolix_test.cpp index 101fab654..fbc2a1db0 100644 --- a/test/ir_Coolix_test.cpp +++ b/test/ir_Coolix_test.cpp @@ -393,6 +393,7 @@ TEST(TestCoolixACClass, SetAndGetRaw) { TEST(TestCoolixACClass, SetAndGetTemp) { IRCoolixAC ac(kGpioUnused); + // Celcius ac.setTemp(25); EXPECT_EQ(25, ac.getTemp()); ac.setTemp(kCoolixTempMin); @@ -403,6 +404,26 @@ TEST(TestCoolixACClass, SetAndGetTemp) { EXPECT_EQ(kCoolixTempMin, ac.getTemp()); ac.setTemp(kCoolixTempMax + 1); EXPECT_EQ(kCoolixTempMax, ac.getTemp()); + + // Fahrenheit low + ac.setTemp(70); + EXPECT_EQ(70, ac.getTemp()); + ac.setTemp(kCoolixTempLowFMin); + EXPECT_EQ(kCoolixTempLowFMin, ac.getTemp()); + ac.setTemp(kCoolixTempLowFMax); + EXPECT_EQ(kCoolixTempLowFMax, ac.getTemp()); + ac.setTemp(kCoolixTempLowFMin - 1); + EXPECT_EQ(kCoolixTempMax, ac.getTemp()); + + // Fahrenheit high + ac.setTemp(80); + EXPECT_EQ(80, ac.getTemp()); + ac.setTemp(kCoolixTempHighFMin); + EXPECT_EQ(kCoolixTempHighFMin, ac.getTemp()); + ac.setTemp(kCoolixTempHighFMax); + EXPECT_EQ(kCoolixTempHighFMax, ac.getTemp()); + ac.setTemp(kCoolixTempHighFMax + 1); + EXPECT_EQ(kCoolixTempMax, ac.getTemp()); } TEST(TestCoolixACClass, SetAndGetMode) { @@ -1104,3 +1125,55 @@ TEST(TestCoolixACClass, Issue2012) { ac.toString()); EXPECT_EQ(kNoTempValue, ac.toCommon().sensorTemperature); } + +// Test decoding Fahrenheit from Coolix48. +TEST(TestCoolixACClass, SetRawFahrenheit) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + IRCoolixAC ac(kGpioUnused); + + ac.stateReset(); + ac.setRawFromCoolix48(0xB25DFF00C03F); + EXPECT_EQ( + "Power: On, Mode: 0 (Cool), Fan: 7 (Fixed), Temp: 63F, " + "Zone Follow: Off, Sensor Temp: Off", + ac.toString()); +} + +// Test sending and decoding of Fahrenheit values. +TEST(TestCoolixACClass, SendFahrenheit) { + IRrecv irrecv(kGpioUnused); + IRCoolixAC ac(kGpioUnused); + + const uint32_t on_cool_63f_fan_fixed_noparity = 0xB2FFC0; + ac.begin(); + ac.on(); + ac.setPower(true); + ac.setMode(kCoolixCool); + ac.setFan(kCoolixFanFixed); + ac.setTemp(63); + EXPECT_EQ(on_cool_63f_fan_fixed_noparity, ac.getRaw()); + + ac.send(); + ac._irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&ac._irsend.capture)); + EXPECT_EQ(COOLIX48, ac._irsend.capture.decode_type); + EXPECT_EQ(kCoolix48Bits, ac._irsend.capture.bits); + EXPECT_EQ(0xB25DFF00C03F, ac._irsend.capture.value); + EXPECT_EQ(0x0, ac._irsend.capture.address); + EXPECT_EQ(0x0, ac._irsend.capture.command); + + const uint32_t on_cool_80f_fan_fixed_noparity = 0xB2FF20; + ac.setTemp(80); + EXPECT_EQ(on_cool_80f_fan_fixed_noparity, ac.getRaw()); + + ac._irsend.reset(); + ac.send(); + ac._irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&ac._irsend.capture)); + EXPECT_EQ(COOLIX48, ac._irsend.capture.decode_type); + EXPECT_EQ(kCoolix48Bits, ac._irsend.capture.bits); + EXPECT_EQ(0xB27DFF0020DF, ac._irsend.capture.value); + EXPECT_EQ(0x0, ac._irsend.capture.address); + EXPECT_EQ(0x0, ac._irsend.capture.command); +}