Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental basic support for Bosch 144bit protocol. #1822

Merged
merged 2 commits into from
Jun 16, 2022
Merged
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
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));
}