Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/IRac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
91 changes: 88 additions & 3 deletions src/ir_Coolix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<uint64_t>(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();
Expand All @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
42 changes: 42 additions & 0 deletions src/ir_Coolix.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down
73 changes: 73 additions & 0 deletions test/ir_Coolix_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
}