diff --git a/src/IRac.cpp b/src/IRac.cpp index e14f05b62..8f6676889 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -20,6 +20,7 @@ #include "ir_Airwell.h" #include "ir_Amcor.h" #include "ir_Argo.h" +#include "ir_Bosch.h" #include "ir_Carrier.h" #include "ir_Coolix.h" #include "ir_Corona.h" @@ -161,6 +162,9 @@ bool IRac::isProtocolSupported(const decode_type_t protocol) { #if SEND_ARGO case decode_type_t::ARGO: #endif +#if SEND_BOSCH144 + case decode_type_t::BOSCH144: +#endif #if SEND_CARRIER_AC64 case decode_type_t::CARRIER_AC64: #endif // SEND_CARRIER_AC64 @@ -459,6 +463,47 @@ void IRac::argo(IRArgoAC *ac, } #endif // SEND_ARGO +#if SEND_BOSCH144 +/// Send a Bosch144 A/C message with the supplied settings. +/// @note May result in multiple messages being sent. +/// @param[in, out] ac A Ptr to an IRBosch144AC object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @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) { + ac->begin(); + ac->setPower(on); + if (!on) { + // after turn off AC no more commands should + // be accepted + ac->send(); + return; + } + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setMode(ac->convertMode(mode)); + ac->setQuiet(quiet); + ac->send(); // Send the state, which will also power on the unit. + // The following are all options/settings that create their own special + // messages. Often they only make sense to be sent after the unit is turned + // on. For instance, assuming a person wants to have the a/c on and in turbo + // mode. If we send the turbo message, it is ignored if the unit is off. + // Hence we send the special mode/setting messages after a normal message + // which will turn on the device. + // No Filter setting available. + // No Beep setting available. + // No Clock setting available. + // No Econo setting available. + // No Sleep setting available. +} +#endif // SEND_BOSCH144 + #if SEND_CARRIER_AC64 /// Send a Carrier 64-bit A/C message with the supplied settings. /// @param[in, out] ac A Ptr to an IRCarrierAc64 object to use. @@ -2759,6 +2804,14 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { break; } #endif // SEND_ARGO +#if SEND_BOSCH144 + case BOSCH144: + { + IRBosch144AC ac(_pin, _inverted, _modulation); + bosch144(&ac, send.power, send.mode, degC, send.fanspeed, send.quiet); + break; + } +#endif // SEND_BOSCH144 #if SEND_CARRIER_AC64 case CARRIER_AC64: { @@ -3686,6 +3739,13 @@ namespace IRAcUtils { return ac.toString(); } #endif // DECODE_ARGO +#if DECODE_BOSCH144 + case decode_type_t::BOSCH144: { + IRBosch144AC ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_BOSCH144 #if DECODE_CARRIER_AC64 case decode_type_t::CARRIER_AC64: { IRCarrierAc64 ac(kGpioUnused); @@ -4135,6 +4195,14 @@ namespace IRAcUtils { break; } #endif // DECODE_ARGO +#if DECODE_BOSCH144 + case decode_type_t::BOSCH144: { + IRBosch144AC ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_BOSCH144 #if DECODE_CARRIER_AC64 case decode_type_t::CARRIER_AC64: { IRCarrierAc64 ac(kGpioUnused); diff --git a/src/IRac.h b/src/IRac.h index 8e3ef7582..67f7bf768 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -11,6 +11,7 @@ #include "ir_Airwell.h" #include "ir_Amcor.h" #include "ir_Argo.h" +#include "ir_Bosch.h" #include "ir_Carrier.h" #include "ir_Coolix.h" #include "ir_Corona.h" @@ -134,6 +135,12 @@ class IRac { const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, const bool turbo, const int16_t sleep = -1); #endif // SEND_ARGO +#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 quiet); +#endif // SEND_COOLIX #if SEND_CARRIER_AC64 void carrier64(IRCarrierAc64 *ac, const bool on, const stdAc::opmode_t mode, diff --git a/src/ir_Bosch.cpp b/src/ir_Bosch.cpp index f6e26eb52..c66a1976b 100644 --- a/src/ir_Bosch.cpp +++ b/src/ir_Bosch.cpp @@ -1,112 +1,331 @@ -// Copyright 2022 David Conran -/// @file -/// @brief Support for the Bosch A/C / heatpump protocol -/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1787 - -// Supports: -// Brand: Bosch, Model: CL3000i-Set 26 E A/C -// Brand: Bosch, Model: RG10A(G2S)BGEF remote - -#include -#ifndef ARDUINO -#include -#endif -#include "IRrecv.h" -#include "IRsend.h" -#include "IRtext.h" -#include "IRutils.h" - -// Constants -const uint16_t kBoschHdrMark = 4366; -const uint16_t kBoschBitMark = 502; -const uint16_t kBoschHdrSpace = 4415; -const uint16_t kBoschOneSpace = 1645; -const uint16_t kBoschZeroSpace = 571; -const uint16_t kBoschFooterSpace = 5235; -const uint16_t kBoschFreq = 38000; // Hz. (Guessing the most common frequency.) -const uint16_t kBosch144NrOfSections = 3; - -#if SEND_BOSCH144 -/// Send a Bosch 144-bit / 18-byte message -/// Status: STABLE / Confirmed Working. -/// @param[in] data The message to be sent. -/// @param[in] nbytes The number of bytes of message to be sent. -/// @param[in] repeat The number of times the command is to be repeated. -void IRsend::sendBosch144(const unsigned char data[], const uint16_t nbytes, - const uint16_t repeat) { - // nbytes is required to be a multiple of kBosch144NrOfSections. - if (nbytes % kBosch144NrOfSections != 0) return; - - // Set IR carrier frequency - enableIROut(kBoschFreq); - - for (uint16_t r = 0; r <= repeat; r++) { - const uint16_t kSectionByteSize = nbytes / kBosch144NrOfSections; - for (uint16_t offset = 0; offset < nbytes; offset += kSectionByteSize) - // Section Header + Data + Footer - sendGeneric(kBoschHdrMark, kBoschHdrSpace, - kBoschBitMark, kBoschOneSpace, - kBoschBitMark, kBoschZeroSpace, - kBoschBitMark, kBoschFooterSpace, - data + offset, kSectionByteSize, - kBoschFreq, true, 0, kDutyDefault); - space(kDefaultMessageGap); // Complete guess - } -} - -#endif // SEND_BOSCH144 - -#if DECODE_BOSCH144 -/// Decode the supplied Bosch 144-bit / 18-byte A/C message. -/// Status: STABLE / Confirmed Working. -/// @param[in,out] results Ptr to the data to decode & where to store the decode -/// result. -/// @param[in] offset The starting index to use when attempting to decode the -/// raw data. Typically/Defaults to kStartOffset. -/// @param[in] nbits The number of data bits to expect. -/// @param[in] strict Flag indicating if we should perform strict matching. -/// @return A boolean. True if it can decode it, false if it can't. -bool IRrecv::decodeBosch144(decode_results *results, uint16_t offset, - const uint16_t nbits, const bool strict) { - if (results->rawlen < 2 * nbits + - kBosch144NrOfSections * (kHeader + kFooter) - - 1 + offset) - return false; // Can't possibly be a valid BOSCH144 message. - if (strict && nbits != kBosch144Bits) - return false; // Not strictly a BOSCH144 message. - if (nbits % 8 != 0) // nbits has to be a multiple of nr. of bits in a byte. - return false; - if (nbits % kBosch144NrOfSections != 0) - return false; // nbits has to be a multiple of kBosch144NrOfSections. - const uint16_t kSectionBits = nbits / kBosch144NrOfSections; - const uint16_t kSectionBytes = kSectionBits / 8; - const uint16_t kNBytes = kSectionBytes * kBosch144NrOfSections; - // Capture each section individually - for (uint16_t pos = 0, section = 0; - pos < kNBytes; - pos += kSectionBytes, section++) { - uint16_t used = 0; - // Section Header + Section Data + Section Footer - used = matchGeneric(results->rawbuf + offset, results->state + pos, - results->rawlen - offset, kSectionBits, - kBoschHdrMark, kBoschHdrSpace, - kBoschBitMark, kBoschOneSpace, - kBoschBitMark, kBoschZeroSpace, - kBoschBitMark, kBoschFooterSpace, - section >= kBosch144NrOfSections - 1, - _tolerance, kMarkExcess, true); - if (!used) return false; // Didn't match. - offset += used; - } - - // Compliance - - // Success - results->decode_type = decode_type_t::BOSCH144; - results->bits = nbits; - // No need to record the state as we stored it as we decoded it. - // As we use result->state, we don't record value, address, or command as it - // is a union data type. - return true; -} -#endif // DECODE_BOSCH144 +// Copyright 2022 David Conran +// Copyright 2022 Nico Thien +/// @file +/// @brief Support for the Bosch A/C / heatpump protocol +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1787 + +#include "ir_Bosch.h" + +#if SEND_BOSCH144 +/// Send a Bosch 144-bit / 18-byte message (96-bit message are also possible) +/// Status: BETA / Probably Working. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The number of bytes of message to be sent. +/// @param[in] repeat The number of times the command is to be repeated. +void IRsend::sendBosch144(const unsigned char data[], const uint16_t nbytes, + const uint16_t repeat) { + // nbytes is required to be a multiple of kBosch144BytesPerSection. + if (nbytes % kBosch144BytesPerSection != 0)return; + // Set IR carrier frequency + enableIROut(kBoschFreq); + + for (uint16_t r = 0; r <= repeat; r++) { + for (uint16_t offset=0; offset < nbytes; offset += kBosch144BytesPerSection) + // Section Header + Data + Footer + sendGeneric(kBoschHdrMark, kBoschHdrSpace, + kBoschBitMark, kBoschOneSpace, + kBoschBitMark, kBoschZeroSpace, + kBoschBitMark, kBoschFooterSpace, + data + offset, kBosch144BytesPerSection, + kBoschFreq, true, 0, kDutyDefault); + space(kDefaultMessageGap); // Complete guess + } +} + +#endif // SEND_BOSCH144 + +/// Class constructor. +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRBosch144AC::IRBosch144AC(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the internal state to a fixed known good state. +void IRBosch144AC::stateReset(void) { + setRaw(kBosch144DefaultState, kBosch144StateLength); + setPower(true); +} + +/// Set up hardware to be able to send a message. +void IRBosch144AC::begin(void) { _irsend.begin(); } + +#if SEND_BOSCH144 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRBosch144AC::send(const uint16_t repeat) { + if (!powerFlag) { // "Off" is a 96bit message + _irsend.sendBosch144(kBosch144Off, sizeof(kBosch144Off), repeat); + } else { + _irsend.sendBosch144(getRaw(), kBosch144StateLength, repeat); + } // other 96bit messages are not yet supported +} +#endif // SEND_BOSCH144 + +/// Get a copy of the internal state as a valid code for this protocol. +/// @return A valid code for this protocol based on the current internal state. +unsigned char* IRBosch144AC::getRaw(void) { + setInvertBytes(); + setCheckSumS3(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +/// @param[in] length Size of the array being passed in in bytes. +void IRBosch144AC::setRaw(const uint8_t new_code[], const uint16_t length) { + const uint16_t len = min(length, kBosch144StateLength); + const uint16_t lenOff = sizeof(kBosch144Off); +// Is it an off message? + if (memcmp(kBosch144Off, new_code, min(lenOff, len)) == 0) + setPower(false); // It is. + else + setPower(true); + memcpy(_.raw, new_code, len); +} + +void IRBosch144AC::setPower(const bool on) { + powerFlag = on; +} + +bool IRBosch144AC::getPower(void) const { + return powerFlag; +} + +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 +} + +/// 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]); +} + +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; + } + } + return retemp; +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRBosch144AC::setFan(const uint16_t speed) { + _.FanS1 = _.FanS2 = speed >> 6; // save 3 bits in S1 and S2 + _.FanS3 = speed & 0b111111; // save 6 bits in Section3 +} + +uint16_t IRBosch144AC::getFan(void) const { + return (_.FanS1 << 6) + _.FanS3; +} + +/// Set the desired operation mode. +/// @param[in] mode The desired operation mode. +void IRBosch144AC::setMode(const uint8_t mode) { + _.ModeS1 = _.ModeS2 = mode >> 1; // save 2 bits in S1 and S2 + _.ModeS3 = mode & 0b1; // save 1 bit in Section3 + if (mode == kBosch144Auto || mode == kBosch144Dry) { + _.FanS1 = _.FanS2 = 0b000; // save 3 bits in S1 and S2 + _.FanS3 = kBosch144FanAuto0; // save 6 bits in Section3 + } +} + +uint8_t IRBosch144AC::getMode(void) const { + return (_.ModeS1 << 1) + _.ModeS3; +} + +/// Set the Quiet mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRBosch144AC::setQuiet(const bool on) { + _.Quiet = on; // save 1 bit in Section3 + setFan(kBosch144FanAuto); // set Fan -> Auto +} + +/// Get the Quiet mode of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRBosch144AC::getQuiet(void) const { return _.Quiet; } + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRBosch144AC::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: + return kBosch144Cool; + case stdAc::opmode_t::kHeat: + return kBosch144Heat; + case stdAc::opmode_t::kDry: + return kBosch144Dry; + case stdAc::opmode_t::kFan: + return kBosch144Fan; + default: + return kBosch144Auto; + } +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint16_t IRBosch144AC::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + return kBosch144Fan20; + case stdAc::fanspeed_t::kLow: + return kBosch144Fan40; + case stdAc::fanspeed_t::kMedium: + return kBosch144Fan60; + case stdAc::fanspeed_t::kHigh: + return kBosch144Fan80; + case stdAc::fanspeed_t::kMax: + return kBosch144Fan100; + default: + return kBosch144FanAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRBosch144AC::toCommonMode(const uint8_t mode) { + switch (mode) { + case kBosch144Cool: return stdAc::opmode_t::kCool; + case kBosch144Heat: return stdAc::opmode_t::kHeat; + case kBosch144Dry: return stdAc::opmode_t::kDry; + case kBosch144Fan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRBosch144AC::toCommonFanSpeed(const uint16_t speed) { + switch (speed) { + case kBosch144Fan100: return stdAc::fanspeed_t::kMax; + case kBosch144Fan80: return stdAc::fanspeed_t::kHigh; + case kBosch144Fan60: return stdAc::fanspeed_t::kMedium; + case kBosch144Fan40: return stdAc::fanspeed_t::kLow; + case kBosch144Fan20: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRBosch144AC::toCommon(void) const { + stdAc::state_t result{}; + result.protocol = decode_type_t::BOSCH144; + result.power = getPower(); + result.mode = toCommonMode(getMode()); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(getFan()); + result.quiet = getQuiet(); + // Not supported. + result.model = -1; + result.turbo = false; + result.swingv = stdAc::swingv_t::kOff; + result.swingh = stdAc::swingh_t::kOff; + result.light = false; + result.filter = false; + result.econo = false; + result.clean = false; + result.beep = false; + result.clock = -1; + result.sleep = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRBosch144AC::toString(void) const { + uint8_t mode = getMode(); + uint8_t fan = static_cast(toCommonFanSpeed(getFan())); + String result = ""; + result.reserve(70); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(getPower(), kPowerStr, false); + result += addModeToString(mode, kBosch144Auto, kBosch144Cool, + kBosch144Heat, kBosch144Dry, kBosch144Fan); + result += addFanToString(fan, static_cast(stdAc::fanspeed_t::kMax), + static_cast(stdAc::fanspeed_t::kMin), + static_cast(stdAc::fanspeed_t::kAuto), + static_cast(stdAc::fanspeed_t::kAuto), + static_cast(stdAc::fanspeed_t::kMedium)); + result += addTempToString(getTemp()); + result += addBoolToString(_.Quiet, kQuietStr); + return result; +} + +void IRBosch144AC::setInvertBytes() { + for (uint8_t i = 0; i <= 10; i += 2) { + _.raw[i + 1] = ~_.raw[i]; + } +} + +void IRBosch144AC::setCheckSumS3() { + _.ChecksumS3 = sumBytes(&(_.raw[12]), 5); +} + +#if DECODE_BOSCH144 +/// Decode the supplied Bosch 144-bit / 18-byte A/C message. +/// Status: STABLE / Confirmed Working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// result. +/// @param[in] offset The starting index to use when attempting to decode the +/// raw data. Typically/Defaults to kStartOffset. +/// @param[in] nbits The number of data bits to expect. +/// @param[in] strict Flag indicating if we should perform strict matching. +/// @return A boolean. True if it can decode it, false if it can't. +bool IRrecv::decodeBosch144(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * nbits + + kBosch144NrOfSections * (kHeader + kFooter) - + 1 + offset) + return false; // Can't possibly be a valid BOSCH144 message. + if (strict && nbits != kBosch144Bits) + return false; // Not strictly a BOSCH144 message. + if (nbits % 8 != 0) // nbits has to be a multiple of nr. of bits in a byte. + return false; + if (nbits % kBosch144NrOfSections != 0) + return false; // nbits has to be a multiple of kBosch144NrOfSections. + const uint16_t kSectionBits = nbits / kBosch144NrOfSections; + const uint16_t kSectionBytes = kSectionBits / 8; + const uint16_t kNBytes = kSectionBytes * kBosch144NrOfSections; + // Capture each section individually + for (uint16_t pos = 0, section = 0; + pos < kNBytes; + pos += kSectionBytes, section++) { + uint16_t used = 0; + // Section Header + Section Data + Section Footer + used = matchGeneric(results->rawbuf + offset, results->state + pos, + results->rawlen - offset, kSectionBits, + kBoschHdrMark, kBoschHdrSpace, + kBoschBitMark, kBoschOneSpace, + kBoschBitMark, kBoschZeroSpace, + kBoschBitMark, kBoschFooterSpace, + section >= kBosch144NrOfSections - 1, + _tolerance, kMarkExcess, true); + if (!used) return false; // Didn't match. + offset += used; + } + + // Compliance + + // Success + results->decode_type = decode_type_t::BOSCH144; + results->bits = nbits; + // No need to record the state as we stored it as we decoded it. + // As we use result->state, we don't record value, address, or command as it + // is a union data type. + return true; +} +#endif // DECODE_BOSCH144 diff --git a/src/ir_Bosch.h b/src/ir_Bosch.h new file mode 100644 index 000000000..071c132cf --- /dev/null +++ b/src/ir_Bosch.h @@ -0,0 +1,193 @@ +// Copyright 2022 Nico Thien +/// @file +/// @brief Support for Bosch A/C protocol +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1787 + +// Supports: +// Brand: Bosch, Model: CL3000i-Set 26 E A/C +// Brand: Bosch, Model: RG10A(G2S)BGEF remote + + +#ifndef IR_BOSCH_H_ +#define IR_BOSCH_H_ + +#define __STDC_LIMIT_MACROS +#include +#include +#include +#include "IRremoteESP8266.h" +#include "IRsend.h" +#include "IRrecv.h" +#include "IRtext.h" +#include "IRutils.h" +#ifndef UNIT_TEST +#include +#endif +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + +// Constants +const uint16_t kBoschHdrMark = 4366; +const uint16_t kBoschBitMark = 502; +const uint16_t kBoschHdrSpace = 4415; +const uint16_t kBoschOneSpace = 1645; +const uint16_t kBoschZeroSpace = 571; +const uint16_t kBoschFooterSpace = 5235; +const uint16_t kBoschFreq = 38000; // Hz. (Guessing the most common frequency.) +const uint16_t kBosch144NrOfSections = 3; +const uint16_t kBosch144BytesPerSection = 6; + +using irutils::addBoolToString; +using irutils::addModeToString; +using irutils::addFanToString; +using irutils::addTempToString; +using std::min; +using std::max; +using std::memcpy; +using std::memcmp; + +// Modes Bit[0] to Section 3 Bit[1-2] to Section 1 +// ModeS3 ModeS1 +const uint8_t kBosch144Cool = 0b000; +const uint8_t kBosch144Dry = 0b011; +const uint8_t kBosch144Auto = 0b101; +const uint8_t kBosch144Heat = 0b110; +const uint8_t kBosch144Fan = 0b010; + +// Fan Control Bit[0-5] to Section 3 Bit[6-8] to Section 1 +// FanS3 FanS1 +const uint16_t kBosch144Fan20 = 0b111001010; +const uint16_t kBosch144Fan40 = 0b100010100; +const uint16_t kBosch144Fan60 = 0b010011110; +const uint16_t kBosch144Fan80 = 0b001101000; +const uint16_t kBosch144Fan100 = 0b001110010; +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 +}; + +// "OFF" is a 96bit-message the same as Coolix protocol +const uint8_t kBosch144Off[] = {0xB2, 0x4D, 0x7B, 0x84, 0xE0, 0x1F, + 0xB2, 0x4D, 0x7B, 0x84, 0xE0, 0x1F}; + +// On, 25C, Mode: Auto +const uint8_t kBosch144DefaultState[kBosch144StateLength] = { + 0xB2, 0x4D, 0x1F, 0xE0, 0xC8, 0x37, + 0xB2, 0x4D, 0x1F, 0xE0, 0xC8, 0x37, + 0xD5, 0x65, 0x00, 0x00, 0x00, 0x3A}; + +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 ########### + }; +}; + +// Classes + +/// Class for handling detailed Bosch144 A/C messages. +class IRBosch144AC { + public: + explicit IRBosch144AC(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_BOSCH144 + void send(const uint16_t repeat = 0); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_BOSCH144 + void begin(); + void setPower(const bool state); + bool getPower(void) const; + void setTemp(const uint8_t temp); + uint8_t getTemp(void) const; + void setFan(const uint16_t speed); + uint16_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setQuiet(const bool on); + bool getQuiet(void) const; + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[], + const uint16_t length = kBosch144StateLength); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint16_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint16_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif + Bosch144Protocol _; ///< The state of the IR remote in IR code form. + + // Internal State settings + bool powerFlag; + + void setInvertBytes(); + void setCheckSumS3(); + void setTempRaw(const uint8_t code); + uint8_t getTempRaw(void) const; +}; + +#endif // IR_BOSCH_H_ diff --git a/test/ir_Bosch_test.cpp b/test/ir_Bosch_test.cpp index e9b6dc9ce..dd9402b69 100644 --- a/test/ir_Bosch_test.cpp +++ b/test/ir_Bosch_test.cpp @@ -13,7 +13,7 @@ TEST(TestUtils, Housekeeping) { ASSERT_EQ("BOSCH144", typeToString(decode_type_t::BOSCH144)); ASSERT_EQ(decode_type_t::BOSCH144, strToDecodeType("BOSCH144")); ASSERT_TRUE(hasACState(decode_type_t::BOSCH144)); - ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::BOSCH144)); + ASSERT_TRUE(IRac::isProtocolSupported(decode_type_t::BOSCH144)); ASSERT_EQ(kBosch144Bits, IRsend::defaultBits(decode_type_t::BOSCH144)); ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::BOSCH144)); } @@ -69,10 +69,10 @@ TEST(TestDecodeBosch144, RealExample) { EXPECT_EQ(kBosch144Bits, irsend.capture.bits); EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits); EXPECT_EQ( - "", + "Power: On, Mode: 0 (Cool), Fan: 5 (High), Temp: 16C, Quiet: Off", IRAcUtils::resultAcToString(&irsend.capture)); stdAc::state_t result, prev; - ASSERT_FALSE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); + ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); } TEST(TestDecodeBosch144, SyntheticSelfDecode) { @@ -93,8 +93,8 @@ TEST(TestDecodeBosch144, SyntheticSelfDecode) { EXPECT_EQ(kBosch144Bits, irsend.capture.bits); EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits); EXPECT_EQ( - "", + "Power: On, Mode: 0 (Cool), Fan: 5 (High), Temp: 16C, Quiet: Off", IRAcUtils::resultAcToString(&irsend.capture)); stdAc::state_t result, prev; - ASSERT_FALSE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); + ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); }