Skip to content

Commit 4888fd7

Browse files
authored
Experimental basic support for Bosch 144bit protocol. (#1822)
* Added `sendBosch144()` & `decodeBosch144()` * TODO: Add integrity (strict) checks. * Add basic unit test coverage. For #1787
1 parent cdacb95 commit 4888fd7

File tree

10 files changed

+255
-1
lines changed

10 files changed

+255
-1
lines changed

src/IRrecv.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,11 @@ bool IRrecv::decode(decode_results *results, irparams_t *save,
729729
DPRINTLN("Attempting Sharp decode");
730730
if (decodeSharp(results, offset)) return true;
731731
#endif
732+
#if DECODE_BOSCH144
733+
DPRINTLN("Attempting Bosch 144-bit decode");
734+
// Bosch is similar to Coolix, so it must be attempted before decodeCOOLIX.
735+
if (decodeBosch144(results, offset)) return true;
736+
#endif // DECODE_BOSCH144
732737
#if DECODE_COOLIX
733738
DPRINTLN("Attempting Coolix 24-bit decode");
734739
if (decodeCOOLIX(results, offset)) return true;

src/IRrecv.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,12 @@ class IRrecv {
839839
const uint16_t nbits = kTcl96AcBits,
840840
const bool strict = true);
841841
#endif // DECODE_TCL96AC
842+
#if DECODE_BOSCH144
843+
bool decodeBosch144(decode_results *results,
844+
uint16_t offset = kStartOffset,
845+
const uint16_t nbits = kBosch144Bits,
846+
const bool strict = true);
847+
#endif // DECODE_BOSCH144
842848
};
843849

844850
#endif // IRRECV_H_

src/IRremoteESP8266.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,13 @@
903903
#define SEND_CLIMABUTLER _IR_ENABLE_DEFAULT_
904904
#endif // SEND_CLIMABUTLER
905905

906+
#ifndef DECODE_BOSCH144
907+
#define DECODE_BOSCH144 _IR_ENABLE_DEFAULT_
908+
#endif // DECODE_BOSCH144
909+
#ifndef SEND_BOSCH144
910+
#define SEND_BOSCH144 _IR_ENABLE_DEFAULT_
911+
#endif // SEND_BOSCH144
912+
906913
#if (DECODE_ARGO || DECODE_DAIKIN || DECODE_FUJITSU_AC || DECODE_GREE || \
907914
DECODE_KELVINATOR || DECODE_MITSUBISHI_AC || DECODE_TOSHIBA_AC || \
908915
DECODE_TROTEC || DECODE_HAIER_AC || DECODE_HITACHI_AC || \
@@ -920,6 +927,7 @@
920927
DECODE_SANYO_AC88 || DECODE_RHOSS || DECODE_HITACHI_AC264 || \
921928
DECODE_KELON168 || DECODE_HITACHI_AC296 || DECODE_CARRIER_AC128 || \
922929
DECODE_DAIKIN200 || DECODE_HAIER_AC160 || DECODE_TCL96AC || \
930+
DECODE_BOSCH144 || \
923931
false)
924932
// Add any DECODE to the above if it uses result->state (see kStateSizeMax)
925933
// you might also want to add the protocol to hasACState function
@@ -1079,8 +1087,9 @@ enum decode_type_t {
10791087
TOTO,
10801088
CLIMABUTLER,
10811089
TCL96AC,
1090+
BOSCH144, // 120
10821091
// Add new entries before this one, and update it to point to the last entry.
1083-
kLastDecodeType = TCL96AC,
1092+
kLastDecodeType = BOSCH144,
10841093
};
10851094

10861095
// Message lengths & required repeat values
@@ -1101,6 +1110,8 @@ const uint16_t kArgoStateLength = 12;
11011110
const uint16_t kArgoBits = kArgoStateLength * 8;
11021111
const uint16_t kArgoDefaultRepeat = kNoRepeat;
11031112
const uint16_t kArrisBits = 32;
1113+
const uint16_t kBosch144StateLength = 18;
1114+
const uint16_t kBosch144Bits = kBosch144StateLength * 8;
11041115
const uint16_t kCoolixBits = 24;
11051116
const uint16_t kCoolix48Bits = kCoolixBits * 2;
11061117
const uint16_t kCoolixDefaultRepeat = kSingleRepeat;

src/IRsend.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,8 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) {
686686
return 64;
687687
case ARGO:
688688
return kArgoBits;
689+
case BOSCH144:
690+
return kBosch144Bits;
689691
case CORONA_AC:
690692
return kCoronaAcBits;
691693
case CARRIER_AC128:
@@ -1148,6 +1150,11 @@ bool IRsend::send(const decode_type_t type, const uint8_t *state,
11481150
sendArgo(state, nbytes);
11491151
break;
11501152
#endif // SEND_ARGO
1153+
#if SEND_BOSCH144
1154+
case BOSCH144:
1155+
sendBosch144(state, nbytes);
1156+
break;
1157+
#endif // SEND_BOSCH144
11511158
#if SEND_CARRIER_AC128
11521159
case CARRIER_AC128:
11531160
sendCarrierAC128(state, nbytes);

src/IRsend.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,11 @@ class IRsend {
820820
const uint16_t nbits = kClimaButlerBits,
821821
const uint16_t repeat = kNoRepeat);
822822
#endif // SEND_CLIMABUTLER
823+
#if SEND_BOSCH144
824+
void sendBosch144(const unsigned char data[],
825+
const uint16_t nbytes = kBosch144StateLength,
826+
const uint16_t repeat = kNoRepeat);
827+
#endif // SEND_BOSCH144
823828

824829
protected:
825830
#ifdef UNIT_TEST

src/IRtext.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ IRTEXT_CONST_BLOB_DECL(kAllProtocolNamesStr) {
402402
D_STR_TOTO "\x0"
403403
D_STR_CLIMABUTLER "\x0"
404404
D_STR_TCL96AC "\x0"
405+
D_STR_BOSCH144 "\x0"
405406
///< New protocol strings should be added just above this line.
406407
"\x0" ///< This string requires double null termination.
407408
};

src/IRutils.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ bool hasACState(const decode_type_t protocol) {
172172
// This is kept sorted by name
173173
case AMCOR:
174174
case ARGO:
175+
case BOSCH144:
175176
case CARRIER_AC128:
176177
case CORONA_AC:
177178
case DAIKIN:

src/ir_Bosch.cpp

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright 2022 David Conran
2+
/// @file
3+
/// @brief Support for the Bosch A/C / heatpump protocol
4+
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1787
5+
6+
// Supports:
7+
// Brand: Bosch, Model: CL3000i-Set 26 E A/C
8+
// Brand: Bosch, Model: RG10A(G2S)BGEF remote
9+
10+
#include <algorithm>
11+
#ifndef ARDUINO
12+
#include <string>
13+
#endif
14+
#include "IRrecv.h"
15+
#include "IRsend.h"
16+
#include "IRtext.h"
17+
#include "IRutils.h"
18+
19+
// Constants
20+
const uint16_t kBoschHdrMark = 4366;
21+
const uint16_t kBoschBitMark = 502;
22+
const uint16_t kBoschHdrSpace = 4415;
23+
const uint16_t kBoschOneSpace = 1645;
24+
const uint16_t kBoschZeroSpace = 571;
25+
const uint16_t kBoschFooterSpace = 5235;
26+
const uint16_t kBoschFreq = 38000; // Hz. (Guessing the most common frequency.)
27+
const uint16_t kBosch144NrOfSections = 3;
28+
29+
#if SEND_BOSCH144
30+
/// Send a Bosch 144-bit / 18-byte message
31+
/// Status: STABLE / Confirmed Working.
32+
/// @param[in] data The message to be sent.
33+
/// @param[in] nbytes The number of bytes of message to be sent.
34+
/// @param[in] repeat The number of times the command is to be repeated.
35+
void IRsend::sendBosch144(const unsigned char data[], const uint16_t nbytes,
36+
const uint16_t repeat) {
37+
// nbytes is required to be a multiple of kBosch144NrOfSections.
38+
if (nbytes % kBosch144NrOfSections != 0) return;
39+
40+
// Set IR carrier frequency
41+
enableIROut(kBoschFreq);
42+
43+
for (uint16_t r = 0; r <= repeat; r++) {
44+
const uint16_t kSectionByteSize = nbytes / kBosch144NrOfSections;
45+
for (uint16_t offset = 0; offset < nbytes; offset += kSectionByteSize)
46+
// Section Header + Data + Footer
47+
sendGeneric(kBoschHdrMark, kBoschHdrSpace,
48+
kBoschBitMark, kBoschOneSpace,
49+
kBoschBitMark, kBoschZeroSpace,
50+
kBoschBitMark, kBoschFooterSpace,
51+
data + offset, kSectionByteSize,
52+
kBoschFreq, true, 0, kDutyDefault);
53+
space(kDefaultMessageGap); // Complete guess
54+
}
55+
}
56+
57+
#endif // SEND_BOSCH144
58+
59+
#if DECODE_BOSCH144
60+
/// Decode the supplied Bosch 144-bit / 18-byte A/C message.
61+
/// Status: STABLE / Confirmed Working.
62+
/// @param[in,out] results Ptr to the data to decode & where to store the decode
63+
/// result.
64+
/// @param[in] offset The starting index to use when attempting to decode the
65+
/// raw data. Typically/Defaults to kStartOffset.
66+
/// @param[in] nbits The number of data bits to expect.
67+
/// @param[in] strict Flag indicating if we should perform strict matching.
68+
/// @return A boolean. True if it can decode it, false if it can't.
69+
bool IRrecv::decodeBosch144(decode_results *results, uint16_t offset,
70+
const uint16_t nbits, const bool strict) {
71+
if (results->rawlen < 2 * nbits +
72+
kBosch144NrOfSections * (kHeader + kFooter) -
73+
1 + offset)
74+
return false; // Can't possibly be a valid BOSCH144 message.
75+
if (strict && nbits != kBosch144Bits)
76+
return false; // Not strictly a BOSCH144 message.
77+
if (nbits % 8 != 0) // nbits has to be a multiple of nr. of bits in a byte.
78+
return false;
79+
if (nbits % kBosch144NrOfSections != 0)
80+
return false; // nbits has to be a multiple of kBosch144NrOfSections.
81+
const uint16_t kSectionBits = nbits / kBosch144NrOfSections;
82+
const uint16_t kSectionBytes = kSectionBits / 8;
83+
const uint16_t kNBytes = kSectionBytes * kBosch144NrOfSections;
84+
// Capture each section individually
85+
for (uint16_t pos = 0, section = 0;
86+
pos < kNBytes;
87+
pos += kSectionBytes, section++) {
88+
uint16_t used = 0;
89+
// Section Header + Section Data + Section Footer
90+
used = matchGeneric(results->rawbuf + offset, results->state + pos,
91+
results->rawlen - offset, kSectionBits,
92+
kBoschHdrMark, kBoschHdrSpace,
93+
kBoschBitMark, kBoschOneSpace,
94+
kBoschBitMark, kBoschZeroSpace,
95+
kBoschBitMark, kBoschFooterSpace,
96+
section >= kBosch144NrOfSections - 1,
97+
_tolerance, kMarkExcess, true);
98+
if (!used) return false; // Didn't match.
99+
offset += used;
100+
}
101+
102+
// Compliance
103+
104+
// Success
105+
results->decode_type = decode_type_t::BOSCH144;
106+
results->bits = nbits;
107+
// No need to record the state as we stored it as we decoded it.
108+
// As we use result->state, we don't record value, address, or command as it
109+
// is a union data type.
110+
return true;
111+
}
112+
#endif // DECODE_BOSCH144

src/locale/defaults.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,12 @@ D_STR_INDIRECT " " D_STR_MODE
706706
#ifndef D_STR_ARRIS
707707
#define D_STR_ARRIS "ARRIS"
708708
#endif // D_STR_ARRIS
709+
#ifndef D_STR_BOSCH
710+
#define D_STR_BOSCH "BOSCH"
711+
#endif // D_STR_BOSCH
712+
#ifndef D_STR_BOSCH144
713+
#define D_STR_BOSCH144 D_STR_BOSCH "144"
714+
#endif // D_STR_BOSCH144
709715
#ifndef D_STR_BOSE
710716
#define D_STR_BOSE "BOSE"
711717
#endif // D_STR_BOSE

test/ir_Bosch_test.cpp

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright 2022 David Conran
2+
3+
#include "IRac.h"
4+
#include "IRrecv.h"
5+
#include "IRrecv_test.h"
6+
#include "IRsend.h"
7+
#include "IRsend_test.h"
8+
#include "gtest/gtest.h"
9+
10+
11+
TEST(TestUtils, Housekeeping) {
12+
// Bosch144
13+
ASSERT_EQ("BOSCH144", typeToString(decode_type_t::BOSCH144));
14+
ASSERT_EQ(decode_type_t::BOSCH144, strToDecodeType("BOSCH144"));
15+
ASSERT_TRUE(hasACState(decode_type_t::BOSCH144));
16+
ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::BOSCH144));
17+
ASSERT_EQ(kBosch144Bits, IRsend::defaultBits(decode_type_t::BOSCH144));
18+
ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::BOSCH144));
19+
}
20+
21+
// Tests for decodeBosch144().
22+
23+
// Decode normal Bosch144 messages.
24+
TEST(TestDecodeBosch144, RealExample) {
25+
IRsendTest irsend(kGpioUnused);
26+
IRrecv irrecv(kGpioUnused);
27+
28+
// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1787#issuecomment-1099993189
29+
// Mode: Cool; Fan: 100% ; Temp: 16°C
30+
const uint16_t rawData[299] = {
31+
4380, 4400,
32+
528, 1646, 504, 570, 504, 1646, 504, 1646, 504, 572, 502, 570, 504, 1646,
33+
504, 570, 504, 572, 502, 1646, 504, 570, 502, 570, 502, 1648, 502, 1646,
34+
502, 570, 502, 1646, 504, 572, 502, 572, 502, 1644, 504, 1646, 504, 1646,
35+
504, 1646, 502, 1648, 500, 1646, 504, 1646, 504, 1646, 504, 572, 502, 570,
36+
504, 570, 504, 570, 504, 570, 504, 570, 506, 570, 502, 572, 502, 570, 502,
37+
572, 502, 572, 502, 572, 502, 572, 502, 572, 500, 1648, 502, 1644, 502,
38+
1646, 504, 1646, 502, 1646, 504, 1646, 504, 1644, 504, 1646,
39+
504, 5234,
40+
4360, 4422,
41+
504, 1646, 502, 596, 478, 1670, 478, 1646, 504, 570, 504, 572, 500, 1646,
42+
502, 572, 502, 572, 502, 1644, 506, 570, 502, 570, 504, 1644, 506, 1644,
43+
502, 574, 502, 1644, 504, 570, 504, 570, 504, 1644, 504, 1646, 504, 1644,
44+
506, 1644, 504, 1646, 504, 1646, 504, 1644, 504, 1646, 502, 570, 504, 570,
45+
504, 570, 504, 570, 502, 570, 504, 570, 502, 572, 502, 570, 504, 570, 504,
46+
570, 504, 570, 502, 572, 502, 570, 506, 570, 504, 1646, 502, 1646, 504,
47+
1646, 504, 1646, 504, 1646, 502, 1644, 504, 1644, 504, 1646,
48+
502, 5236,
49+
4360, 4424,
50+
504, 1646, 504, 1646, 502, 572, 504, 1644, 504, 570, 504, 1646, 504, 570,
51+
502, 1644, 504, 570, 504, 1644, 506, 1646, 502, 572, 502, 572, 502, 1646,
52+
504, 570, 504, 570, 504, 570, 502, 572, 504, 570, 504, 570, 504, 570, 502,
53+
572, 502, 570, 504, 570, 502, 570, 504, 572, 502, 572, 502, 1646, 504,
54+
570, 504, 570, 504, 570, 502, 574, 502, 572, 502, 572, 502, 572, 502, 572,
55+
502, 572, 502, 570, 504, 572, 502, 572, 502, 572, 502, 1646, 504, 572,
56+
502, 570, 502, 1646, 504, 572, 504, 570, 504, 1644,
57+
504}; // COOLIX B23F00
58+
const uint8_t expectedState[kBosch144StateLength] = {
59+
0xB2, 0x4D, 0x3F, 0xC0, 0x00, 0xFF,
60+
0xB2, 0x4D, 0x3F, 0xC0, 0x00, 0xFF,
61+
0xD5, 0x64, 0x00, 0x10, 0x00, 0x49};
62+
irsend.begin();
63+
irsend.reset();
64+
65+
irsend.sendRaw(rawData, 299, 38000);
66+
irsend.makeDecodeResult();
67+
ASSERT_TRUE(irrecv.decode(&irsend.capture));
68+
EXPECT_EQ(decode_type_t::BOSCH144, irsend.capture.decode_type);
69+
EXPECT_EQ(kBosch144Bits, irsend.capture.bits);
70+
EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits);
71+
EXPECT_EQ(
72+
"",
73+
IRAcUtils::resultAcToString(&irsend.capture));
74+
stdAc::state_t result, prev;
75+
ASSERT_FALSE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev));
76+
}
77+
78+
TEST(TestDecodeBosch144, SyntheticSelfDecode) {
79+
IRsendTest irsend(kGpioUnused);
80+
IRrecv irrecv(kGpioUnused);
81+
irsend.begin();
82+
83+
irsend.reset();
84+
const uint8_t expectedState[kBosch144StateLength] = {
85+
0xB2, 0x4D, 0x3F, 0xC0, 0x00, 0xFF,
86+
0xB2, 0x4D, 0x3F, 0xC0, 0x00, 0xFF,
87+
0xD5, 0x64, 0x00, 0x10, 0x00, 0x49};
88+
irsend.sendBosch144(expectedState);
89+
irsend.makeDecodeResult();
90+
91+
ASSERT_TRUE(irrecv.decode(&irsend.capture));
92+
EXPECT_EQ(decode_type_t::BOSCH144, irsend.capture.decode_type);
93+
EXPECT_EQ(kBosch144Bits, irsend.capture.bits);
94+
EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits);
95+
EXPECT_EQ(
96+
"",
97+
IRAcUtils::resultAcToString(&irsend.capture));
98+
stdAc::state_t result, prev;
99+
ASSERT_FALSE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev));
100+
}

0 commit comments

Comments
 (0)