Skip to content

Commit

Permalink
Experimental basic support for EcoClim 56bit protocol.
Browse files Browse the repository at this point in the history
* Basic support for 56bit protocol
  - `sendEcoclim()` & `decodeEcoclim()`.
* Unit tests coverage.
  - Housekeeping.
  - sending
  - selfdecode
  - Real example

For #1397
  • Loading branch information
crankyoldgit committed Jan 31, 2021
1 parent b592510 commit fd9e5f5
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 1 deletion.
4 changes: 4 additions & 0 deletions src/IRrecv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -991,6 +991,10 @@ bool IRrecv::decode(decode_results *results, irparams_t *save,
if (decodePanasonicAC32(results, offset, kPanasonicAc32Bits / 2))
return true;
#endif // DECODE_PANASONIC_AC32
#if DECODE_ECOCLIM
DPRINTLN("Attempting Ecoclim decode");
if (decodeEcoclim(results, offset)) return true;
#endif // DECODE_ECOCLIM
// 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 @@ -714,6 +714,11 @@ class IRrecv {
const uint16_t nbits = kEliteScreensBits,
const bool strict = true);
#endif // DECODE_ELITESCREENS
#if DECODE_ECOCLIM
bool decodeEcoclim(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kEcoclimBits,
const bool strict = true);
#endif // DECODE_ECOCLIM
};

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

#ifndef DECODE_ECOCLIM
#define DECODE_ECOCLIM _IR_ENABLE_DEFAULT_
#endif // DECODE_ECOCLIM
#ifndef SEND_ECOCLIM
#define SEND_ECOCLIM _IR_ENABLE_DEFAULT_
#endif // SEND_ECOCLIM

#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 Down Expand Up @@ -875,8 +882,9 @@ enum decode_type_t {
ELITESCREENS, // 95
PANASONIC_AC32,
MILESTAG2,
ECOCLIM,

This comment has been minimized.

Copy link
@NiKiZe

NiKiZe Jan 31, 2021

Collaborator

Know it is WIP, but should it be ECOCLIM_AC?

This comment has been minimized.

Copy link
@crankyoldgit

crankyoldgit Jan 31, 2021

Author Owner

I tend to only add a _AC suffix when there is a likelihood (i.e. known examples) of a non-A/C IR product from the Brand.
e.g.
Samsung make TVs & A/Cs. Thus SAMSUNG & SAMSUNG_AC.

When it's fairly clear it's only for A/Cs I tend to drop the _AC as it seems redundant.
e.g. I can't easily imagine a brand called "EcoClim" making TVs or Stereos etc.

But you raise a fair point. I haven't documented that "policy", and it could easily bite us. So far it hasn't.

// Add new entries before this one, and update it to point to the last entry.
kLastDecodeType = MILESTAG2,
kLastDecodeType = ECOCLIM,
};

// Message lengths & required repeat values
Expand Down Expand Up @@ -941,6 +949,7 @@ const uint16_t kDenonLegacyBits = 14;
const uint16_t kDishBits = 16;
const uint16_t kDishMinRepeat = 3;
const uint16_t kDoshishaBits = 40;
const uint16_t kEcoclimBits = 56;
const uint16_t kEpsonBits = 32;
const uint16_t kEpsonMinRepeat = 2;
const uint16_t kElectraAcStateLength = 13;
Expand Down
6 changes: 6 additions & 0 deletions src/IRsend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,7 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) {
case MIDEA:
case PANASONIC:
return 48;
case ECOCLIM:
case MAGIQUEST:
case VESTEL_AC:
case TECHNIBEL_AC:
Expand Down Expand Up @@ -821,6 +822,11 @@ bool IRsend::send(const decode_type_t type, const uint64_t data,
sendDoshisha(data, nbits, min_repeat);
break;
#endif
#if SEND_ECOCLIM
case ECOCLIM:
sendEcoclim(data, nbits, min_repeat);
break;
#endif // SEND_ECOCLIM
#if SEND_ELITESCREENS
case ELITESCREENS:
sendElitescreens(data, nbits, min_repeat);
Expand Down
4 changes: 4 additions & 0 deletions src/IRsend.h
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,10 @@ class IRsend {
const uint16_t nbits = kMilesTag2ShotBits,
const uint16_t repeat = kMilesMinRepeat);
#endif // SEND_MILESTAG2
#if SEND_ECOCLIM
void sendEcoclim(const uint64_t data, const uint16_t nbits = kEcoclimBits,
const uint16_t repeat = kNoRepeat);
#endif // SEND_ECOCLIM

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 @@ -279,5 +279,6 @@ const PROGMEM char *kAllProtocolNamesStr =
D_STR_ELITESCREENS "\x0"
D_STR_PANASONIC_AC32 "\x0"
D_STR_MILESTAG2 "\x0"
D_STR_ECOCLIM "\x0"
///< New protocol strings should be added just above this line.
"\x0"; ///< This string requires double null termination.
102 changes: 102 additions & 0 deletions src/ir_EcoClim.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2021 David Conran

/// @file
/// @brief EcoClim A/C protocol.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1397

// Supports:
// Brand: EcoClim, Model: HYSFR-P348 remote
// Brand: EcoClim, Model: ZC200DPO A/C

#include <algorithm>
#include <cstring>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRtext.h"
#include "IRutils.h"

// Constants
const uint16_t kEcoclimHdrMark = 5730;
const uint16_t kEcoclimHdrSpace = 1935;
const uint16_t kEcoclimBitMark = 440;
const uint16_t kEcoclimOneSpace = 1739;
const uint16_t kEcoclimZeroSpace = 637;
const uint16_t kEcoclimFooterMark = 7820;
const uint32_t kEcoclimGap = kDefaultMessageGap;
const uint8_t kEcoclimSections = 3;

#if SEND_ECOCLIM
/// Send a EcoClim A/C formatted message.
/// Status: Alpha / Completely untested.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendEcoclim(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
enableIROut(38, kDutyDefault);
for (uint16_t r = 0; r <= repeat; r++) {
for (uint8_t section = 0; section < kEcoclimSections; section++)
// Header + Data
sendGeneric(kEcoclimHdrMark, kEcoclimHdrSpace,
kEcoclimBitMark, kEcoclimOneSpace,
kEcoclimBitMark, kEcoclimZeroSpace,
0, 0, data, nbits, 38, true, 0, kDutyDefault);
mark(kEcoclimFooterMark);
space(kEcoclimGap);
}
}
#endif // SEND_ECOCLIM

#if DECODE_ECOCLIM
/// Decode the supplied EcoClim A/C message.
/// Status: BETA / Probably works.
/// @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::decodeEcoclim(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen < (2 * nbits + kHeader) * kEcoclimSections +
kFooter - 1 + offset)
return false; // Can't possibly be a valid Ecoclim message.
if (strict && nbits != kEcoclimBits)
return false; // Unexpected bit size.

for (uint8_t section = 0; section < kEcoclimSections; section++) {
uint16_t used;
uint64_t data;
// Header + Data Block
used = matchGeneric(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
kEcoclimHdrMark, kEcoclimHdrSpace,
kEcoclimBitMark, kEcoclimOneSpace,
kEcoclimBitMark, kEcoclimZeroSpace,
0, 0); // Note: No footer.
if (!used) return false;
DPRINTLN("DEBUG: Data section matched okay.");
offset += used;
// Compliance
if (strict) {
if (section) { // Each section should contain the same data.
if (data != results->value) return false;
} else {
results->value = data;
}
}
}

// Footer
if (!matchMark(results->rawbuf[offset++], kEcoclimFooterMark)) return false;
if (results->rawlen <= offset && !matchAtLeast(results->rawbuf[offset++],
kEcoclimGap))
return false;
// Success
results->bits = nbits;
results->decode_type = ECOCLIM;
// No need to record the value as we stored it as we decoded it.
return true;
}
#endif // DECODE_ECOCLIM
3 changes: 3 additions & 0 deletions src/locale/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,9 @@
#ifndef D_STR_DOSHISHA
#define D_STR_DOSHISHA "DOSHISHA"
#endif // D_STR_DOSHISHA
#ifndef D_STR_ECOCLIM
#define D_STR_ECOCLIM "ECOCLIM"
#endif // D_STR_ECOCLIM
#ifndef D_STR_ELECTRA_AC
#define D_STR_ELECTRA_AC "ELECTRA_AC"
#endif // D_STR_ELECTRA_AC
Expand Down
117 changes: 117 additions & 0 deletions test/ir_Ecoclim_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright 2021 David Conran

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

TEST(TestUtils, Housekeeping) {
ASSERT_EQ("ECOCLIM", typeToString(decode_type_t::ECOCLIM));
ASSERT_EQ(decode_type_t::ECOCLIM, strToDecodeType("ECOCLIM"));
ASSERT_FALSE(hasACState(decode_type_t::ECOCLIM));
ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::ECOCLIM));
ASSERT_EQ(kEcoclimBits, IRsend::defaultBits(decode_type_t::ECOCLIM));
ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::ECOCLIM));
}

// Test sending typical data only.
TEST(TestSendEcoclim, SendDataOnly) {
IRsendTest irsend(kGpioUnused);
irsend.begin();

irsend.reset();
irsend.sendEcoclim(0x110673AEFFFF72);
EXPECT_EQ(
"f38000d50"
"m5730s1935"
"m440s637m440s637m440s637m440s1739m440s637m440s637m440s637m440s1739"
"m440s637m440s637m440s637m440s637m440s637m440s1739m440s1739m440s637"
"m440s637m440s1739m440s1739m440s1739m440s637m440s637m440s1739m440s1739"
"m440s1739m440s637m440s1739m440s637m440s1739m440s1739m440s1739m440s637"
"m440s1739m440s1739m440s1739m440s1739m440s1739m440s1739m440s1739m440s1739"
"m440s1739m440s1739m440s1739m440s1739m440s1739m440s1739m440s1739m440s1739"
"m440s637m440s1739m440s1739m440s1739m440s637m440s637m440s1739m440s637"
"m5730s1935"
"m440s637m440s637m440s637m440s1739m440s637m440s637m440s637m440s1739"
"m440s637m440s637m440s637m440s637m440s637m440s1739m440s1739m440s637"
"m440s637m440s1739m440s1739m440s1739m440s637m440s637m440s1739m440s1739"
"m440s1739m440s637m440s1739m440s637m440s1739m440s1739m440s1739m440s637"
"m440s1739m440s1739m440s1739m440s1739m440s1739m440s1739m440s1739m440s1739"
"m440s1739m440s1739m440s1739m440s1739m440s1739m440s1739m440s1739m440s1739"
"m440s637m440s1739m440s1739m440s1739m440s637m440s637m440s1739m440s637"
"m5730s1935"
"m440s637m440s637m440s637m440s1739m440s637m440s637m440s637m440s1739"
"m440s637m440s637m440s637m440s637m440s637m440s1739m440s1739m440s637"
"m440s637m440s1739m440s1739m440s1739m440s637m440s637m440s1739m440s1739"
"m440s1739m440s637m440s1739m440s637m440s1739m440s1739m440s1739m440s637"
"m440s1739m440s1739m440s1739m440s1739m440s1739m440s1739m440s1739m440s1739"
"m440s1739m440s1739m440s1739m440s1739m440s1739m440s1739m440s1739m440s1739"
"m440s637m440s1739m440s1739m440s1739m440s637m440s637m440s1739m440s637"
"m7820s100000",
irsend.outputStr());
}

TEST(TestDecodeEcoclim, SyntheticSelfDecode) {
IRsendTest irsend(kGpioUnused);
IRrecv irrecv(kGpioUnused);

irsend.begin();
irsend.reset();
irsend.sendEcoclim(0x110673AEFFFF72);
irsend.makeDecodeResult();
ASSERT_TRUE(irrecv.decode(&irsend.capture));
EXPECT_EQ(ECOCLIM, irsend.capture.decode_type);
EXPECT_EQ(kEcoclimBits, irsend.capture.bits);
EXPECT_EQ(0x110673AEFFFF72, irsend.capture.value);
}

TEST(TestDecodeEcoclim, RealExample) {
IRsendTest irsend(kGpioUnused);
IRrecv irrecv(kGpioUnused);
irsend.begin();

// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1397#issuecomment-770376241
uint16_t rawData[343] = {
5834, 1950, 482, 580, 506, 614, 480, 612, 456, 1738, 482, 562, 532, 582,
508, 608, 430, 1760, 456, 642, 456, 608, 484, 638, 458, 634, 456, 634,
456, 1734, 482, 1704, 402, 690, 456, 638, 404, 1786, 458, 1730, 430, 1762,
454, 638, 456, 636, 456, 1732, 426, 1764, 426, 1788, 400, 692, 402, 1764,
452, 638, 430, 1760, 424, 1786, 374, 1818, 402, 690, 374, 1786, 424, 1796,
402, 1758, 426, 1790, 376, 1782, 426, 1766, 400, 1810, 398, 1796, 400,
1788, 428, 1734, 398, 1814, 400, 1762, 470, 1742, 400, 1786, 398, 1794,
400, 1762, 398, 718, 400, 1792, 400, 1788, 400, 1788, 400, 694, 400, 694,
402, 1788, 398, 664, 5720, 1944, 426, 642, 450, 696, 442, 650, 396, 1794,
468, 602, 422, 642, 448, 696, 442, 1744, 392, 702, 392, 678, 420, 700,
394, 700, 464, 628, 466, 1720, 464, 1726, 462, 628, 464, 630, 464, 1728,
438, 1752, 462, 1722, 464, 636, 438, 626, 464, 1722, 490, 1698, 488, 1722,
464, 628, 466, 1724, 466, 626, 464, 1724, 464, 1724, 462, 1732, 462, 626,
464, 1726, 464, 1724, 464, 1722, 464, 1732, 464, 1690, 490, 1724, 464,
1726, 464, 1728, 464, 1728, 462, 1690, 492, 1728, 462, 1724, 464, 1726,
464, 1728, 464, 1720, 462, 1736, 460, 604, 490, 1718, 464, 1730, 462,
1720, 462, 630, 464, 628, 462, 1734, 462, 600, 5632, 2028, 490, 630, 464,
632, 464, 630, 462, 1724, 462, 630, 462, 636, 462, 602, 488, 1728, 464,
630, 462, 632, 464, 630, 462, 628, 462, 630, 464, 1730, 460, 1724, 464,
630, 464, 630, 462, 1728, 464, 1724, 462, 1728, 462, 630, 464, 630, 462,
1724, 464, 1728, 460, 1730, 462, 628, 460, 1732, 464, 602, 492, 1722, 464,
1726, 460, 1726, 464, 632, 464, 1696, 488, 1728, 460, 1732, 462, 1728,
462, 1694, 488, 1728, 462, 1724, 464, 1732, 460, 1700, 490, 1728, 462,
1694, 488, 1730, 462, 1720, 462, 1728, 464, 1726, 462, 1726, 460, 632,
464, 1724, 462, 1726, 460, 1730, 464, 630, 464, 632, 464, 1728, 462, 596,
7862}; // UNKNOWN 842242BF

irsend.reset();
irsend.sendRaw(rawData, 343, 38000);
irsend.makeDecodeResult();
ASSERT_TRUE(irrecv.decodeEcoclim(&irsend.capture));
EXPECT_EQ(ECOCLIM, irsend.capture.decode_type);
EXPECT_EQ(kEcoclimBits, irsend.capture.bits);
EXPECT_EQ(0x110673AEFFFF72, irsend.capture.value);
EXPECT_EQ(
"",
IRAcUtils::resultAcToString(&irsend.capture));
stdAc::state_t r, p;
ASSERT_FALSE(IRAcUtils::decodeToState(&irsend.capture, &r, &p));
}

0 comments on commit fd9e5f5

Please sign in to comment.