From 331b7fb7a3e82cf59826ceb02b7a4239519e2668 Mon Sep 17 00:00:00 2001 From: Ben Gruver Date: Thu, 27 Nov 2025 20:29:31 -0600 Subject: [PATCH] Add Fahrenheit support for the BOSCH144 protocol Issue #2224 --- src/IRac.cpp | 10 +-- src/IRac.h | 2 +- src/ir_Bosch.cpp | 60 ++++++++++++----- src/ir_Bosch.h | 145 ++++++++++++++++++++++++++--------------- test/ir_Bosch_test.cpp | 51 +++++++++++++++ 5 files changed, 196 insertions(+), 72 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index da59af1b0..f10bdf2bf 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -644,13 +644,14 @@ void IRac::argoWrem3_SetTimer(IRArgoAC_WREM3 *ac, bool on, /// @param[in] on The power setting. /// @param[in] mode The operation mode setting. /// @param[in] degrees The temperature setting in degrees. +/// @param[in] celsius Temperature units. True is Celsius, False is Fahrenheit. /// @param[in] fan The speed setting for the fan. /// @param[in] quiet Run the device in quiet/silent mode. /// @note -1 is Off, >= 0 is on. void IRac::bosch144(IRBosch144AC *ac, const bool on, const stdAc::opmode_t mode, - const float degrees, const stdAc::fanspeed_t fan, - const bool quiet) { + const float degrees, const bool celsius, + const stdAc::fanspeed_t fan, const bool quiet) { ac->begin(); ac->setPower(on); if (!on) { @@ -659,7 +660,7 @@ void IRac::bosch144(IRBosch144AC *ac, ac->send(); return; } - ac->setTemp(degrees); + ac->setTemp(degrees, !celsius); ac->setFan(ac->convertFan(fan)); ac->setMode(ac->convertMode(mode)); ac->setQuiet(quiet); @@ -3107,7 +3108,8 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { case BOSCH144: { IRBosch144AC ac(_pin, _inverted, _modulation); - bosch144(&ac, send.power, send.mode, degC, send.fanspeed, send.quiet); + bosch144(&ac, send.power, send.mode, send.degrees, send.celsius, + send.fanspeed, send.quiet); break; } #endif // SEND_BOSCH144 diff --git a/src/IRac.h b/src/IRac.h index ae78b8ce3..1d8c42933 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -162,7 +162,7 @@ class IRac { #if SEND_BOSCH144 void bosch144(IRBosch144AC *ac, const bool on, const stdAc::opmode_t mode, const float degrees, - const stdAc::fanspeed_t fan, + const bool celsius, const stdAc::fanspeed_t fan, const bool quiet); #endif // SEND_BOSCH144 #if SEND_CARRIER_AC64 diff --git a/src/ir_Bosch.cpp b/src/ir_Bosch.cpp index c18dddf38..38ef8f930 100644 --- a/src/ir_Bosch.cpp +++ b/src/ir_Bosch.cpp @@ -95,27 +95,55 @@ bool IRBosch144AC::getPower(void) const { } void IRBosch144AC::setTempRaw(const uint8_t code) { - _.TempS1 = _.TempS2 = code >> 1; // save 4 bits in S1 and S2 - _.TempS3 = code & 1; // save 1 bit in Section3 + _.TempS1 = _.TempS2 = code >> 2; // save bits 3-6 in S1 and S2 + _.TempS3 = code >> 1; // save bit 2 in Section3 + _.TempS4 = code; // save bit 1 in Section3 } -/// Set the temperature. -/// @param[in] degrees The temperature in degrees celsius. -void IRBosch144AC::setTemp(const uint8_t degrees) { - uint8_t temp = max(kBosch144TempMin, degrees); - temp = min(kBosch144TempMax, temp); - setTempRaw(kBosch144TempMap[temp - kBosch144TempMin]); +/// Set the temp. in degrees +/// @param[in] temp Desired temperature in Degrees. +/// @param[in] fahrenheit Use units of Fahrenheit and set that as units used. +/// false is Celsius (Default), true is Fahrenheit. +void IRBosch144AC::setTemp(const uint8_t temp, const bool fahrenheit) { + if (fahrenheit) { + uint8_t constrainedTemp = max(kBosch144FahrenheitMin, temp); + constrainedTemp = min(kBosch144FahrenheitMax, constrainedTemp); + setTempRaw( + kBosch144FahrenheitMap[constrainedTemp - kBosch144FahrenheitMin]); + setUseFahrenheit(true); + } else { + uint8_t constrainedTemp = max(kBosch144CelsiusMin, temp); + constrainedTemp = min(kBosch144CelsiusMax, constrainedTemp); + setTempRaw(kBosch144CelsiusMap[constrainedTemp - kBosch144CelsiusMin]); + setUseFahrenheit(false); + } } uint8_t IRBosch144AC::getTemp(void) const { - uint8_t temp = (_.TempS1 << 1) + _.TempS3; - uint8_t retemp = 25; - for (uint8_t i = 0; i < kBosch144TempRange; i++) { - if (temp == kBosch144TempMap[i]) { - retemp = kBosch144TempMin + i; + uint8_t temp = (_.TempS1 << 2) + (_.TempS3 << 1) + _.TempS4; + if (getUseFahrenheit()) { + for (uint8_t i = 0; i < sizeof(kBosch144FahrenheitMap); i++) { + if (temp == kBosch144FahrenheitMap[i]) { + return kBosch144FahrenheitMin + i; + } + } + return 77; + } else { + for (uint8_t i = 0; i < sizeof(kBosch144CelsiusMap); i++) { + if (temp == kBosch144CelsiusMap[i]) { + return kBosch144CelsiusMin + i; + } } + return 25; } - return retemp; +} + +void IRBosch144AC::setUseFahrenheit(const bool on) { + _.UseFahrenheit = on; +} + +bool IRBosch144AC::getUseFahrenheit(void) const { + return _.UseFahrenheit; } /// Set the speed of the fan. @@ -227,7 +255,7 @@ stdAc::state_t IRBosch144AC::toCommon(void) const { result.protocol = decode_type_t::BOSCH144; result.power = getPower(); result.mode = toCommonMode(getMode()); - result.celsius = true; + result.celsius = !getUseFahrenheit(); result.degrees = getTemp(); result.fanspeed = toCommonFanSpeed(getFan()); result.quiet = getQuiet(); @@ -261,7 +289,7 @@ String IRBosch144AC::toString(void) const { static_cast(stdAc::fanspeed_t::kAuto), static_cast(stdAc::fanspeed_t::kAuto), static_cast(stdAc::fanspeed_t::kMedium)); - result += addTempToString(getTemp()); + result += addTempToString(getTemp(), !getUseFahrenheit()); result += addBoolToString(_.Quiet, kQuietStr); return result; } diff --git a/src/ir_Bosch.h b/src/ir_Bosch.h index 9ebee9854..f79324e1e 100644 --- a/src/ir_Bosch.h +++ b/src/ir_Bosch.h @@ -6,6 +6,7 @@ // Supports: // Brand: Bosch, Model: CL3000i-Set 26 E A/C // Brand: Bosch, Model: RG10A(G2S)BGEF remote +// Brand: Durastar, Model: RG10R(M2S)/BGEFU1 remote #ifndef IR_BOSCH_H_ @@ -66,25 +67,62 @@ const uint16_t kBosch144FanAuto = 0b101110011; const uint16_t kBosch144FanAuto0 = 0b000110011; // Temperature -const uint8_t kBosch144TempMin = 16; // Celsius -const uint8_t kBosch144TempMax = 30; // Celsius -const uint8_t kBosch144TempRange = kBosch144TempMax - kBosch144TempMin + 1; -const uint8_t kBosch144TempMap[kBosch144TempRange] = { - 0b00001, // 16C // Bit[0] to Section 3 Bit[1-4] to Section 1 - 0b00000, // 17C // TempS3 TempS1 - 0b00010, // 18c - 0b00110, // 19C - 0b00100, // 20C - 0b01100, // 21C - 0b01110, // 22C - 0b01010, // 23C - 0b01000, // 24C - 0b11000, // 25C - 0b11010, // 26C - 0b10010, // 27C - 0b10000, // 28C - 0b10100, // 29C - 0b10110 // 30C +const uint8_t kBosch144CelsiusMin = 16; +const uint8_t kBosch144CelsiusMax = 30; +const uint8_t kBosch144CelsiusMap[] = { + // Bit[0] to Section 3: TempS4 (the "half-degree" bit) + // Bit[1] to Section 3: TempS3 + // Bit[1-4] to Section 1: TempS1 + 0b000010, // 16C + 0b000000, // 17C + 0b000100, // 18C + 0b001100, // 19C + 0b001000, // 20C + 0b011000, // 21C + 0b011100, // 22C + 0b010100, // 23C + 0b010000, // 24C + 0b110000, // 25C + 0b110100, // 26C + 0b100100, // 27C + 0b100000, // 28C + 0b101000, // 29C + 0b101100 // 30C +}; + +const uint8_t kBosch144FahrenheitMin = 60; +const uint8_t kBosch144FahrenheitMax = 86; +const uint8_t kBosch144FahrenheitMap[] = { + // Bit[0] to Section 3: TempS4 + // Bit[1] to Section 3: TempS3 + // Bit[1-4] to Section 1: TempS1 + 0b000010, // 60F + 0b000011, // 61F + 0b000000, // 62F + 0b000001, // 63F + 0b000100, // 64F + 0b000101, // 65F + 0b001100, // 66F + 0b001101, // 67F + 0b001000, // 68F + 0b001001, // 69F + 0b011000, // 70F + 0b011001, // 71F + 0b011100, // 72F + 0b010100, // 73F + 0b010101, // 74F + 0b010000, // 75F + 0b010001, // 76F + 0b110000, // 77F + 0b110001, // 78F + 0b110100, // 79F + 0b110101, // 80F + 0b100100, // 81F + 0b100000, // 82F + 0b100001, // 83F + 0b101000, // 84F + 0b101001, // 85F + 0b101100 // 86F }; // "OFF" is a 96bit-message the same as Coolix protocol @@ -100,37 +138,40 @@ const uint8_t kBosch144DefaultState[kBosch144StateLength] = { union Bosch144Protocol { uint8_t raw[kBosch144StateLength]; ///< The state in IR code form. struct { - uint8_t :8; // Fixed value 0b10110010 / 0xB2. ############ - uint8_t InnvertS1_1:8; // Invert byte 0b01001101 / 0x4D # - uint8_t :5; // not used (without timer use) # - uint8_t FanS1 :3; // Fan speed bits in Section 1 # - uint8_t InnvertS1_2:8; // Invert byte # Section 1 = - uint8_t :2; // not used (without timer use) # Sektion 2 - uint8_t ModeS1 :2; // Operation mode bits S1 # - uint8_t TempS1 :4; // Desired temperature (Celsius) S2 # - uint8_t InnvertS1_3:8; // Invert byte (without timer use) ############ - - uint8_t :8; // Fixed value 0b10110010 / 0xB2. ############ - uint8_t InnvertS2_1:8; // Invert byte 0b01001101 / 0x4D # - uint8_t :5; // not used (without timer use) # - uint8_t FanS2 :3; // Fan speed bits in Section 2 # - uint8_t InnvertS2_2:8; // Invert byte # Section 2 = - uint8_t :2; // not used (without timer use) # Sektion 1 - uint8_t ModeS2 :2; // Operation mode bits S2 # - uint8_t TempS2 :4; // Desired temperature (Celsius) S2 # - uint8_t InnvertS2_3:8; // Invert byte (without timer use) ########### - - uint8_t :8; // Fixed value 0b11010101 / 0xD5 ########### - uint8_t ModeS3 :1; // ModeBit in Section 3 # - uint8_t FanS3 :6; // Fan speed bits in Section 3 # - uint8_t :1; // Unknown # - uint8_t :7; // Unknown # - uint8_t Quiet :1; // Silent-Mode # Section 3 - uint8_t :4; // Unknown # - uint8_t TempS3 :1; // Desired temp. Bit in Section3 # - uint8_t :3; // Unknown # - uint8_t :8; // Unknown # - uint8_t ChecksumS3 :8; // Checksum from byte 13-17 ########### + uint8_t :8; // Fixed value 0b10110010 / 0xB2. ############ + uint8_t InnvertS1_1 :8; // Invert byte 0b01001101 / 0x4D # + uint8_t :5; // not used (without timer use) # + uint8_t FanS1 :3; // Fan speed bits in Section 1 # + uint8_t InnvertS1_2 :8; // Invert byte # Section 1 + uint8_t :2; // not used (without timer use) # = + uint8_t ModeS1 :2; // Operation mode bits S1 # Section 2 + uint8_t TempS1 :4; // Desired temperature (Celsius) S2 # + uint8_t InnvertS1_3 :8; // Invert byte (without timer use) ############ + + uint8_t :8; // Fixed value 0b10110010 / 0xB2. ############ + uint8_t InnvertS2_1 :8; // Invert byte 0b01001101 / 0x4D # + uint8_t :5; // not used (without timer use) # + uint8_t FanS2 :3; // Fan speed bits in Section 2 # + uint8_t InnvertS2_2 :8; // Invert byte # Section 2 + uint8_t :2; // not used (without timer use) # = + uint8_t ModeS2 :2; // Operation mode bits S2 # Section 1 + uint8_t TempS2 :4; // Desired temperature (Celsius) S2 # + uint8_t InnvertS2_3 :8; // Invert byte (without timer use) ########### + + uint8_t :8; // Fixed value 0b11010101 / 0xD5 ########### + uint8_t ModeS3 :1; // ModeBit in Section 3 # + uint8_t FanS3 :6; // Fan speed bits in Section 3 # + uint8_t :1; // Unknown # + uint8_t :5; // Unknown # + uint8_t TempS4 :1; // Desired temp # + uint8_t :1; // Unknown # + uint8_t Quiet :1; // Silent-Mode # Section 3 + uint8_t UseFahrenheit :1; // Fahrenheit or Celcius # + uint8_t :3; // Unknown # + uint8_t TempS3 :1; // Desired temp. Bit in Section3 # + uint8_t :3; // Unknown # + uint8_t :8; // Unknown # + uint8_t ChecksumS3 :8; // Checksum from byte 13-17 ########### }; }; @@ -153,8 +194,10 @@ class IRBosch144AC { void begin(); void setPower(const bool state); bool getPower(void) const; - void setTemp(const uint8_t temp); + void setTemp(const uint8_t temp, const bool fahrenheit = false); uint8_t getTemp(void) const; + void setUseFahrenheit(const bool on); + bool getUseFahrenheit(void) const; void setFan(const uint16_t speed); uint16_t getFan(void) const; void setMode(const uint8_t mode); diff --git a/test/ir_Bosch_test.cpp b/test/ir_Bosch_test.cpp index dd9402b69..39c97ba36 100644 --- a/test/ir_Bosch_test.cpp +++ b/test/ir_Bosch_test.cpp @@ -75,6 +75,57 @@ TEST(TestDecodeBosch144, RealExample) { ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); } +TEST(TestDecodeBosch144, DurastarExample) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + + // Mode: Heat; Fan: auto ; Temp: 73°F + uint16_t rawData[299] = { + 4382, 4414, 522, 1630, 522, 552, 550, 1600, 538, 1616, 540, 532, 548, + 526, 550, 1602, 548, 528, 548, 528, 550, 1604, 522, 552, 548, 528, 548, + 1604, 522, 1630, 546, 528, 548, 1604, 522, 1628, 524, 552, 548, 1604, + 546, 1606, 548, 1604, 522, 1630, 520, 1632, 546, 1604, 522, 554, 522, + 1630, 520, 554, 522, 552, 522, 552, 524, 552, 546, 530, 546, 528, 548, + 528, 548, 1604, 546, 528, 522, 1630, 522, 1630, 522, 1630, 546, 528, 524, + 552, 522, 1630, 524, 552, 524, 1630, 520, 554, 522, 554, 522, 554, 548, + 1606, 520, 1630, 522, 5224, 4404, 4390, 522, 1630, 522, 554, 520, 1632, + 520, 1632, 520, 554, 520, 556, 520, 1632, 520, 556, 520, 556, 520, 1632, + 518, 558, 518, 556, 518, 1634, 518, 1634, 518, 558, 518, 1634, 518, 1634, + 518, 558, 516, 1636, 516, 1636, 516, 1636, 514, 1638, 514, 1638, 514, + 1638, 514, 582, 494, 1658, 492, 582, 492, 584, 492, 584, 490, 584, 492, + 584, 490, 586, 488, 586, 488, 1664, 488, 588, 486, 1666, 484, 1666, 486, + 1666, 484, 590, 484, 592, 484, 1668, 484, 592, 484, 1666, 486, 590, 484, + 592, 484, 592, 484, 1668, 484, 1666, 486, 5262, 4344, 4450, 484, 1668, + 484, 1668, 482, 592, 484, 1668, 482, 592, 484, 1680, 472, 594, 482, 1668, + 482, 616, 460, 1692, 460, 1692, 458, 616, 460, 616, 460, 1692, 460, 1692, + 458, 616, 460, 616, 458, 618, 458, 616, 460, 616, 458, 616, 460, 614, + 460, 616, 460, 616, 458, 616, 460, 616, 460, 616, 460, 616, 460, 616, + 460, 616, 460, 616, 458, 1692, 458, 616, 460, 616, 460, 616, 460, 616, + 444, 632, 438, 638, 434, 642, 434, 642, 434, 640, 434, 640, 434, 1718, + 434, 1718, 434, 1718, 434, 1718, 432, 644, 432, 642, 434 + }; // DURASTAR DRAW09F2A + + uint8_t expectedState[18] = { + 0xB2, 0x4D, 0xBF, 0x40, 0x5C, 0xA3, + 0xB2, 0x4D, 0xBF, 0x40, 0x5C, 0xA3, + 0xD5, 0x66, 0x00, 0x01, 0x00, 0x3C}; + + irsend.begin(); + irsend.reset(); + + irsend.sendRaw(rawData, 299, 38000); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(decode_type_t::BOSCH144, irsend.capture.decode_type); + EXPECT_EQ(kBosch144Bits, irsend.capture.bits); + EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits); + EXPECT_EQ( + "Power: On, Mode: 6 (Heat), Fan: 0 (Auto), Temp: 73F, Quiet: Off", + IRAcUtils::resultAcToString(&irsend.capture)); + stdAc::state_t result, prev; + ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); +} + TEST(TestDecodeBosch144, SyntheticSelfDecode) { IRsendTest irsend(kGpioUnused); IRrecv irrecv(kGpioUnused);