Skip to content

Commit

Permalink
[BREAKING][BUG] Fix Symphony protocol. (#1107)
Browse files Browse the repository at this point in the history
* [Breaking] Per #1105 it turns out this protocol is 12 bits, **and** we had the 1's and 0's around the wrong way.
  - All _previous_ values will be incorrect.
* Adjust timings slightly.
* Increase default repeat to `3`, to match the spec.
* Added `matchGenericConstBitTime()` to handle constant bit time protocols correctly/better.
* Add and adjust unit tests accordingly.

FYI #1057 @soumaxetuirk
Fixes #1105
  • Loading branch information
crankyoldgit authored May 12, 2020
1 parent 2799b84 commit c905203
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 28 deletions.
4 changes: 4 additions & 0 deletions SupportedProtocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@
| [Sharp](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Sharp.cpp) | **[Sharp](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Sharp.h)** | AH-AxSAY A/C<BR>AH-XP10NRY A/C<BR>AY-ZP40KR A/C<BR>CRMC-820JBEZ remote<BR>LC-52D62U TV | | Yes |
| [Sherwood](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Sherwood.cpp) | **Sherwood** | RC-138 remote<BR>RD6505(B) Receiver | | - |
| [Sony](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Sony.cpp) | **Sony** | HT-CT380 Soundbar (Uses 38kHz & 3 repeats) | | - |
| [Symphony](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Symphony.cpp) | **Blyss** | Owen-SW-5 3 Fan<BR>WP-YK8 090218 remote | | - |
| [Symphony](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Symphony.cpp) | **SamHop** | SM3015 Fan Remote Control<BR>SM5021 Encoder chip<BR>SM5032 Decoder chip | | - |
| [Symphony](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Symphony.cpp) | **Satellite Electronic** | ID6 Remote<BR>JY199I Fan driver<BR>JY199I-L Fan driver | | - |
| [Symphony](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Symphony.cpp) | **Symphony** | Air Cooler 3Di | | - |
| [Symphony](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Symphony.cpp) | **Westinghouse** | 78095 Remote<BR>Ceiling fan | | - |
| [Tcl](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Tcl.cpp) | **[Leberg](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Tcl.h)** | LBS-TOR07 A/C | | Yes |
| [Teco](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Teco.cpp) | **[Alaska](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Teco.h)** | SAC9010QC A/C<BR>SAC9010QC remote | | Yes |
| [Toshiba](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Toshiba.cpp) | **[Toshiba](https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Toshiba.h)** | Akita EVO II<BR>RAS 18SKP-ES<BR>RAS-B13N3KV2<BR>RAS-B13N3KVP-E<BR>WC-L03SE<BR>WH-TA04NE | | Yes |
Expand Down
86 changes: 86 additions & 0 deletions src/IRrecv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1309,6 +1309,92 @@ uint16_t IRrecv::matchGeneric(volatile uint16_t *data_ptr,
tolerance, excess, MSBfirst);
}

// Match & decode a generic/typical constant bit time <= 64bit IR message.
// The data is stored at result_ptr.
// Values of 0 for hdrmark, hdrspace, footermark, or footerspace mean skip
// that requirement.
//
// Args:
// data_ptr: A pointer to where we are at in the capture buffer.
// result_ptr: A pointer to where to start storing the bits we decoded.
// remaining: The size of the capture buffer are remaining.
// nbits: Nr. of data bits we expect.
// hdrmark: Nr. of uSeconds for the expected header mark signal.
// hdrspace: Nr. of uSeconds for the expected header space signal.
// one: Nr. of uSeconds in an expected mark signal for a '1' bit.
// zero: Nr. of uSeconds in an expected mark signal for a '0' bit.
// footermark: Nr. of uSeconds for the expected footer mark signal.
// footerspace: Nr. of uSeconds for the expected footer space/gap signal.
// atleast: Is the match on the footerspace a matchAtLeast or matchSpace?
// tolerance: Percentage error margin to allow. (Def: kUseDefTol)
// excess: Nr. of useconds. (Def: kMarkExcess)
// MSBfirst: Bit order to save the data in. (Def: true)
// Returns:
// A uint16_t: If successful, how many buffer entries were used. Otherwise 0.
//
// Note: one + zero add up to the total time for a bit.
// e.g. mark(one) + space(zero) is a `1`, mark(zero) + space(one) is a `0`.
uint16_t IRrecv::matchGenericConstBitTime(volatile uint16_t *data_ptr,
uint64_t *result_ptr,
const uint16_t remaining,
const uint16_t nbits,
const uint16_t hdrmark,
const uint32_t hdrspace,
const uint16_t one,
const uint32_t zero,
const uint16_t footermark,
const uint32_t footerspace,
const bool atleast,
const uint8_t tolerance,
const int16_t excess,
const bool MSBfirst) {
uint16_t offset = 0;
uint64_t result = 0;
// If we expect a footermark, then this can be processed like normal.
if (footermark)
return _matchGeneric(data_ptr, result_ptr, NULL, true, remaining, nbits,
hdrmark, hdrspace, one, zero, zero, one,
footermark, footerspace, atleast,
tolerance, excess, MSBfirst);
// Overwise handle like normal, except for the last bit. and no footer.
uint16_t bits = (nbits > 0) ? nbits - 1 : 0; // Make sure we don't underflow.
offset = _matchGeneric(data_ptr, &result, NULL, true, remaining, bits,
hdrmark, hdrspace, one, zero, zero, one, 0, 0, false,
tolerance, excess, true);
if (!offset) return 0; // Didn't match.
// Now for the last bit.
if (remaining <= offset) return 0; // Not enough buffer.
result <<= 1;
bool last_bit = 0;
// Is the mark a '1' or a `0`?
if (matchMark(*(data_ptr + offset), one, tolerance, excess)) { // 1
last_bit = 1;
result |= 1;
} else if (matchMark(*(data_ptr + offset), zero, tolerance, excess)) { // 0
last_bit = 0;
} else {
return 0; // It's neither, so fail.
}
offset++;
uint32_t expected_space = (last_bit ? zero : one) + footerspace;
// If we are not at the end of the buffer, check for at least the expected
// space value.
if (remaining > offset) {
if (atleast) {
if (!matchAtLeast(*(data_ptr + offset), expected_space, tolerance,
excess))
return false;
} else {
if (!matchSpace(*(data_ptr + offset), expected_space, tolerance))
return false;
}
offset++;
}
if (!MSBfirst) result = reverseBits(result, nbits);
*result_ptr = result;
return offset;
}

// Match & decode a Manchester Code <= 64bit IR message.
// The data is stored at result_ptr.
// Values of 0 for hdrmark, hdrspace, footermark, or footerspace mean skip
Expand Down
14 changes: 14 additions & 0 deletions src/IRrecv.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,20 @@ class IRrecv {
const uint8_t tolerance = kUseDefTol,
const int16_t excess = kMarkExcess,
const bool MSBfirst = true);
uint16_t matchGenericConstBitTime(volatile uint16_t *data_ptr,
uint64_t *result_ptr,
const uint16_t remaining,
const uint16_t nbits,
const uint16_t hdrmark,
const uint32_t hdrspace,
const uint16_t one,
const uint32_t zero,
const uint16_t footermark,
const uint32_t footerspace,
const bool atleast = false,
const uint8_t tolerance = kUseDefTol,
const int16_t excess = kMarkExcess,
const bool MSBfirst = true);
uint16_t matchManchester(volatile const uint16_t *data_ptr,
uint64_t *result_ptr,
const uint16_t remaining,
Expand Down
4 changes: 2 additions & 2 deletions src/IRremoteESP8266.h
Original file line number Diff line number Diff line change
Expand Up @@ -925,8 +925,8 @@ const uint16_t kSony15Bits = 15;
const uint16_t kSony20Bits = 20;
const uint16_t kSonyMinBits = 12;
const uint16_t kSonyMinRepeat = 2;
const uint16_t kSymphonyBits = 11;
const uint16_t kSymphonyDefaultRepeat = kSingleRepeat;
const uint16_t kSymphonyBits = 12;
const uint16_t kSymphonyDefaultRepeat = 3;
const uint16_t kTcl112AcStateLength = 14;
const uint16_t kTcl112AcBits = kTcl112AcStateLength * 8;
const uint16_t kTcl112AcDefaultRepeat = kNoRepeat;
Expand Down
6 changes: 3 additions & 3 deletions src/IRsend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,6 @@ uint16_t IRsend::minRepeats(const decode_type_t protocol) {
case MITSUBISHI_AC:
case MULTIBRACKETS:
case SHERWOOD:
case SYMPHONY:
case TOSHIBA_AC:
return kSingleRepeat;
// Special
Expand All @@ -623,6 +622,8 @@ uint16_t IRsend::minRepeats(const decode_type_t protocol) {
return kSonyMinRepeat;
case SONY_38K:
return kSonyMinRepeat + 1;
case SYMPHONY:
return kSymphonyDefaultRepeat;
default:
return kNoRepeat;
}
Expand All @@ -637,9 +638,8 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) {
switch (protocol) {
case MULTIBRACKETS:
return 8;
case SYMPHONY:
return 11;
case RC5:
case SYMPHONY:
return 12;
case LASERTAG:
case RC5X:
Expand Down
42 changes: 28 additions & 14 deletions src/ir_Symphony.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@

// Supports:
// Brand: Symphony, Model: Air Cooler 3Di
// Brand: SamHop, Model: SM3015 Fan Remote Control
// Brand: SamHop, Model: SM5021 Encoder chip
// Brand: SamHop, Model: SM5032 Decoder chip
// Brand: Blyss, Model: Owen-SW-5 3 Fan
// Brand: Blyss, Model: WP-YK8 090218 remote
// Brand: Westinghouse, Model: Ceiling fan
// Brand: Westinghouse, Model: 78095 Remote
// Brand: Satellite Electronic, Model: ID6 Remote
// Brand: Satellite Electronic, Model: JY199I Fan driver
// Brand: Satellite Electronic, Model: JY199I-L Fan driver

#include <algorithm>
#include "IRrecv.h"
Expand All @@ -14,12 +24,12 @@
// Constants
// Ref:
// https://github.com/crankyoldgit/IRremoteESP8266/issues/1057
const uint16_t kSymphonyZeroMark = 1250;
const uint16_t kSymphonyZeroSpace = 400;
const uint16_t kSymphonyZeroMark = 400;
const uint16_t kSymphonyZeroSpace = 1250;
const uint16_t kSymphonyOneMark = kSymphonyZeroSpace;
const uint16_t kSymphonyOneSpace = kSymphonyZeroMark;
const uint16_t kSymphonyFooterMark = kSymphonyOneMark;
const uint32_t kSymphonyFooterGap = 8000;
const uint32_t kSymphonyFooterGap = 4 * (kSymphonyZeroMark +
kSymphonyZeroSpace);

#if SEND_SYMPHONY
// Send a Symphony packet.
Expand All @@ -33,11 +43,13 @@ const uint32_t kSymphonyFooterGap = 8000;
//
// Ref:
// https://github.com/crankyoldgit/IRremoteESP8266/issues/1057
// https://github.com/crankyoldgit/IRremoteESP8266/issues/1105
// https://www.alldatasheet.com/datasheet-pdf/pdf/124369/ANALOGICTECH/SM5021B.html
void IRsend::sendSymphony(uint64_t data, uint16_t nbits, uint16_t repeat) {
sendGeneric(0, 0,
kSymphonyOneMark, kSymphonyOneSpace,
kSymphonyZeroMark, kSymphonyZeroSpace,
kSymphonyFooterMark, kSymphonyFooterGap,
0, kSymphonyFooterGap,
data, nbits, 38000, true, repeat, kDutyDefault);
}
#endif // SEND_SYMPHONY
Expand All @@ -57,23 +69,25 @@ void IRsend::sendSymphony(uint64_t data, uint16_t nbits, uint16_t repeat) {
// Status: STABLE / Should be working.
//
// Ref:
//
// https://github.com/crankyoldgit/IRremoteESP8266/issues/1057
// https://github.com/crankyoldgit/IRremoteESP8266/issues/1105
// https://www.alldatasheet.com/datasheet-pdf/pdf/124369/ANALOGICTECH/SM5021B.html
bool IRrecv::decodeSymphony(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
uint64_t data = 0;

if (results->rawlen < 2 * nbits + kFooter + offset - 1)
if (results->rawlen < 2 * nbits + offset - 1)
return false; // Not enough entries to ever be SYMPHONY.
// Compliance
if (strict && nbits != kSymphonyBits) return false;

if (!matchGeneric(results->rawbuf + offset, &data, results->rawlen - offset,
nbits,
0, 0, // No Header
kSymphonyOneMark, kSymphonyOneSpace,
kSymphonyZeroMark, kSymphonyZeroSpace,
kSymphonyFooterMark, kSymphonyFooterGap, true,
_tolerance, 0))
if (!matchGenericConstBitTime(results->rawbuf + offset, &data,
results->rawlen - offset,
nbits,
0, 0, // No Header
kSymphonyOneMark, kSymphonyZeroMark,
0, kSymphonyFooterGap, true,
_tolerance, 0))
return false;

// Success
Expand Down
59 changes: 50 additions & 9 deletions test/ir_Symphony_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,21 @@
TEST(TestSendSymphony, SendDataOnly) {
IRsendTest irsend(kGpioUnused);
irsend.begin();
irsend.sendSymphony(0x137);
irsend.sendSymphony(0xD90);
EXPECT_EQ(
"f38000d50"
"m1250s400m1250s400m400s1250m1250s400m1250s400m400s1250m400s1250m1250s400"
"m400s1250m400s1250m400s1250"
"m400s8000"
"m400s7850"
"m1250s400m1250s400m400s1250m1250s400m1250s400m400s1250m400s1250m1250s400"
"m400s1250m400s1250m400s1250"
"m400s8000",
"m400s7850"
"m1250s400m1250s400m400s1250m1250s400m1250s400m400s1250m400s1250m1250s400"
"m400s1250m400s1250m400s1250"
"m400s7850"
"m1250s400m1250s400m400s1250m1250s400m1250s400m400s1250m400s1250m1250s400"
"m400s1250m400s1250m400s1250"
"m400s7850",
irsend.outputStr());
}

Expand Down Expand Up @@ -69,7 +75,7 @@ TEST(TestDecodeSymphony, RealMessageDecode) {
EXPECT_TRUE(irrecv.decode(&irsend.capture));
EXPECT_EQ(decode_type_t::SYMPHONY, irsend.capture.decode_type);
EXPECT_EQ(kSymphonyBits, irsend.capture.bits);
EXPECT_EQ(0x137, irsend.capture.value);
EXPECT_EQ(0xD90, irsend.capture.value);
EXPECT_EQ(0x0, irsend.capture.address);
EXPECT_EQ(0x0, irsend.capture.command);
EXPECT_FALSE(irsend.capture.repeat);
Expand All @@ -81,10 +87,10 @@ TEST(TestDecodeSymphony, RealMessageDecode) {
1198, 1284, 396, 444, 1224, 470, 1200, 470, 1198, 472};
irsend.sendRaw(power, 23, 38000);
irsend.makeDecodeResult();
EXPECT_TRUE(irrecv.decode(&irsend.capture));
EXPECT_TRUE(irrecv.decodeSymphony(&irsend.capture));
EXPECT_EQ(decode_type_t::SYMPHONY, irsend.capture.decode_type);
EXPECT_EQ(kSymphonyBits, irsend.capture.bits);
EXPECT_EQ(0x137, irsend.capture.value);
EXPECT_EQ(0xD90, irsend.capture.value);
EXPECT_EQ(0x0, irsend.capture.address);
EXPECT_EQ(0x0, irsend.capture.command);
EXPECT_FALSE(irsend.capture.repeat);
Expand All @@ -98,7 +104,7 @@ TEST(TestDecodeSymphony, RealMessageDecode) {
EXPECT_TRUE(irrecv.decode(&irsend.capture));
EXPECT_EQ(decode_type_t::SYMPHONY, irsend.capture.decode_type);
EXPECT_EQ(kSymphonyBits, irsend.capture.bits);
EXPECT_EQ(0x13E, irsend.capture.value);
EXPECT_EQ(0xD82, irsend.capture.value);
EXPECT_EQ(0x0, irsend.capture.address);
EXPECT_EQ(0x0, irsend.capture.command);
EXPECT_FALSE(irsend.capture.repeat);
Expand All @@ -122,7 +128,7 @@ TEST(TestDecodeSymphony, RealMessageSentViaLibrary) {
EXPECT_TRUE(irrecv.decode(&irsend.capture));
EXPECT_EQ(decode_type_t::SYMPHONY, irsend.capture.decode_type);
EXPECT_EQ(kSymphonyBits, irsend.capture.bits);
EXPECT_EQ(0x137, irsend.capture.value);
EXPECT_EQ(0xD90, irsend.capture.value);
EXPECT_EQ(0x0, irsend.capture.address);
EXPECT_EQ(0x0, irsend.capture.command);
EXPECT_FALSE(irsend.capture.repeat);
Expand All @@ -139,12 +145,47 @@ TEST(TestDecodeSymphony, RealMessageSentViaLibrary) {
EXPECT_TRUE(irrecv.decode(&irsend.capture));
EXPECT_EQ(decode_type_t::SYMPHONY, irsend.capture.decode_type);
EXPECT_EQ(kSymphonyBits, irsend.capture.bits);
EXPECT_EQ(0x137, irsend.capture.value);
EXPECT_EQ(0xD90, irsend.capture.value);
EXPECT_EQ(0x0, irsend.capture.address);
EXPECT_EQ(0x0, irsend.capture.command);
EXPECT_FALSE(irsend.capture.repeat);
}

// Decode a real SM5021 generated message.
// Note: This used to fail because it had a "long" mark before the gap in mesg.
TEST(TestDecodeSymphony, Issue1105) {
IRsendTest irsend(kGpioUnused);
IRrecv irrecv(kGpioUnused);
irsend.begin();

// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1105#issuecomment-625327833
irsend.reset();
uint16_t rawData[191] = {
1324, 372, 1322, 398, 448, 1218, 476, 1216, 478, 1216, 478, 1218, 478,
1218, 476, 1218, 476, 1220, 474, 1220, 474, 1220, 1346, 7128, 1320, 402,
1290, 404, 440, 1252, 442, 1254, 440, 1254, 440, 1254, 440, 1254, 440,
1254, 440, 1254, 442, 1254, 440, 1254, 1288, 7186, 1288, 408, 1286, 408,
438, 1254, 440, 1254, 440, 1254, 440, 1254, 440, 1254, 440, 1256, 440,
1254, 440, 1254, 440, 1254, 1288, 7186, 1288, 408, 1286, 408, 438, 1254,
440, 1256, 440, 1254, 440, 1254, 440, 1254, 440, 1254, 438, 1256, 438,
1256, 440, 1254, 1288, 7188, 1288, 408, 1286, 408, 438, 1256, 438, 1256,
440, 1254, 438, 1256, 438, 1256, 438, 1256, 438, 1256, 438, 1256, 440,
1254, 1288, 7188, 1286, 408, 1286, 408, 440, 1254, 438, 1256, 438, 1256,
438, 1256, 440, 1254, 438, 1254, 440, 1256, 438, 1256, 440, 1256, 438,
8038, 1284, 408, 1286, 408, 438, 1282, 412, 1282, 414, 1280, 414, 1280,
414, 1282, 414, 1280, 412, 1276, 412, 1286, 414, 1282, 412, 8062, 1260,
434, 1262, 432, 414, 1280, 414, 1282, 412, 1282, 412, 1282, 412, 1282,
414, 1280, 414, 1282, 412, 1282, 412, 1282, 412}; // UNKNOWN 827AA7B
irsend.sendRaw(rawData, 191, 38000);
irsend.makeDecodeResult();
EXPECT_TRUE(irrecv.decode(&irsend.capture));
EXPECT_EQ(decode_type_t::SYMPHONY, irsend.capture.decode_type);
EXPECT_EQ(kSymphonyBits, irsend.capture.bits);
EXPECT_EQ(0xC01, irsend.capture.value);
EXPECT_EQ(0x0, irsend.capture.address);
EXPECT_EQ(0x0, irsend.capture.command);
EXPECT_FALSE(irsend.capture.repeat);
}

TEST(TestUtils, Housekeeping) {
ASSERT_EQ("SYMPHONY", typeToString(decode_type_t::SYMPHONY));
Expand Down

0 comments on commit c905203

Please sign in to comment.