From 6660698f8ba8c42c624900d8466054af2bbf3427 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 25 Feb 2019 23:21:33 +1000 Subject: [PATCH] Experimental support for Samsung 36 bit protocol * sendSamsung36() & decodeSamsung36() added. * Unit test coverage for those routines. * Update examples. For #621 --- examples/IRMQTTServer/IRMQTTServer.ino | 10 +- src/IRrecv.cpp | 4 + src/IRrecv.h | 5 + src/IRremoteESP8266.h | 6 + src/IRsend.h | 4 + src/IRutils.cpp | 3 + src/ir_Samsung.cpp | 123 ++++++++++++++++++++- test/ir_Samsung_test.cpp | 145 ++++++++++++++++++++++++- 8 files changed, 297 insertions(+), 3 deletions(-) diff --git a/examples/IRMQTTServer/IRMQTTServer.ino b/examples/IRMQTTServer/IRMQTTServer.ino index 250e8b6ba..13e68c948 100644 --- a/examples/IRMQTTServer/IRMQTTServer.ino +++ b/examples/IRMQTTServer/IRMQTTServer.ino @@ -261,7 +261,7 @@ const uint16_t kMinUnknownSize = 2 * 10; // ----------------- End of User Configuration Section ------------------------- // Globals -#define _MY_VERSION_ "v0.8.3" +#define _MY_VERSION_ "v0.8.4" // HTML arguments we will parse for IR code information. #define argType "type" #define argData "code" @@ -467,6 +467,7 @@ void handleRoot() { "" "" "" + "" "" "" "" @@ -1413,6 +1414,13 @@ bool sendIRCode(int const ir_type, uint64_t const code, char const * code_str, irsend.sendSAMSUNG(code, bits, repeat); break; #endif +#if SEND_SAMSUNG36 + case SAMSUNG36: // 56 + if (bits == 0) + bits = kSamsung36Bits; + irsend.sendSamsung36(code, bits, repeat); + break; +#endif #if SEND_WHYNTER case WHYNTER: // 8 if (bits == 0) diff --git a/src/IRrecv.cpp b/src/IRrecv.cpp index 9e926de4a..a52a63f18 100644 --- a/src/IRrecv.cpp +++ b/src/IRrecv.cpp @@ -364,6 +364,10 @@ bool IRrecv::decode(decode_results *results, irparams_t *save) { DPRINTLN("Attempting SAMSUNG decode"); if (decodeSAMSUNG(results)) return true; #endif +#if DECODE_SAMSUNG36 + DPRINTLN("Attempting Samsung36 decode"); + if (decodeSamsung36(results)) return true; +#endif #if DECODE_WHYNTER DPRINTLN("Attempting Whynter decode"); if (decodeWhynter(results)) return true; diff --git a/src/IRrecv.h b/src/IRrecv.h index 24df9b960..c0bb38055 100644 --- a/src/IRrecv.h +++ b/src/IRrecv.h @@ -216,6 +216,11 @@ class IRrecv { bool decodeSAMSUNG(decode_results *results, uint16_t nbits = kSamsungBits, bool strict = true); #endif +#if DECODE_SAMSUNG + bool decodeSamsung36(decode_results *results, + const uint16_t nbits = kSamsung36Bits, + const bool strict = true); +#endif #if DECODE_SAMSUNG_AC bool decodeSamsungAC(decode_results *results, uint16_t nbits = kSamsungAcBits, bool strict = true); diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h index 8912d5883..4a80d0301 100644 --- a/src/IRremoteESP8266.h +++ b/src/IRremoteESP8266.h @@ -86,6 +86,9 @@ #define DECODE_SAMSUNG true #define SEND_SAMSUNG true +#define DECODE_SAMSUNG36 true +#define SEND_SAMSUNG36 true + #define DECODE_SAMSUNG_AC true #define SEND_SAMSUNG_AC true @@ -285,6 +288,8 @@ enum decode_type_t { MWM, DAIKIN2, VESTEL_AC, + TECO, // (55) + SAMSUNG36, }; // Message lengths & required repeat values @@ -373,6 +378,7 @@ const uint16_t kRC6Mode0Bits = 20; // Excludes the 'start' bit. const uint16_t kRC6_36Bits = 36; // Excludes the 'start' bit. const uint16_t kRCMMBits = 24; const uint16_t kSamsungBits = 32; +const uint16_t kSamsung36Bits = 36; const uint16_t kSamsungAcStateLength = 14; const uint16_t kSamsungAcBits = kSamsungAcStateLength * 8; const uint16_t kSamsungAcExtendedStateLength = 21; diff --git a/src/IRsend.h b/src/IRsend.h index fddbb8f89..b0131ac88 100644 --- a/src/IRsend.h +++ b/src/IRsend.h @@ -92,6 +92,10 @@ class IRsend { uint16_t repeat = kNoRepeat); uint32_t encodeSAMSUNG(uint8_t customer, uint8_t command); #endif +#if SEND_SAMSUNG36 + void sendSamsung36(const uint64_t data, const uint16_t nbits = kSamsung36Bits, + const uint16_t repeat = kNoRepeat); +#endif #if SEND_SAMSUNG_AC void sendSamsungAC(unsigned char data[], uint16_t nbytes = kSamsungAcStateLength, diff --git a/src/IRutils.cpp b/src/IRutils.cpp index 7c9347d88..da43d5814 100644 --- a/src/IRutils.cpp +++ b/src/IRutils.cpp @@ -231,6 +231,9 @@ std::string typeToString(const decode_type_t protocol, const bool isRepeat) { case SAMSUNG: result = "SAMSUNG"; break; + case SAMSUNG36: + result = "SAMSUNG36"; + break; case SAMSUNG_AC: result = "SAMSUNG_AC"; break; diff --git a/src/ir_Samsung.cpp b/src/ir_Samsung.cpp index d70e571c0..6c90e7481 100644 --- a/src/ir_Samsung.cpp +++ b/src/ir_Samsung.cpp @@ -1,5 +1,5 @@ // Copyright 2009 Ken Shirriff -// Copyright 2017 David Conran +// Copyright 2017, 2018, 2019 David Conran #include "ir_Samsung.h" #include @@ -168,6 +168,127 @@ bool IRrecv::decodeSAMSUNG(decode_results *results, uint16_t nbits, } #endif +#if SEND_SAMSUNG36 +// Send a Samsung 36-bit formatted message. +// +// Args: +// data: The message to be sent. +// nbits: The bit size of the message being sent. typically kSamsung36Bits. +// repeat: The number of times the message is to be repeated. +// +// Status: Alpha / Experimental. +// +// Note: +// Protocol is used by Samsung Bluray Remote: ak59-00167a +// +// Ref: +// https://github.com/markszabo/IRremoteESP8266/issues/621 +void IRsend::sendSamsung36(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + if (nbits < 16) return; // To small to send. + for (uint16_t r = 0; r <= repeat; r++) { + // Block #1 (16 bits) + sendGeneric(kSamsungHdrMark, kSamsungHdrSpace, + kSamsungBitMark, kSamsungOneSpace, + kSamsungBitMark, kSamsungZeroSpace, + kSamsungBitMark, kSamsungHdrSpace, + data >> (nbits - 16), 16, 38, true, 0, kDutyDefault); + // Block #2 (The rest, typically 20 bits) + sendGeneric(0, 0, // No header + kSamsungBitMark, kSamsungOneSpace, + kSamsungBitMark, kSamsungZeroSpace, + kSamsungBitMark, kSamsungMinGap, // Gap is just a guess. + // Mask off the rest of the bits. + data & ((1ULL << (nbits - 16)) - 1), + nbits - 16, 38, true, 0, kDutyDefault); + } +} +#endif // SEND_SAMSUNG36 + +#if DECODE_SAMSUNG36 +// Decode the supplied Samsung36 message. +// +// Args: +// results: Ptr to the data to decode and where to store the decode result. +// nbits: Nr. of bits to expect in the data portion. +// Typically kSamsung36Bits. +// strict: Flag to indicate if we strictly adhere to the specification. +// Returns: +// boolean: True if it can decode it, false if it can't. +// +// Status: Alpha / Experimental +// +// Note: +// Protocol is used by Samsung Bluray Remote: ak59-00167a +// +// Ref: +// https://github.com/markszabo/IRremoteESP8266/issues/621 +bool IRrecv::decodeSamsung36(decode_results *results, const uint16_t nbits, + const bool strict) { + if (results->rawlen < 2 * nbits + kHeader + kFooter * 2 - 1) + return false; // Can't possibly be a valid Samsung message. + // We need to be looking for > 16 bits to make sense. + if (nbits <= 16) return false; + if (strict && nbits != kSamsung36Bits) + return false; // We expect nbits to be 36 bits of message. + + uint64_t data = 0; + uint16_t offset = kStartOffset; + + // Header + if (!matchMark(results->rawbuf[offset], kSamsungHdrMark)) return false; + // Calculate how long the common tick time is based on the header mark. + uint32_t m_tick = results->rawbuf[offset++] * kRawTick / kSamsungHdrMarkTicks; + if (!matchSpace(results->rawbuf[offset], kSamsungHdrSpace)) return false; + // Calculate how long the common tick time is based on the header space. + uint32_t s_tick = + results->rawbuf[offset++] * kRawTick / kSamsungHdrSpaceTicks; + // Data (Block #1) + match_result_t data_result = + matchData(&(results->rawbuf[offset]), 16, + kSamsungBitMarkTicks * m_tick, kSamsungOneSpaceTicks * s_tick, + kSamsungBitMarkTicks * m_tick, kSamsungZeroSpaceTicks * s_tick); + if (data_result.success == false) return false; + data = data_result.data; + offset += data_result.used; + uint16_t bitsSoFar = data_result.used / 2; + // Footer (Block #1) + if (!matchMark(results->rawbuf[offset++], kSamsungBitMarkTicks * m_tick)) + return false; + if (!matchSpace(results->rawbuf[offset++], kSamsungHdrSpaceTicks * s_tick)) + return false; + // Data (Block #2) + data_result = matchData(&(results->rawbuf[offset]), + nbits - 16, + kSamsungBitMarkTicks * m_tick, + kSamsungOneSpaceTicks * s_tick, + kSamsungBitMarkTicks * m_tick, + kSamsungZeroSpaceTicks * s_tick); + if (data_result.success == false) return false; + data <<= (nbits - 16); + data += data_result.data; + offset += data_result.used; + bitsSoFar += data_result.used / 2; + // Footer (Block #2) + if (!matchMark(results->rawbuf[offset++], kSamsungBitMarkTicks * m_tick)) + return false; + if (offset < results->rawlen && + !matchAtLeast(results->rawbuf[offset], kSamsungMinGapTicks * s_tick)) + return false; + + // Compliance + if (nbits != bitsSoFar) return false; + + // Success + results->bits = bitsSoFar; + results->value = data; + results->decode_type = SAMSUNG36; + results->command = data & ((1ULL << (nbits - 16)) - 1); + results->address = data >> (nbits - 16); + return true; +} +#endif // DECODE_SAMSUNG36 + #if SEND_SAMSUNG_AC // Send a Samsung A/C message. // diff --git a/test/ir_Samsung_test.cpp b/test/ir_Samsung_test.cpp index 4c798036b..390e86e79 100644 --- a/test/ir_Samsung_test.cpp +++ b/test/ir_Samsung_test.cpp @@ -1,4 +1,4 @@ -// Copyright 2017 David Conran +// Copyright 2017, 2018, 2019 David Conran #include "ir_Samsung.h" #include "IRrecv.h" @@ -7,6 +7,13 @@ #include "IRsend_test.h" #include "gtest/gtest.h" + +// General housekeeping +TEST(TestSamsung, Housekeeping) { + ASSERT_EQ("SAMSUNG", typeToString(SAMSUNG)); + ASSERT_FALSE(hasACState(SAMSUNG)); +} + // Tests for sendSAMSUNG(). // Test sending typical data only. @@ -281,6 +288,12 @@ TEST(TestDecodeSamsung, FailToDecodeNonSamsungExample) { ASSERT_FALSE(irrecv.decodeSAMSUNG(&irsend.capture, kSamsungBits, false)); } +// General housekeeping +TEST(TestSamsungAC, Housekeeping) { + ASSERT_EQ("SAMSUNG_AC", typeToString(SAMSUNG_AC)); + ASSERT_TRUE(hasACState(SAMSUNG_AC)); +} + // Tests for sendSamsungAC(). // Test sending typical data only. @@ -976,3 +989,133 @@ TEST(TestDecodeSamsungAC, Issue604DecodeExtended) { "Beep: Off, Clean: Off, Quiet: Off", samsung.toString()); } + +TEST(TestSendSamsung36, SendDataOnly) { + IRsendTest irsend(0); + irsend.begin(); + + irsend.reset(); + irsend.sendSamsung36(0); + EXPECT_EQ( + "m4480s4480" + "m560s560m560s560m560s560m560s560m560s560m560s560m560s560m560s560" + "m560s560m560s560m560s560m560s560m560s560m560s560m560s560m560s560" + "m560s4480" + "m560s560m560s560m560s560m560s560m560s560m560s560m560s560m560s560" + "m560s560m560s560m560s560m560s560m560s560m560s560m560s560m560s560" + "m560s560m560s560m560s560m560s560" + "m560s26880", + irsend.outputStr()); + irsend.sendSamsung36(0x400E00FF); + EXPECT_EQ( + "m4480s4480" + "m560s560m560s560m560s560m560s560m560s560m560s1680m560s560m560s560" + "m560s560m560s560m560s560m560s560m560s560m560s560m560s560m560s560" + "m560s4480" + "m560s1680m560s1680m560s1680m560s560m560s560m560s560m560s560m560s560" + "m560s560m560s560m560s560m560s560m560s1680m560s1680m560s1680m560s1680" + "m560s1680m560s1680m560s1680m560s1680" + "m560s26880", + irsend.outputStr()); + irsend.reset(); +} + +// General housekeeping +TEST(TestSamsung36, Housekeeping) { + ASSERT_EQ("SAMSUNG36", typeToString(SAMSUNG36)); + ASSERT_FALSE(hasACState(SAMSUNG36)); +} + +// Test sending with different repeats. +TEST(TestSendSamsung36, SendWithRepeats) { + IRsendTest irsend(0); + irsend.begin(); + + irsend.reset(); + irsend.sendSamsung36(0x400E00FF, kSamsung36Bits, 1); // 1 repeat. + EXPECT_EQ( + "m4480s4480" + "m560s560m560s560m560s560m560s560m560s560m560s1680m560s560m560s560" + "m560s560m560s560m560s560m560s560m560s560m560s560m560s560m560s560" + "m560s4480" + "m560s1680m560s1680m560s1680m560s560m560s560m560s560m560s560m560s560" + "m560s560m560s560m560s560m560s560m560s1680m560s1680m560s1680m560s1680" + "m560s1680m560s1680m560s1680m560s1680" + "m560s26880" + "m4480s4480" + "m560s560m560s560m560s560m560s560m560s560m560s1680m560s560m560s560" + "m560s560m560s560m560s560m560s560m560s560m560s560m560s560m560s560" + "m560s4480" + "m560s1680m560s1680m560s1680m560s560m560s560m560s560m560s560m560s560" + "m560s560m560s560m560s560m560s560m560s1680m560s1680m560s1680m560s1680" + "m560s1680m560s1680m560s1680m560s1680" + "m560s26880", + irsend.outputStr()); + irsend.sendSamsung36(0x400E00FF, kSamsung36Bits, 2); // 2 repeats. + EXPECT_EQ( + "m4480s4480" + "m560s560m560s560m560s560m560s560m560s560m560s1680m560s560m560s560" + "m560s560m560s560m560s560m560s560m560s560m560s560m560s560m560s560" + "m560s4480" + "m560s1680m560s1680m560s1680m560s560m560s560m560s560m560s560m560s560" + "m560s560m560s560m560s560m560s560m560s1680m560s1680m560s1680m560s1680" + "m560s1680m560s1680m560s1680m560s1680" + "m560s26880" + "m4480s4480" + "m560s560m560s560m560s560m560s560m560s560m560s1680m560s560m560s560" + "m560s560m560s560m560s560m560s560m560s560m560s560m560s560m560s560" + "m560s4480" + "m560s1680m560s1680m560s1680m560s560m560s560m560s560m560s560m560s560" + "m560s560m560s560m560s560m560s560m560s1680m560s1680m560s1680m560s1680" + "m560s1680m560s1680m560s1680m560s1680" + "m560s26880" + "m4480s4480" + "m560s560m560s560m560s560m560s560m560s560m560s1680m560s560m560s560" + "m560s560m560s560m560s560m560s560m560s560m560s560m560s560m560s560" + "m560s4480" + "m560s1680m560s1680m560s1680m560s560m560s560m560s560m560s560m560s560" + "m560s560m560s560m560s560m560s560m560s1680m560s1680m560s1680m560s1680" + "m560s1680m560s1680m560s1680m560s1680" + "m560s26880", + irsend.outputStr()); +} + +TEST(TestDecodeSamsung36, RealExample) { + IRsendTest irsend(0); + IRrecv irrecv(0); + irsend.begin(); + + irsend.reset(); + uint16_t rawData[77] = { + 4542, 4438, 568, 432, 562, 436, 536, 462, 538, 460, 538, 460, 564, 1434, + 564, 434, 534, 464, 536, 462, 562, 436, 536, 464, 564, 432, 538, 462, 536, + 464, 534, 464, 564, 420, 566, 4414, 538, 1462, 566, 1432, 562, 1436, 536, + 462, 564, 436, 562, 436, 560, 436, 562, 436, 562, 436, 560, 438, 536, 462, + 562, 436, 562, 1436, 562, 1434, 536, 1462, 564, 1434, 562, 1436, 564, + 1436, 534, 1462, 534, 1464, 536}; // UNKNOWN E4CD1208 + + irsend.sendRaw(rawData, 77, 38000); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + ASSERT_EQ(SAMSUNG36, irsend.capture.decode_type); + EXPECT_EQ(kSamsung36Bits, irsend.capture.bits); + EXPECT_EQ(0x400E00FF, irsend.capture.value); + EXPECT_EQ(0xE00FF, irsend.capture.command); + EXPECT_EQ(0x400, irsend.capture.address); +} + +TEST(TestDecodeSamsung36, SyntheticExample) { + IRsendTest irsend(0); + IRrecv irrecv(0); + irsend.begin(); + irsend.reset(); + + irsend.sendSamsung36(0x400E00FF); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decodeSamsung36(&irsend.capture)); + ASSERT_EQ(SAMSUNG36, irsend.capture.decode_type); + EXPECT_EQ(kSamsung36Bits, irsend.capture.bits); + EXPECT_EQ(0x400E00FF, irsend.capture.value); + EXPECT_EQ(0xE00FF, irsend.capture.command); + EXPECT_EQ(0x400, irsend.capture.address); +}