Skip to content

Commit

Permalink
Experimental basic support for Bosch 144bit protocol. (#1822)
Browse files Browse the repository at this point in the history
* Added `sendBosch144()` & `decodeBosch144()`
* TODO: Add integrity (strict) checks.
* Add basic unit test coverage.

For #1787
  • Loading branch information
crankyoldgit committed Jun 16, 2022
1 parent cdacb95 commit 4888fd7
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 1 deletion.
5 changes: 5 additions & 0 deletions src/IRrecv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,11 @@ bool IRrecv::decode(decode_results *results, irparams_t *save,
DPRINTLN("Attempting Sharp decode");
if (decodeSharp(results, offset)) return true;
#endif
#if DECODE_BOSCH144
DPRINTLN("Attempting Bosch 144-bit decode");
// Bosch is similar to Coolix, so it must be attempted before decodeCOOLIX.
if (decodeBosch144(results, offset)) return true;
#endif // DECODE_BOSCH144
#if DECODE_COOLIX
DPRINTLN("Attempting Coolix 24-bit decode");
if (decodeCOOLIX(results, offset)) return true;
Expand Down
6 changes: 6 additions & 0 deletions src/IRrecv.h
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,12 @@ class IRrecv {
const uint16_t nbits = kTcl96AcBits,
const bool strict = true);
#endif // DECODE_TCL96AC
#if DECODE_BOSCH144
bool decodeBosch144(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kBosch144Bits,
const bool strict = true);
#endif // DECODE_BOSCH144
};

#endif // IRRECV_H_
13 changes: 12 additions & 1 deletion src/IRremoteESP8266.h
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,13 @@
#define SEND_CLIMABUTLER _IR_ENABLE_DEFAULT_
#endif // SEND_CLIMABUTLER

#ifndef DECODE_BOSCH144
#define DECODE_BOSCH144 _IR_ENABLE_DEFAULT_
#endif // DECODE_BOSCH144
#ifndef SEND_BOSCH144
#define SEND_BOSCH144 _IR_ENABLE_DEFAULT_
#endif // SEND_BOSCH144

#if (DECODE_ARGO || DECODE_DAIKIN || DECODE_FUJITSU_AC || DECODE_GREE || \
DECODE_KELVINATOR || DECODE_MITSUBISHI_AC || DECODE_TOSHIBA_AC || \
DECODE_TROTEC || DECODE_HAIER_AC || DECODE_HITACHI_AC || \
Expand All @@ -920,6 +927,7 @@
DECODE_SANYO_AC88 || DECODE_RHOSS || DECODE_HITACHI_AC264 || \
DECODE_KELON168 || DECODE_HITACHI_AC296 || DECODE_CARRIER_AC128 || \
DECODE_DAIKIN200 || DECODE_HAIER_AC160 || DECODE_TCL96AC || \
DECODE_BOSCH144 || \
false)
// Add any DECODE to the above if it uses result->state (see kStateSizeMax)
// you might also want to add the protocol to hasACState function
Expand Down Expand Up @@ -1079,8 +1087,9 @@ enum decode_type_t {
TOTO,
CLIMABUTLER,
TCL96AC,
BOSCH144, // 120
// Add new entries before this one, and update it to point to the last entry.
kLastDecodeType = TCL96AC,
kLastDecodeType = BOSCH144,
};

// Message lengths & required repeat values
Expand All @@ -1101,6 +1110,8 @@ const uint16_t kArgoStateLength = 12;
const uint16_t kArgoBits = kArgoStateLength * 8;
const uint16_t kArgoDefaultRepeat = kNoRepeat;
const uint16_t kArrisBits = 32;
const uint16_t kBosch144StateLength = 18;
const uint16_t kBosch144Bits = kBosch144StateLength * 8;
const uint16_t kCoolixBits = 24;
const uint16_t kCoolix48Bits = kCoolixBits * 2;
const uint16_t kCoolixDefaultRepeat = kSingleRepeat;
Expand Down
7 changes: 7 additions & 0 deletions src/IRsend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,8 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) {
return 64;
case ARGO:
return kArgoBits;
case BOSCH144:
return kBosch144Bits;
case CORONA_AC:
return kCoronaAcBits;
case CARRIER_AC128:
Expand Down Expand Up @@ -1148,6 +1150,11 @@ bool IRsend::send(const decode_type_t type, const uint8_t *state,
sendArgo(state, nbytes);
break;
#endif // SEND_ARGO
#if SEND_BOSCH144
case BOSCH144:
sendBosch144(state, nbytes);
break;
#endif // SEND_BOSCH144
#if SEND_CARRIER_AC128
case CARRIER_AC128:
sendCarrierAC128(state, nbytes);
Expand Down
5 changes: 5 additions & 0 deletions src/IRsend.h
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,11 @@ class IRsend {
const uint16_t nbits = kClimaButlerBits,
const uint16_t repeat = kNoRepeat);
#endif // SEND_CLIMABUTLER
#if SEND_BOSCH144
void sendBosch144(const unsigned char data[],
const uint16_t nbytes = kBosch144StateLength,
const uint16_t repeat = kNoRepeat);
#endif // SEND_BOSCH144

protected:
#ifdef UNIT_TEST
Expand Down
1 change: 1 addition & 0 deletions src/IRtext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ IRTEXT_CONST_BLOB_DECL(kAllProtocolNamesStr) {
D_STR_TOTO "\x0"
D_STR_CLIMABUTLER "\x0"
D_STR_TCL96AC "\x0"
D_STR_BOSCH144 "\x0"
///< New protocol strings should be added just above this line.
"\x0" ///< This string requires double null termination.
};
Expand Down
1 change: 1 addition & 0 deletions src/IRutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ bool hasACState(const decode_type_t protocol) {
// This is kept sorted by name
case AMCOR:
case ARGO:
case BOSCH144:
case CARRIER_AC128:
case CORONA_AC:
case DAIKIN:
Expand Down
112 changes: 112 additions & 0 deletions src/ir_Bosch.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// 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 <algorithm>
#ifndef ARDUINO
#include <string>
#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
6 changes: 6 additions & 0 deletions src/locale/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,12 @@ D_STR_INDIRECT " " D_STR_MODE
#ifndef D_STR_ARRIS
#define D_STR_ARRIS "ARRIS"
#endif // D_STR_ARRIS
#ifndef D_STR_BOSCH
#define D_STR_BOSCH "BOSCH"
#endif // D_STR_BOSCH
#ifndef D_STR_BOSCH144
#define D_STR_BOSCH144 D_STR_BOSCH "144"
#endif // D_STR_BOSCH144
#ifndef D_STR_BOSE
#define D_STR_BOSE "BOSE"
#endif // D_STR_BOSE
Expand Down
100 changes: 100 additions & 0 deletions test/ir_Bosch_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright 2022 David Conran

#include "IRac.h"
#include "IRrecv.h"
#include "IRrecv_test.h"
#include "IRsend.h"
#include "IRsend_test.h"
#include "gtest/gtest.h"


TEST(TestUtils, Housekeeping) {
// Bosch144
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_EQ(kBosch144Bits, IRsend::defaultBits(decode_type_t::BOSCH144));
ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::BOSCH144));
}

// Tests for decodeBosch144().

// Decode normal Bosch144 messages.
TEST(TestDecodeBosch144, RealExample) {
IRsendTest irsend(kGpioUnused);
IRrecv irrecv(kGpioUnused);

// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1787#issuecomment-1099993189
// Mode: Cool; Fan: 100% ; Temp: 16°C
const uint16_t rawData[299] = {
4380, 4400,
528, 1646, 504, 570, 504, 1646, 504, 1646, 504, 572, 502, 570, 504, 1646,
504, 570, 504, 572, 502, 1646, 504, 570, 502, 570, 502, 1648, 502, 1646,
502, 570, 502, 1646, 504, 572, 502, 572, 502, 1644, 504, 1646, 504, 1646,
504, 1646, 502, 1648, 500, 1646, 504, 1646, 504, 1646, 504, 572, 502, 570,
504, 570, 504, 570, 504, 570, 504, 570, 506, 570, 502, 572, 502, 570, 502,
572, 502, 572, 502, 572, 502, 572, 502, 572, 500, 1648, 502, 1644, 502,
1646, 504, 1646, 502, 1646, 504, 1646, 504, 1644, 504, 1646,
504, 5234,
4360, 4422,
504, 1646, 502, 596, 478, 1670, 478, 1646, 504, 570, 504, 572, 500, 1646,
502, 572, 502, 572, 502, 1644, 506, 570, 502, 570, 504, 1644, 506, 1644,
502, 574, 502, 1644, 504, 570, 504, 570, 504, 1644, 504, 1646, 504, 1644,
506, 1644, 504, 1646, 504, 1646, 504, 1644, 504, 1646, 502, 570, 504, 570,
504, 570, 504, 570, 502, 570, 504, 570, 502, 572, 502, 570, 504, 570, 504,
570, 504, 570, 502, 572, 502, 570, 506, 570, 504, 1646, 502, 1646, 504,
1646, 504, 1646, 504, 1646, 502, 1644, 504, 1644, 504, 1646,
502, 5236,
4360, 4424,
504, 1646, 504, 1646, 502, 572, 504, 1644, 504, 570, 504, 1646, 504, 570,
502, 1644, 504, 570, 504, 1644, 506, 1646, 502, 572, 502, 572, 502, 1646,
504, 570, 504, 570, 504, 570, 502, 572, 504, 570, 504, 570, 504, 570, 502,
572, 502, 570, 504, 570, 502, 570, 504, 572, 502, 572, 502, 1646, 504,
570, 504, 570, 504, 570, 502, 574, 502, 572, 502, 572, 502, 572, 502, 572,
502, 572, 502, 570, 504, 572, 502, 572, 502, 572, 502, 1646, 504, 572,
502, 570, 502, 1646, 504, 572, 504, 570, 504, 1644,
504}; // COOLIX B23F00
const uint8_t expectedState[kBosch144StateLength] = {
0xB2, 0x4D, 0x3F, 0xC0, 0x00, 0xFF,
0xB2, 0x4D, 0x3F, 0xC0, 0x00, 0xFF,
0xD5, 0x64, 0x00, 0x10, 0x00, 0x49};
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(
"",
IRAcUtils::resultAcToString(&irsend.capture));
stdAc::state_t result, prev;
ASSERT_FALSE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev));
}

TEST(TestDecodeBosch144, SyntheticSelfDecode) {
IRsendTest irsend(kGpioUnused);
IRrecv irrecv(kGpioUnused);
irsend.begin();

irsend.reset();
const uint8_t expectedState[kBosch144StateLength] = {
0xB2, 0x4D, 0x3F, 0xC0, 0x00, 0xFF,
0xB2, 0x4D, 0x3F, 0xC0, 0x00, 0xFF,
0xD5, 0x64, 0x00, 0x10, 0x00, 0x49};
irsend.sendBosch144(expectedState);
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(
"",
IRAcUtils::resultAcToString(&irsend.capture));
stdAc::state_t result, prev;
ASSERT_FALSE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev));
}

0 comments on commit 4888fd7

Please sign in to comment.