Skip to content

Commit

Permalink
DAIKIN: Add basic support for 200-bit Daikin protocol. (#1803)
Browse files Browse the repository at this point in the history
* Add `sendDaikin200()` & `decodeDaikin200()` routines
* Checksum verification added.
* Update related text & routines.
* Add unit tests coverage for the above.


Capture verified on real device per:
  #1802 (comment)

For #1802
  • Loading branch information
crankyoldgit authored May 13, 2022
1 parent 5275a96 commit 3c6b057
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 9 deletions.
4 changes: 4 additions & 0 deletions src/IRrecv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,10 @@ bool IRrecv::decode(decode_results *results, irparams_t *save,
DPRINTLN("Attempting Coolix 48-bit decode");
if (decodeCoolix48(results, offset)) return true;
#endif // DECODE_COOLIX48
#if DECODE_DAIKIN200
DPRINTLN("Attempting Daikin 200-bit decode");
if (decodeDaikin200(results, offset)) return true;
#endif // DECODE_DAIKIN200
// Typically new protocols are added above this line.
}
#if DECODE_HASH
Expand Down
5 changes: 5 additions & 0 deletions src/IRrecv.h
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,11 @@ class IRrecv {
const uint16_t nbits = kDaikin2Bits,
const bool strict = true);
#endif
#if DECODE_DAIKIN200
bool decodeDaikin200(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kDaikin200Bits,
const bool strict = true);
#endif // DECODE_DAIKIN200
#if DECODE_DAIKIN216
bool decodeDaikin216(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kDaikin216Bits,
Expand Down
14 changes: 13 additions & 1 deletion src/IRremoteESP8266.h
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,13 @@
#define SEND_KELON168 _IR_ENABLE_DEFAULT_
#endif // SEND_KELON168

#ifndef DECODE_DAIKIN200
#define DECODE_DAIKIN200 _IR_ENABLE_DEFAULT_
#endif // DECODE_DAIKIN200
#ifndef SEND_DAIKIN200
#define SEND_DAIKIN200 _IR_ENABLE_DEFAULT_
#endif // SEND_DAIKIN200

#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 @@ -877,6 +884,7 @@
DECODE_TEKNOPOINT || DECODE_KELON || DECODE_TROTEC_3550 || \
DECODE_SANYO_AC88 || DECODE_RHOSS || DECODE_HITACHI_AC264 || \
DECODE_KELON168 || DECODE_HITACHI_AC296 || \
DECODE_DAIKIN200 || \
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 @@ -1030,8 +1038,9 @@ enum decode_type_t {
HITACHI_AC264,
KELON168,
HITACHI_AC296,
DAIKIN200,
// Add new entries before this one, and update it to point to the last entry.
kLastDecodeType = HITACHI_AC296,
kLastDecodeType = DAIKIN200,
};

// Message lengths & required repeat values
Expand Down Expand Up @@ -1087,6 +1096,9 @@ const uint16_t kDaikin152DefaultRepeat = kNoRepeat;
const uint16_t kDaikin176StateLength = 22;
const uint16_t kDaikin176Bits = kDaikin176StateLength * 8;
const uint16_t kDaikin176DefaultRepeat = kNoRepeat;
const uint16_t kDaikin200StateLength = 25;
const uint16_t kDaikin200Bits = kDaikin200StateLength * 8;
const uint16_t kDaikin200DefaultRepeat = kNoRepeat;
const uint16_t kDaikin216StateLength = 27;
const uint16_t kDaikin216Bits = kDaikin216StateLength * 8;
const uint16_t kDaikin216DefaultRepeat = kNoRepeat;
Expand Down
7 changes: 7 additions & 0 deletions src/IRsend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,8 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) {
return kDaikin176Bits;
case DAIKIN2:
return kDaikin2Bits;
case DAIKIN200:
return kDaikin200Bits;
case DAIKIN216:
return kDaikin216Bits;
case DAIKIN64:
Expand Down Expand Up @@ -1161,6 +1163,11 @@ bool IRsend::send(const decode_type_t type, const uint8_t *state,
sendDaikin2(state, nbytes);
break;
#endif // SEND_DAIKIN2
#if SEND_DAIKIN200
case DAIKIN200:
sendDaikin200(state, nbytes);
break;
#endif // SEND_DAIKIN200
#if SEND_DAIKIN216
case DAIKIN216:
sendDaikin216(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 @@ -482,6 +482,11 @@ class IRsend {
const uint16_t nbytes = kDaikin2StateLength,
const uint16_t repeat = kDaikin2DefaultRepeat);
#endif
#if SEND_DAIKIN200
void sendDaikin200(const unsigned char data[],
const uint16_t nbytes = kDaikin200StateLength,
const uint16_t repeat = kDaikin200DefaultRepeat);
#endif // SEND_DAIKIN200
#if SEND_DAIKIN216
void sendDaikin216(const unsigned char data[],
const uint16_t nbytes = kDaikin216StateLength,
Expand Down
1 change: 1 addition & 0 deletions src/IRtext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ IRTEXT_CONST_BLOB_DECL(kAllProtocolNamesStr) {
D_STR_HITACHI_AC264 "\x0"
D_STR_KELON168 "\x0"
D_STR_HITACHI_AC296 "\x0"
D_STR_DAIKIN200 "\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 @@ -179,6 +179,7 @@ bool hasACState(const decode_type_t protocol) {
case DAIKIN160:
case DAIKIN176:
case DAIKIN2:
case DAIKIN200:
case DAIKIN216:
case ELECTRA_AC:
case FUJITSU_AC:
Expand Down
88 changes: 87 additions & 1 deletion src/ir_Daikin.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright 2016 sillyfrog
// Copyright 2017 sillyfrog, crankyoldgit
// Copyright 2018-2021 crankyoldgit
// Copyright 2018-2022 crankyoldgit
// Copyright 2019 pasna (IRDaikin160 class / Daikin176 class)

/// @file
Expand All @@ -21,6 +21,7 @@
/// @see Daikin216 https://github.com/crankyoldgit/IRremoteESP8266/issues/689
/// @see Daikin216 https://github.com/danny-source/Arduino_DY_IRDaikin
/// @see Daikin64 https://github.com/crankyoldgit/IRremoteESP8266/issues/1064
/// @see Daikin200 https://github.com/crankyoldgit/IRremoteESP8266/issues/1802

#include "ir_Daikin.h"
#include <algorithm>
Expand Down Expand Up @@ -3733,3 +3734,88 @@ stdAc::state_t IRDaikin64::toCommon(const stdAc::state_t *prev) const {
result.light = false;
return result;
}

#if SEND_DAIKIN200
/// Send a Daikin200 (200-bit) A/C formatted message.
/// Status: BETA / Untested on a real device.
/// @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.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1802
void IRsend::sendDaikin200(const unsigned char data[], const uint16_t nbytes,
const uint16_t repeat) {
if (nbytes < kDaikin200Section1Length)
return; // Not enough bytes to send a partial message.

for (uint16_t r = 0; r <= repeat; r++) {
// Section #1
sendGeneric(kDaikin200HdrMark, kDaikin200HdrSpace, kDaikin200BitMark,
kDaikin200OneSpace, kDaikin200BitMark, kDaikin200ZeroSpace,
kDaikin200BitMark, kDaikin200Gap, data,
kDaikin200Section1Length,
kDaikin200Freq, false, 0, kDutyDefault);
// Section #2
sendGeneric(kDaikin200HdrMark, kDaikin200HdrSpace, kDaikin200BitMark,
kDaikin200OneSpace, kDaikin200BitMark, kDaikin200ZeroSpace,
kDaikin200BitMark, kDaikin200Gap,
data + kDaikin200Section1Length,
nbytes - kDaikin200Section1Length,
kDaikin200Freq, false, 0, kDutyDefault);
}
}
#endif // SEND_DAIKIN200

#if DECODE_DAIKIN200
/// Decode the supplied Daikin 200-bit message. (DAIKIN200)
/// Status: STABLE / Known to be 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.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1802
bool IRrecv::decodeDaikin200(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen < 2 * (nbits + kHeader + kFooter) - 1 + offset)
return false;

// Compliance
if (strict && nbits != kDaikin200Bits) return false;

const uint8_t ksectionSize[kDaikin200Sections] = {kDaikin200Section1Length,
kDaikin200Section2Length};
// Sections
uint16_t pos = 0;
for (uint8_t section = 0; section < kDaikin200Sections; section++) {
uint16_t used;
// Section Header + Section Data + Section Footer
used = matchGeneric(results->rawbuf + offset, results->state + pos,
results->rawlen - offset, ksectionSize[section] * 8,
kDaikin200HdrMark, kDaikin200HdrSpace,
kDaikin200BitMark, kDaikin200OneSpace,
kDaikin200BitMark, kDaikin200ZeroSpace,
kDaikin200BitMark, kDaikin200Gap,
section >= kDaikin200Sections - 1,
kDaikinTolerance, 0, false);
if (used == 0) return false;
offset += used;
pos += ksectionSize[section];
}
// Compliance
if (strict) {
if (pos * 8 != kDaikin200Bits) return false;
// Validate the checksum.
if (!IRDaikin176::validChecksum(results->state, pos)) return false;
}

// Success
results->decode_type = decode_type_t::DAIKIN200;
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_DAIKIN200
14 changes: 14 additions & 0 deletions src/ir_Daikin.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
/// @see Daikin216 https://github.com/crankyoldgit/IRremoteESP8266/issues/689
/// @see Daikin216 https://github.com/danny-source/Arduino_DY_IRDaikin
/// @see Daikin64 https://github.com/crankyoldgit/IRremoteESP8266/issues/1064
/// @see Daikin200 https://github.com/crankyoldgit/IRremoteESP8266/issues/1802

// Supports:
// Brand: Daikin, Model: ARC433** remote (DAIKIN)
Expand All @@ -47,6 +48,7 @@
// Brand: Daikin, Model: FTWX35AXV1 A/C (DAIKIN64)
// Brand: Daikin, Model: ARC484A4 remote (DAIKIN216)
// Brand: Daikin, Model: FTQ60TV16U2 A/C (DAIKIN216)
// Brand: Daikin, Model: BRC4M150W16 remote (DAIKIN200)

#ifndef IR_DAIKIN_H_
#define IR_DAIKIN_H_
Expand Down Expand Up @@ -676,6 +678,18 @@ const uint8_t kDaikin64MaxTemp = 30; // Celsius
const uint8_t kDaikin64ChecksumOffset = 60;
const uint8_t kDaikin64ChecksumSize = 4; // Mask 0b1111 << 59

const uint16_t kDaikin200Freq = 38000; // Modulation Frequency in Hz.
const uint16_t kDaikin200HdrMark = 4920;
const uint16_t kDaikin200HdrSpace = 2230;
const uint16_t kDaikin200BitMark = 290;
const uint16_t kDaikin200OneSpace = 1850;
const uint16_t kDaikin200ZeroSpace = 780;
const uint16_t kDaikin200Gap = 29400;
const uint16_t kDaikin200Sections = 2;
const uint16_t kDaikin200Section1Length = 7;
const uint16_t kDaikin200Section2Length = kDaikin200StateLength -
kDaikin200Section1Length;

// Legacy defines.
#define DAIKIN_COOL kDaikinCool
#define DAIKIN_HEAT kDaikinHeat
Expand Down
17 changes: 10 additions & 7 deletions src/locale/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -731,25 +731,28 @@ D_STR_INDIRECT " " D_STR_MODE
#define D_STR_DAIKIN "DAIKIN"
#endif // D_STR_DAIKIN
#ifndef D_STR_DAIKIN128
#define D_STR_DAIKIN128 "DAIKIN128"
#define D_STR_DAIKIN128 D_STR_DAIKIN "128"
#endif // D_STR_DAIKIN128
#ifndef D_STR_DAIKIN152
#define D_STR_DAIKIN152 "DAIKIN152"
#define D_STR_DAIKIN152 D_STR_DAIKIN "152"
#endif // D_STR_DAIKIN152
#ifndef D_STR_DAIKIN160
#define D_STR_DAIKIN160 "DAIKIN160"
#define D_STR_DAIKIN160 D_STR_DAIKIN "160"
#endif // D_STR_DAIKIN160
#ifndef D_STR_DAIKIN176
#define D_STR_DAIKIN176 "DAIKIN176"
#define D_STR_DAIKIN176 D_STR_DAIKIN "176"
#endif // D_STR_DAIKIN176
#ifndef D_STR_DAIKIN2
#define D_STR_DAIKIN2 "DAIKIN2"
#define D_STR_DAIKIN2 D_STR_DAIKIN "2"
#endif // D_STR_DAIKIN2
#ifndef D_STR_DAIKIN200
#define D_STR_DAIKIN200 D_STR_DAIKIN "200"
#endif // D_STR_DAIKIN200
#ifndef D_STR_DAIKIN216
#define D_STR_DAIKIN216 "DAIKIN216"
#define D_STR_DAIKIN216 D_STR_DAIKIN "216"
#endif // D_STR_DAIKIN216
#ifndef D_STR_DAIKIN64
#define D_STR_DAIKIN64 "DAIKIN64"
#define D_STR_DAIKIN64 D_STR_DAIKIN "64"
#endif // D_STR_DAIKIN64
#ifndef D_STR_DELONGHI_AC
#define D_STR_DELONGHI_AC "DELONGHI_AC"
Expand Down
87 changes: 87 additions & 0 deletions test/ir_Daikin_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1552,6 +1552,13 @@ TEST(TestUtils, Housekeeping) {
ASSERT_EQ(decode_type_t::DAIKIN64, strToDecodeType("DAIKIN64"));
ASSERT_FALSE(hasACState(decode_type_t::DAIKIN64));
ASSERT_TRUE(IRac::isProtocolSupported(decode_type_t::DAIKIN64));

ASSERT_EQ("DAIKIN200", typeToString(decode_type_t::DAIKIN200));
ASSERT_EQ(decode_type_t::DAIKIN200, strToDecodeType("DAIKIN200"));
ASSERT_TRUE(hasACState(decode_type_t::DAIKIN200));
ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::DAIKIN200));
ASSERT_EQ(kDaikin200Bits, IRsend::defaultBits(decode_type_t::DAIKIN200));
ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::DAIKIN200));
}

// https://github.com/crankyoldgit/IRremoteESP8266/issues/582#issuecomment-453863879
Expand Down Expand Up @@ -3919,3 +3926,83 @@ TEST(TestDaikin128Class, Issue1754_HeatMode) {
// Compare the synthetic state to the captured one.
EXPECT_STATE_EQ(heatmode, ac.getRaw(), kDaikin128Bits);
}

// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1802
TEST(TestDecodeDaikin200, RealExample) {
IRsendTest irsend(kGpioUnused);
IRrecv irrecv(kGpioUnused);
const uint16_t rawData[407] = {
4852, 2298,
202, 1942, 202, 870, 200, 870, 202, 868, 202, 1942, 202, 870, 202, 870,
202, 870, 202, 870, 202, 1942, 202, 870, 202, 1942, 202, 1942, 202, 868,
202, 1942, 202, 1942, 202, 1912, 232, 1942, 202, 1942, 202, 868, 202,
1942, 202, 870, 202, 870, 202, 868, 202, 868, 204, 868, 202, 870, 202,
1942, 202, 868, 204, 868, 202, 1942, 202, 870, 202, 870, 200, 870, 202,
1942, 202, 868, 202, 870, 202, 870, 202, 870, 202, 870, 202, 870, 202,
870, 202, 870, 202, 868, 202, 868, 204, 868, 202, 870, 202, 870, 202, 870,
202, 1942, 202, 1942, 202, 1942, 202, 868, 202, 868, 204, 1942, 202, 870,
202, 29468,
4880, 2266,
256, 1888, 230, 844, 202, 868, 202, 870, 256, 1888, 230, 842, 228, 842,
230, 842, 256, 814, 232, 1914, 254, 816, 230, 1916, 256, 1888, 256, 814,
256, 1890, 254, 1888, 256, 1888, 256, 1888, 256, 1888, 256, 816, 230,
1912, 258, 814, 256, 816, 256, 816, 282, 790, 280, 790, 282, 790, 282,
1862, 258, 814, 280, 790, 282, 1862, 280, 792, 280, 790, 282, 788, 282,
790, 282, 790, 282, 790, 282, 790, 282, 788, 282, 790, 282, 1862, 282,
1862, 282, 790, 282, 788, 282, 1862, 282, 1864, 280, 1862, 282, 790, 280,
790, 282, 790, 282, 790, 282, 788, 284, 790, 282, 760, 310, 790, 282, 788,
282, 1862, 282, 788, 282, 790, 282, 788, 282, 788, 282, 1862, 284, 788,
282, 788, 284, 788, 282, 790, 282, 788, 284, 788, 282, 790, 282, 788, 284,
758, 312, 788, 284, 788, 282, 788, 284, 788, 282, 788, 284, 788, 282, 788,
284, 788, 284, 758, 314, 786, 284, 788, 284, 1860, 282, 790, 282, 790,
282, 1862, 282, 758, 312, 762, 310, 788, 284, 788, 282, 758, 314, 758,
314, 1862, 282, 760, 312, 1860, 284, 760, 310, 788, 282, 760, 312, 788,
284, 760, 312, 788, 282, 790, 282, 788, 284, 788, 282, 790, 282, 788, 282,
758, 314, 788, 282, 758, 314, 1862, 282, 758, 312, 788, 284, 758, 312,
758, 314, 788, 282, 758, 314, 758, 312, 788, 284, 788, 284, 758, 312, 760,
312, 762, 310, 790, 284, 788, 282, 758, 314, 758, 312, 788, 282, 760, 312,
760, 312, 760, 312, 788, 282, 758, 314, 788, 284, 788, 284, 758, 312, 788,
282, 790, 282, 1832, 310, 758, 316, 788, 284, 1862, 282, 1862, 282, 1832,
310, 788, 284}; // UNKNOWN 3BFB2888

const uint8_t expectedState[kDaikin200StateLength] = {
0x11, 0xDA, 0x17, 0x48, 0x04, 0x00, 0x4E, // Section 1
0x11, 0xDA, 0x17, 0x48, 0x00, 0x73, 0x00, 0x21, 0x00, // Section 2
0x00, 0x24, 0x50, 0x00, 0x20, 0x00, 0x00, 0x00, 0x72};
irsend.begin();
irsend.reset();
irsend.sendRaw(rawData, 407, kDaikin64Freq);
irsend.makeDecodeResult();
ASSERT_TRUE(irrecv.decode(&irsend.capture));
ASSERT_EQ(decode_type_t::DAIKIN200, irsend.capture.decode_type);
ASSERT_EQ(kDaikin200Bits, 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));
}

// Decoding a message we entirely constructed based solely on a given state.
TEST(TestDecodeDaikin200, SyntheticExample) {
IRsendTest irsend(kGpioUnused);
IRrecv irrecv(kGpioUnused);
irsend.begin();

const uint8_t expectedState[kDaikin200StateLength] = {
0x11, 0xDA, 0x17, 0x48, 0x04, 0x00, 0x4E, // Section 1
0x11, 0xDA, 0x17, 0x48, 0x00, 0x73, 0x00, 0x21, 0x00, // Section 2
0x00, 0x24, 0x50, 0x00, 0x20, 0x00, 0x00, 0x00, 0x72};

irsend.reset();
irsend.sendDaikin200(expectedState);
irsend.makeDecodeResult();
ASSERT_TRUE(irrecv.decode(&irsend.capture));
ASSERT_EQ(DAIKIN200, irsend.capture.decode_type);
ASSERT_EQ(kDaikin200Bits, irsend.capture.bits);
EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits);
EXPECT_EQ(
"",
IRAcUtils::resultAcToString(&irsend.capture));
}

0 comments on commit 3c6b057

Please sign in to comment.