From e14af3e69eac7a12495c257f0083f3970576df8a Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Tue, 29 Jun 2021 21:37:21 +1000 Subject: [PATCH 1/5] Experimental basic support for Tornado/Sanyo 88 bit A/C protocol * Basic support added via `sendSanyoAc88()` & `decodeSanyoAc88()` * Unit test coverage for the additions/changes. Note: Bit ordering has NOT been determined. Data values may change in future. For #1503 --- src/IRrecv.cpp | 4 ++ src/IRrecv.h | 6 ++ src/IRremoteESP8266.h | 14 ++++- src/IRsend.cpp | 9 +++ src/IRsend.h | 5 ++ src/IRtext.cpp | 1 + src/IRutils.cpp | 1 + src/ir_Sanyo.cpp | 81 ++++++++++++++++++++++++- src/ir_Sanyo.h | 3 +- src/locale/defaults.h | 3 + test/ir_Sanyo_test.cpp | 132 ++++++++++++++++++++++++++++++++++++++++- 11 files changed, 255 insertions(+), 4 deletions(-) diff --git a/src/IRrecv.cpp b/src/IRrecv.cpp index 10c98fd06..887d79c4f 100644 --- a/src/IRrecv.cpp +++ b/src/IRrecv.cpp @@ -1028,6 +1028,10 @@ bool IRrecv::decode(decode_results *results, irparams_t *save, DPRINTLN("Attempting Kelon decode"); if (decodeKelon(results, offset)) return true; #endif // DECODE_KELON +#if DECODE_SANYO_AC88 + DPRINTLN("Attempting SanyoAc88 decode"); + if (decodeSanyoAc88(results, offset)) return true; +#endif // DECODE_SANYO_AC88 // Typically new protocols are added above this line. } #if DECODE_HASH diff --git a/src/IRrecv.h b/src/IRrecv.h index 1e5f3e46d..e748cde32 100644 --- a/src/IRrecv.h +++ b/src/IRrecv.h @@ -308,6 +308,12 @@ class IRrecv { const uint16_t nbits = kSanyoAcBits, const bool strict = true); #endif // DECODE_SANYO_AC +#if DECODE_SANYO_AC88 + bool decodeSanyoAc88(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kSanyoAc88Bits, + const bool strict = true); +#endif // DECODE_SANYO_AC88 #if DECODE_MITSUBISHI bool decodeMitsubishi(decode_results *results, uint16_t offset = kStartOffset, const uint16_t nbits = kMitsubishiBits, diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h index 2796a0e1b..977c964e6 100644 --- a/src/IRremoteESP8266.h +++ b/src/IRremoteESP8266.h @@ -209,6 +209,13 @@ #define SEND_SANYO_AC _IR_ENABLE_DEFAULT_ #endif // SEND_SANYO_AC +#ifndef DECODE_SANYO_AC88 +#define DECODE_SANYO_AC88 _IR_ENABLE_DEFAULT_ +#endif // DECODE_SANYO_AC88 +#ifndef SEND_SANYO_AC88 +#define SEND_SANYO_AC88 _IR_ENABLE_DEFAULT_ +#endif // SEND_SANYO_AC88 + #ifndef DECODE_MITSUBISHI #define DECODE_MITSUBISHI _IR_ENABLE_DEFAULT_ #endif // DECODE_MITSUBISHI @@ -790,6 +797,7 @@ DECODE_HITACHI_AC344 || DECODE_CORONA_AC || DECODE_SANYO_AC || \ DECODE_VOLTAS || DECODE_MIRAGE || DECODE_HAIER_AC176 || \ DECODE_TEKNOPOINT || DECODE_KELON || DECODE_TROTEC_3550 || \ + DECODE_SANYO_AC88 || \ 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 @@ -934,8 +942,9 @@ enum decode_type_t { TEKNOPOINT, KELON, TROTEC_3550, + SANYO_AC88, // 105 // Add new entries before this one, and update it to point to the last entry. - kLastDecodeType = TROTEC_3550, + kLastDecodeType = SANYO_AC88, }; // Message lengths & required repeat values @@ -1121,6 +1130,9 @@ const uint16_t kSamsungAcExtendedBits = kSamsungAcExtendedStateLength * 8; const uint16_t kSamsungAcDefaultRepeat = kNoRepeat; const uint16_t kSanyoAcStateLength = 9; const uint16_t kSanyoAcBits = kSanyoAcStateLength * 8; +const uint16_t kSanyoAc88StateLength = 11; +const uint16_t kSanyoAc88Bits = kSanyoAc88StateLength * 8; +const uint16_t kSanyoAc88MinRepeat = 2; const uint16_t kSanyoSA8650BBits = 12; const uint16_t kSanyoLC7461AddressBits = 13; const uint16_t kSanyoLC7461CommandBits = 8; diff --git a/src/IRsend.cpp b/src/IRsend.cpp index 7fc20895d..28e6b634b 100644 --- a/src/IRsend.cpp +++ b/src/IRsend.cpp @@ -580,6 +580,8 @@ uint16_t IRsend::minRepeats(const decode_type_t protocol) { return kDishMinRepeat; case EPSON: return kEpsonMinRepeat; + case SANYO_AC88: + return kSanyoAc88MinRepeat; case SONY: return kSonyMinRepeat; case SONY_38K: @@ -739,6 +741,8 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) { return kSamsungAcBits; case SANYO_AC: return kSanyoAcBits; + case SANYO_AC88: + return kSanyoAc88Bits; case SHARP_AC: return kSharpAcBits; case TCL112AC: @@ -1247,6 +1251,11 @@ bool IRsend::send(const decode_type_t type, const uint8_t *state, sendSanyoAc(state, nbytes); break; #endif // SEND_SANYO_AC +#if SEND_SANYO_AC88 + case SANYO_AC88: + sendSanyoAc88(state, nbytes); + break; +#endif // SEND_SANYO_AC88 #if SEND_SHARP_AC case SHARP_AC: sendSharpAc(state, nbytes); diff --git a/src/IRsend.h b/src/IRsend.h index 4c865068b..e906178f9 100644 --- a/src/IRsend.h +++ b/src/IRsend.h @@ -319,6 +319,11 @@ class IRsend { const uint16_t nbytes = kSanyoAcStateLength, const uint16_t repeat = kNoRepeat); #endif // SEND_SANYO_AC +#if SEND_SANYO_AC88 + void sendSanyoAc88(const uint8_t *data, + const uint16_t nbytes = kSanyoAc88StateLength, + const uint16_t repeat = kSanyoAc88MinRepeat); +#endif // SEND_SANYO_AC88 #if SEND_DISH // sendDISH() should typically be called with repeat=3 as DISH devices // expect the code to be sent at least 4 times. (code + 3 repeats = 4 codes) diff --git a/src/IRtext.cpp b/src/IRtext.cpp index 0f4f7a8b1..21b96b67f 100644 --- a/src/IRtext.cpp +++ b/src/IRtext.cpp @@ -290,5 +290,6 @@ const PROGMEM char *kAllProtocolNamesStr = D_STR_TEKNOPOINT "\x0" D_STR_KELON "\x0" D_STR_TROTEC_3550 "\x0" + D_STR_SANYO_AC88 "\x0" ///< New protocol strings should be added just above this line. "\x0"; ///< This string requires double null termination. diff --git a/src/IRutils.cpp b/src/IRutils.cpp index 15d2a5143..f9892d2a4 100644 --- a/src/IRutils.cpp +++ b/src/IRutils.cpp @@ -177,6 +177,7 @@ bool hasACState(const decode_type_t protocol) { case PANASONIC_AC: case SAMSUNG_AC: case SANYO_AC: + case SANYO_AC88: case SHARP_AC: case TCL112AC: case TEKNOPOINT: diff --git a/src/ir_Sanyo.cpp b/src/ir_Sanyo.cpp index 637d346fb..39fce67a7 100644 --- a/src/ir_Sanyo.cpp +++ b/src/ir_Sanyo.cpp @@ -1,6 +1,6 @@ // Copyright 2009 Ken Shirriff // Copyright 2016 marcosamarinho -// Copyright 2017-2020 David Conran +// Copyright 2017-2021 David Conran /// @file /// @brief Support for Sanyo protocols. @@ -13,6 +13,7 @@ /// @see http://slydiman.narod.ru/scr/kb/sanyo.htm /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1211 /// @see https://docs.google.com/spreadsheets/d/1dYfLsnYvpjV-SgO8pdinpfuBIpSzm8Q1R5SabrLeskw/edit?usp=sharing +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1503 #include "ir_Sanyo.h" #include @@ -67,6 +68,15 @@ const uint16_t kSanyoAcZeroSpace = 550; ///< uSeconds const uint32_t kSanyoAcGap = kDefaultMessageGap; ///< uSeconds (Guess only) const uint16_t kSanyoAcFreq = 38000; ///< Hz. (Guess only) +const uint16_t kSanyoAc88HdrMark = 5400; ///< uSeconds +const uint16_t kSanyoAc88HdrSpace = 2000; ///< uSeconds +const uint16_t kSanyoAc88BitMark = 500; ///< uSeconds +const uint16_t kSanyoAc88OneSpace = 1500; ///< uSeconds +const uint16_t kSanyoAc88ZeroSpace = 750; ///< uSeconds +const uint32_t kSanyoAc88Gap = 3675; ///< uSeconds +const uint16_t kSanyoAc88Freq = 38000; ///< Hz. (Guess only) +const uint8_t kSanyoAc88ExtraTolerance = 5; /// (%) Extra tolerance to use. + #if SEND_SANYO /// Construct a Sanyo LC7461 message. /// @param[in] address The 13 bit value of the address(Custom) portion of the @@ -656,3 +666,72 @@ String IRSanyoAc::toString(void) const { kOffTimerStr); return result; } + +#if SEND_SANYO_AC88 +/// Send a SanyoAc88 formatted message. +/// Status: ALPHA / Completely untested. +/// @param[in] data An array of bytes containing the IR command. +/// @warning data's bit order may change. It is not yet confirmed. +/// @param[in] nbytes Nr. of bytes of data in the array. +/// @param[in] repeat Nr. of times the message is to be repeated. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1503 +void IRsend::sendSanyoAc88(const uint8_t data[], const uint16_t nbytes, + const uint16_t repeat) { + // (Header + Data + Footer) per repeat + sendGeneric(kSanyoAc88HdrMark, kSanyoAc88HdrSpace, + kSanyoAc88BitMark, kSanyoAc88OneSpace, + kSanyoAc88BitMark, kSanyoAc88ZeroSpace, + kSanyoAc88BitMark, kSanyoAc88Gap, + data, nbytes, kSanyoAc88Freq, false, repeat, kDutyDefault); + space(kDefaultMessageGap); // Make a guess at a post message gap. +} +#endif // SEND_SANYO_AC88 + +#if DECODE_SANYO_AC88 +/// Decode the supplied SanyoAc message. +/// Status: ALPHA / Untested. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// @warning data's bit order may change. It is not yet confirmed. +/// @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/1503 +bool IRrecv::decodeSanyoAc88(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kSanyoAc88Bits) + return false; + + uint16_t used = 0; + // Compliance + const uint16_t expected_repeats = strict ? kSanyoAc88MinRepeat : 0; + + // Handle the expected nr of repeats. + for (uint16_t r = 0; r <= expected_repeats; r++) { + // Header + Data + Footer + used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + kSanyoAc88HdrMark, kSanyoAc88HdrSpace, + kSanyoAc88BitMark, kSanyoAc88OneSpace, + kSanyoAc88BitMark, kSanyoAc88ZeroSpace, + kSanyoAc88BitMark, + // Expect an inter-message gap, or just the end of msg? + (r < expected_repeats) ? kSanyoAc88Gap + : kDefaultMessageGap, + r == expected_repeats, + _tolerance + kSanyoAc88ExtraTolerance, + kMarkExcess, false); + if (!used) return false; // No match! + offset += used; + } + + // Success + results->decode_type = decode_type_t::SANYO_AC88; + 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_SANYO_AC88 diff --git a/src/ir_Sanyo.h b/src/ir_Sanyo.h index d02b06c21..be699cc56 100644 --- a/src/ir_Sanyo.h +++ b/src/ir_Sanyo.h @@ -1,4 +1,4 @@ -// Copyright 2020 David Conran +// Copyright 2020-2021 David Conran /// @file /// @brief Support for Sanyo protocols. @@ -11,6 +11,7 @@ /// @see http://slydiman.narod.ru/scr/kb/sanyo.htm /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1211 /// @see https://docs.google.com/spreadsheets/d/1dYfLsnYvpjV-SgO8pdinpfuBIpSzm8Q1R5SabrLeskw/edit?usp=sharing +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1503 // Supports: // Brand: Sanyo, Model: SA 8650B - disabled diff --git a/src/locale/defaults.h b/src/locale/defaults.h index 6b38c0b28..dd3108cb4 100644 --- a/src/locale/defaults.h +++ b/src/locale/defaults.h @@ -742,6 +742,9 @@ #ifndef D_STR_SANYO_AC #define D_STR_SANYO_AC "SANYO_AC" #endif // D_STR_SANYO_AC +#ifndef D_STR_SANYO_AC88 +#define D_STR_SANYO_AC88 "SANYO_AC88" +#endif // D_STR_SANYO_AC88 #ifndef D_STR_SANYO_LC7461 #define D_STR_SANYO_LC7461 "SANYO_LC7461" #endif // D_STR_SANYO_LC7461 diff --git a/test/ir_Sanyo_test.cpp b/test/ir_Sanyo_test.cpp index c494e87d8..38d7344ef 100644 --- a/test/ir_Sanyo_test.cpp +++ b/test/ir_Sanyo_test.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2020 David Conran +// Copyright 2017-2021 David Conran #include "ir_Sanyo.h" #include "IRac.h" @@ -277,6 +277,13 @@ TEST(TestUtils, Housekeeping) { ASSERT_TRUE(IRac::isProtocolSupported(decode_type_t::SANYO_AC)); ASSERT_EQ(kSanyoAcBits, IRsend::defaultBits(decode_type_t::SANYO_AC)); ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::SANYO_AC)); + // Sanyo A/C 88 Bit. + ASSERT_EQ("SANYO_AC88", typeToString(decode_type_t::SANYO_AC88)); + ASSERT_EQ(decode_type_t::SANYO_AC88, strToDecodeType("SANYO_AC88")); + ASSERT_TRUE(hasACState(decode_type_t::SANYO_AC88)); + ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::SANYO_AC88)); + ASSERT_EQ(kSanyoAc88Bits, IRsend::defaultBits(decode_type_t::SANYO_AC88)); + ASSERT_EQ(kSanyoAc88MinRepeat, IRsend::minRepeats(decode_type_t::SANYO_AC88)); } TEST(TestDecodeSanyoAc, DecodeRealExamples) { @@ -530,3 +537,126 @@ TEST(TestSanyoAcClass, Beep) { ac.setRaw(beep_on); EXPECT_TRUE(ac.getBeep()); } + +TEST(TestDecodeSanyoAc88, DecodeRealExamples) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + // Ref: "On" from https://github.com/crankyoldgit/IRremoteESP8266/issues/1503#issuecomment-868450739 + const uint16_t rawData[539] = {5374, 1972, 512, 762, 522, 1510, 498, 780, 578, + 1486, 462, 790, 462, 1550, 544, 708, 510, 1500, 544, 1470, 516, 762, 482, + 770, 516, 1496, 518, 1510, 498, 780, 468, 1544, 466, 786, 466, 786, 466, + 786, 466, 786, 486, 748, 526, 756, 506, 1520, 516, 764, 516, 1492, 518, + 762, 544, 708, 548, 704, 602, 1434, 556, 1456, 524, 726, 552, 700, 554, + 698, 648, 1438, 548, 684, 536, 1478, 568, 682, 568, 682, 578, 702, 568, + 684, 570, 712, 624, 724, 528, 1482, 528, 726, 528, 1482, 526, 724, 546, + 1524, 568, 682, 540, 710, 544, 734, 542, 1516, 492, 762, 488, 744, 546, + 1482, 492, 788, 544, 734, 494, 758, 492, 760, 490, 744, 548, 828, 494, + 760, 494, 758, 494, 758, 494, 786, 492, 760, 494, 758, 494, 742, 508, 746, + 508, 788, 492, 760, 494, 786, 494, 786, 494, 742, 508, 744, 508, 788, 570, + 760, 494, 784, 494, 788, 568, 786, 492, 744, 508, 772, 608, 768, 538, 714, + 490, 786, 628, 728, 494, 786, 494, 758, 494, 742, 508, 1390, 494, 3692, + 5394, 1960, 494, 784, 494, 1516, 494, 760, 494, 1516, 494, 760, 492, 1516, + 494, 760, 492, 1518, 524, 1486, 494, 790, 486, 764, 492, 1524, 538, 1472, + 540, 740, 538, 1546, 546, 728, 524, 726, 526, 728, 524, 728, 526, 728, + 524, 728, 524, 1486, 526, 726, 546, 1464, 550, 702, 550, 728, 552, 702, + 550, 1460, 550, 1460, 550, 702, 552, 728, 550, 702, 550, 1460, 550, 728, + 550, 1464, 548, 704, 546, 704, 548, 732, 546, 704, 548, 704, 548, 730, + 548, 1462, 548, 706, 518, 1492, 546, 706, 518, 1490, 546, 706, 520, 732, + 520, 732, 522, 1492, 518, 760, 518, 732, 546, 1468, 516, 736, 516, 732, + 520, 732, 520, 758, 520, 818, 478, 772, 480, 802, 454, 894, 520, 734, 518, + 734, 518, 760, 518, 734, 518, 734, 518, 718, 506, 746, 480, 818, 488, 790, + 490, 744, 508, 790, 490, 744, 510, 770, 508, 788, 490, 762, 544, 788, 490, + 788, 462, 818, 566, 788, 490, 744, 480, 770, 456, 794, 458, 894, 492, 762, + 492, 760, 490, 744, 508, 1390, 492, 3664, + 5398, 1960, 490, 746, 476, 1552, 492, 742, 508, 1504, 508, 746, 504, 1522, + 494, 786, 492, 1516, 494, 1516, 494, 760, 492, 786, 494, 1520, 490, 1520, + 458, 822, 458, 1554, 510, 788, 494, 784, 494, 760, 492, 742, 508, 770, + 512, 786, 492, 1516, 520, 760, 520, 1488, 520, 734, 520, 760, 518, 734, + 520, 1490, 520, 1494, 516, 734, 518, 732, 520, 762, 516, 762, 488, 1522, + 458, 1554, 506, 774, 506, 744, 508, 774, 508, 770, 510, 742, 506, 746, + 540, 1472, 506, 744, 508, 1522, 488, 764, 488, 1504, 538, 758, 520, 734, + 520, 760, 520, 1490, 518, 734, 520, 734, 520, 1490, 520, 760, 518, 760, + 538, 770, 488, 764, 516, 734, 518, 734, 518, 818, 476, 802, 476, 774, 478, + 904, 538, 734, 520, 762, 516, 818, 510, 748, 476, 772, 458, 794, 458, 792, + 516, 736, 516, 736, 516, 736, 516, 738, 514, 762, 516, 764, 514, 738, 516, + 738, 538, 766, 516, 738, 514, 738, 514, 766, 512, 794, 486, 768, 486, 794, + 486, 766, 512, 738, 488, 1398, 504}; // UNKNOWN ABF4C698 + + const uint8_t expectedState[kSanyoAc88StateLength] = { + 0xAA, 0x59, 0xA0, 0x18, 0x06, 0x2A, 0x12, 0x00, 0x00, 0x00, 0x80}; + irsend.begin(); + irsend.reset(); + irsend.sendRaw(rawData, 539, 38000); + irsend.makeDecodeResult(); + + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + ASSERT_EQ(SANYO_AC88, irsend.capture.decode_type); + EXPECT_EQ(kSanyoAc88Bits, irsend.capture.bits); + EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits); + EXPECT_FALSE(irsend.capture.repeat); + EXPECT_EQ( + "", + IRAcUtils::resultAcToString(&irsend.capture)); +} + +TEST(TestDecodeSanyoAc88, SyntheticSelfDecode) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + const uint8_t expectedState[kSanyoAc88StateLength] = { + 0xAA, 0x59, 0xA0, 0x18, 0x06, 0x2A, 0x12, 0x00, 0x00, 0x00, 0x80}; + irsend.begin(); + irsend.reset(); + irsend.sendSanyoAc88(expectedState); + irsend.makeDecodeResult(); + + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(SANYO_AC88, irsend.capture.decode_type); + EXPECT_EQ(kSanyoAc88Bits, irsend.capture.bits); + EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits); + EXPECT_FALSE(irsend.capture.repeat); + EXPECT_EQ( + "", + IRAcUtils::resultAcToString(&irsend.capture)); + EXPECT_EQ( + "f38000d50" + "m5400s2000" + "m500s750m500s1500m500s750m500s1500m500s750m500s1500m500s750m500s1500" + "m500s1500m500s750m500s750m500s1500m500s1500m500s750m500s1500m500s750" + "m500s750m500s750m500s750m500s750m500s750m500s1500m500s750m500s1500" + "m500s750m500s750m500s750m500s1500m500s1500m500s750m500s750m500s750" + "m500s750m500s1500m500s1500m500s750m500s750m500s750m500s750m500s750" + "m500s750m500s1500m500s750m500s1500m500s750m500s1500m500s750m500s750" + "m500s750m500s1500m500s750m500s750m500s1500m500s750m500s750m500s750" + "m500s750m500s750m500s750m500s750m500s750m500s750m500s750m500s750" + "m500s750m500s750m500s750m500s750m500s750m500s750m500s750m500s750" + "m500s750m500s750m500s750m500s750m500s750m500s750m500s750m500s750" + "m500s750m500s750m500s750m500s750m500s750m500s750m500s750m500s1500" + "m500s3675" + "m5400s2000" + "m500s750m500s1500m500s750m500s1500m500s750m500s1500m500s750m500s1500" + "m500s1500m500s750m500s750m500s1500m500s1500m500s750m500s1500m500s750" + "m500s750m500s750m500s750m500s750m500s750m500s1500m500s750m500s1500" + "m500s750m500s750m500s750m500s1500m500s1500m500s750m500s750m500s750" + "m500s750m500s1500m500s1500m500s750m500s750m500s750m500s750m500s750" + "m500s750m500s1500m500s750m500s1500m500s750m500s1500m500s750m500s750" + "m500s750m500s1500m500s750m500s750m500s1500m500s750m500s750m500s750" + "m500s750m500s750m500s750m500s750m500s750m500s750m500s750m500s750" + "m500s750m500s750m500s750m500s750m500s750m500s750m500s750m500s750" + "m500s750m500s750m500s750m500s750m500s750m500s750m500s750m500s750" + "m500s750m500s750m500s750m500s750m500s750m500s750m500s750m500s1500" + "m500s3675" + "m5400s2000" + "m500s750m500s1500m500s750m500s1500m500s750m500s1500m500s750m500s1500" + "m500s1500m500s750m500s750m500s1500m500s1500m500s750m500s1500m500s750" + "m500s750m500s750m500s750m500s750m500s750m500s1500m500s750m500s1500" + "m500s750m500s750m500s750m500s1500m500s1500m500s750m500s750m500s750" + "m500s750m500s1500m500s1500m500s750m500s750m500s750m500s750m500s750" + "m500s750m500s1500m500s750m500s1500m500s750m500s1500m500s750m500s750" + "m500s750m500s1500m500s750m500s750m500s1500m500s750m500s750m500s750" + "m500s750m500s750m500s750m500s750m500s750m500s750m500s750m500s750" + "m500s750m500s750m500s750m500s750m500s750m500s750m500s750m500s750" + "m500s750m500s750m500s750m500s750m500s750m500s750m500s750m500s750" + "m500s750m500s750m500s750m500s750m500s750m500s750m500s750m500s1500" + "m500s103675", + irsend.outputStr()); +} From 33bb3bbc56ab5f62e5d844ab837e61e30d83ca44 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Sun, 1 Aug 2021 23:10:57 +1000 Subject: [PATCH 2/5] Experimental detailed support for Tornado/Sanyo 88-bit A/C * Settings supported: - Power - Mode - Temp - Fan Speed - Sleep - Turbo - Filter * Add support in `IRac` class. * Unit tests added & updated. * Other minor code cleanups. Note: Protocol breakdown is nowhere near complete. This is untested. Not enough data supplied. For #1503 --- src/IRac.cpp | 65 +++++++++++- src/IRac.h | 7 ++ src/ir_Sanyo.cpp | 226 +++++++++++++++++++++++++++++++++++++++++ src/ir_Sanyo.h | 107 +++++++++++++++++++ test/IRac_test.cpp | 28 +++++ test/ir_Sanyo_test.cpp | 180 ++++++++++++++++++++++++++++---- 6 files changed, 594 insertions(+), 19 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index 669b07630..7a37ed2eb 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -259,6 +259,9 @@ bool IRac::isProtocolSupported(const decode_type_t protocol) { #if SEND_SANYO_AC case decode_type_t::SANYO_AC: #endif +#if SEND_SANYO_AC88 + case decode_type_t::SANYO_AC88: +#endif #if SEND_SHARP_AC case decode_type_t::SHARP_AC: #endif @@ -1766,7 +1769,7 @@ void IRac::samsung(IRSamsungAc *ac, #endif // SEND_SAMSUNG_AC #if SEND_SANYO_AC -/// Send a Toshiba A/C message with the supplied settings. +/// Send a Sanyo A/C message with the supplied settings. /// @param[in, out] ac A Ptr to an IRSanyoAc object to use. /// @param[in] on The power setting. /// @param[in] mode The operation mode setting. @@ -1804,6 +1807,42 @@ void IRac::sanyo(IRSanyoAc *ac, } #endif // SEND_SANYO_AC +#if SEND_SANYO_AC88 +/// Send a Sanyo 88-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRSanyoAc88 object to use. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] filter Turn on the (ion/pollen/etc) filter mode. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +void IRac::sanyo88(IRSanyoAc88 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const bool turbo, + const bool filter, const int16_t sleep) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingV(swingv != stdAc::swingv_t::kOff); + // No Horizontal swing setting available. + // No Quiet setting available. + ac->setTurbo(turbo); + // No Econo setting available. + // No Light setting available. + ac->setFilter(filter); + // No Clean setting available. + // No Beep setting available. + ac->setSleep(sleep >= 0); // Sleep is either on/off, so convert to boolean. + // No Clock setting available. + ac->send(); +} +#endif // SEND_SANYO_AC88 + #if SEND_SHARP_AC /// Send a Sharp A/C message with the supplied settings. /// @note Multiple IR messages may be generated & sent. @@ -2798,6 +2837,15 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { break; } #endif // SEND_SANYO_AC +#if SEND_SANYO_AC88 + case SANYO_AC88: + { + IRSanyoAc88 ac(_pin, _inverted, _modulation); + sanyo88(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.turbo, send.filter, send.sleep); + break; + } +#endif // SEND_SANYO_AC88 #if SEND_SHARP_AC case SHARP_AC: { @@ -3529,6 +3577,13 @@ namespace IRAcUtils { return ac.toString(); } #endif // DECODE_SANYO_AC +#if DECODE_SANYO_AC88 + case decode_type_t::SANYO_AC88: { + IRSanyoAc88 ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_SANYO_AC88 #if DECODE_SHARP_AC case decode_type_t::SHARP_AC: { IRSharpAc ac(kGpioUnused); @@ -4006,6 +4061,14 @@ namespace IRAcUtils { break; } #endif // DECODE_SANYO_AC +#if DECODE_SANYO_AC88 + case decode_type_t::SANYO_AC88: { + IRSanyoAc88 ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_SANYO_AC88 #if DECODE_SHARP_AC case decode_type_t::SHARP_AC: { IRSharpAc ac(kGpioUnused); diff --git a/src/IRac.h b/src/IRac.h index 203de6e53..db9475dd1 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -393,6 +393,13 @@ void electra(IRElectraAc *ac, const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, const bool beep, const int16_t sleep = -1); #endif // SEND_SANYO_AC +#if SEND_SANYO_AC88 + void sanyo88(IRSanyoAc88 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const bool turbo, + const bool filter, const int16_t sleep = -1); +#endif // SEND_SANYO_AC88 #if SEND_SHARP_AC void sharp(IRSharpAc *ac, const sharp_ac_remote_model_t model, const bool on, const bool prev_power, const stdAc::opmode_t mode, diff --git a/src/ir_Sanyo.cpp b/src/ir_Sanyo.cpp index 39fce67a7..e7cfbdfed 100644 --- a/src/ir_Sanyo.cpp +++ b/src/ir_Sanyo.cpp @@ -735,3 +735,229 @@ bool IRrecv::decodeSanyoAc88(decode_results *results, uint16_t offset, return true; } #endif // DECODE_SANYO_AC88 + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRSanyoAc88::IRSanyoAc88(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the state of the remote to a known good state/sequence. +/// @see https://docs.google.com/spreadsheets/d/1dYfLsnYvpjV-SgO8pdinpfuBIpSzm8Q1R5SabrLeskw/edit?ts=5f0190a5#gid=1050142776&range=A2:B2 +void IRSanyoAc88::stateReset(void) { + static const uint8_t kReset[kSanyoAc88StateLength] = { + 0xAA, 0x55, 0xA0, 0x16, 0x0F, 0x21, 0x01, 0x01, 0x00, 0x00, 0x10}; + std::memcpy(_.raw, kReset, kSanyoAc88StateLength); +} + +/// Set up hardware to be able to send a message. +void IRSanyoAc88::begin(void) { _irsend.begin(); } + +#if SEND_SANYO_AC +/// Send the current internal state as IR messages. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRSanyoAc88::send(const uint16_t repeat) { + _irsend.sendSanyoAc88(getRaw(), kSanyoAc88StateLength, repeat); +} +#endif // SEND_SANYO_AC + +/// Get a PTR to the internal state/code for this protocol with all integrity +/// checks passing. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t* IRSanyoAc88::getRaw(void) { + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] newState A valid code for this protocol. +void IRSanyoAc88::setRaw(const uint8_t newState[]) { + std::memcpy(_.raw, newState, kSanyoAc88StateLength); +} + +/// Set the requested power state of the A/C to on. +void IRSanyoAc88::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRSanyoAc88::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSanyoAc88::setPower(const bool on) { _.Power = on; } + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRSanyoAc88::getPower(void) const { return _.Power; } + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRSanyoAc88::getMode(void) const { return _.Mode; } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +/// @note If we get an unexpected mode, default to AUTO. +void IRSanyoAc88::setMode(const uint8_t mode) { + switch (mode) { + case kSanyoAc88Auto: + case kSanyoAc88FeelCool: + case kSanyoAc88Cool: + case kSanyoAc88FeelHeat: + case kSanyoAc88Heat: + case kSanyoAc88Fan: + _.Mode = mode; + break; + default: _.Mode = kSanyoAc88Auto; + } +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRSanyoAc88::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kSanyoAc88Cool; + case stdAc::opmode_t::kHeat: return kSanyoAc88Heat; + case stdAc::opmode_t::kFan: return kSanyoAc88Fan; + default: return kSanyoAc88Auto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRSanyoAc88::toCommonMode(const uint8_t mode) { + switch (mode) { + case kSanyoAc88FeelCool: + case kSanyoAc88Cool: + return stdAc::opmode_t::kCool; + case kSanyoAc88FeelHeat: + case kSanyoAc88Heat: + return stdAc::opmode_t::kHeat; + case kSanyoAc88Fan: + return stdAc::opmode_t::kFan; + default: + return stdAc::opmode_t::kAuto; + } +} + +/// Set the desired temperature. +/// @param[in] degrees The temperature in degrees celsius. +void IRSanyoAc88::setTemp(const uint8_t degrees) { + uint8_t temp = std::max((uint8_t)kSanyoAc88TempMin, degrees); + _.Temp = std::min((uint8_t)kSanyoAc88TempMax, temp); +} + +/// Get the current desired temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRSanyoAc88::getTemp(void) const { return _.Temp; } + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRSanyoAc88::setFan(const uint8_t speed) { _.Fan = speed; } + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRSanyoAc88::getFan(void) const { return _.Fan; } + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRSanyoAc88::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kSanyoAc88FanLow; + case stdAc::fanspeed_t::kMedium: return kSanyoAc88FanMedium; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kSanyoAc88FanHigh; + default: return kSanyoAc88FanAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] spd The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRSanyoAc88::toCommonFanSpeed(const uint8_t spd) { + switch (spd) { + case kSanyoAc88FanHigh: return stdAc::fanspeed_t::kHigh; + case kSanyoAc88FanMedium: return stdAc::fanspeed_t::kMedium; + case kSanyoAc88FanLow: return stdAc::fanspeed_t::kLow; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Change the SwingV setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSanyoAc88::setSwingV(const bool on) { _.SwingV = on; } + +/// Get the value of the current SwingV setting. +/// @return true, the setting is on. false, the setting is off. +bool IRSanyoAc88::getSwingV(void) const { return _.SwingV; } + +/// Change the Turbo setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSanyoAc88::setTurbo(const bool on) { _.Turbo = on; } + +/// Get the value of the current Turbo setting. +/// @return true, the setting is on. false, the setting is off. +bool IRSanyoAc88::getTurbo(void) const { return _.Turbo; } + +/// Change the Filter setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSanyoAc88::setFilter(const bool on) { _.Filter = on; } + +/// Get the value of the current Filter setting. +/// @return true, the setting is on. false, the setting is off. +bool IRSanyoAc88::getFilter(void) const { return _.Filter; } + +/// Change the Sleep setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRSanyoAc88::setSleep(const bool on) { _.Sleep = on; } + +/// Get the value of the current Sleep setting. +/// @return true, the setting is on. false, the setting is off. +bool IRSanyoAc88::getSleep(void) const { return _.Sleep; } + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRSanyoAc88::toCommon(void) const { + stdAc::state_t result; + result.protocol = decode_type_t::SANYO_AC88; + result.model = -1; // Not supported. + result.power = getPower(); + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = _.SwingV ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; + result.filter = _.Filter; + result.turbo = _.Turbo; + result.sleep = _.Sleep ? 0 : -1; + // Not supported. + result.swingh = stdAc::swingh_t::kOff; + result.econo = false; + result.light = false; + result.quiet = false; + result.beep = false; + result.clean = false; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRSanyoAc88::toString(void) const { + String result = ""; + result.reserve(100); + result += addBoolToString(getPower(), kPowerStr, false); + result += addModeToString(_.Mode, kSanyoAc88Auto, kSanyoAc88Cool, + kSanyoAc88Heat, kSanyoAc88Auto, kSanyoAc88Fan); + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kSanyoAc88FanHigh, kSanyoAc88FanLow, + kSanyoAc88FanAuto, kSanyoAc88FanAuto, + kSanyoAc88FanMedium); + result += addBoolToString(_.SwingV, kSwingVStr); + result += addBoolToString(_.Turbo, kTurboStr); + result += addBoolToString(_.Sleep, kSleepStr); + return result; +} diff --git a/src/ir_Sanyo.h b/src/ir_Sanyo.h index be699cc56..acde7f2b3 100644 --- a/src/ir_Sanyo.h +++ b/src/ir_Sanyo.h @@ -12,6 +12,7 @@ /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1211 /// @see https://docs.google.com/spreadsheets/d/1dYfLsnYvpjV-SgO8pdinpfuBIpSzm8Q1R5SabrLeskw/edit?usp=sharing /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1503 +/// @see https://docs.google.com/spreadsheets/d/1weUmGAsEpfX38gg5rlDN69Uchnbr6gQl9FqHffLBIRk/edit#gid=0 // Supports: // Brand: Sanyo, Model: SA 8650B - disabled @@ -167,4 +168,110 @@ class IRSanyoAc { const uint16_t length = kSanyoAcStateLength); }; +const uint8_t kSanyoAc88Auto = 0; ///< 0b000 +const uint8_t kSanyoAc88FeelCool = 1; ///< 0b001 +const uint8_t kSanyoAc88Cool = 2; ///< 0b010 +const uint8_t kSanyoAc88FeelHeat = 3; ///< 0b011 +const uint8_t kSanyoAc88Heat = 4; ///< 0b100 +const uint8_t kSanyoAc88Fan = 5; ///< 0b101 + +const uint8_t kSanyoAc88TempMin = 10; ///< Celsius +const uint8_t kSanyoAc88TempMax = 30; ///< Celsius + +const uint8_t kSanyoAc88FanAuto = 0; ///< 0b00 +const uint8_t kSanyoAc88FanLow = 1; ///< 0b11 +const uint8_t kSanyoAc88FanMedium = 2; ///< 0b10 +const uint8_t kSanyoAc88FanHigh = 3; ///< 0b11 + +/// Native representation of a Sanyo 88-bit A/C message. +union SanyoAc88Protocol{ + uint8_t raw[kSanyoAc88StateLength]; ///< The state in IR code form. + // Ref: https://docs.google.com/spreadsheets/d/1weUmGAsEpfX38gg5rlDN69Uchnbr6gQl9FqHffLBIRk/edit#gid=0 + struct { + // Byte 0-1 + uint8_t :8; // 0xAA (Fixed?) + uint8_t :8; // 0x55 (Fixed?) + // Byte 2 + uint8_t Fan :2; + uint8_t :2; + uint8_t Mode :3; + uint8_t Power :1; + // Byte 3 + uint8_t Temp :5; + uint8_t Filter :1; + uint8_t SwingV :1; + uint8_t :1; + // Byte 4-9 (Timer times?) + uint8_t :8; + uint8_t :8; + uint8_t :8; + uint8_t :8; + uint8_t :8; + uint8_t :8; + // Byte 10 + uint8_t :3; + uint8_t Turbo :1; + uint8_t EnableStartTimer :1; + uint8_t EnableStopTimer :1; + uint8_t Sleep :1; + uint8_t :1; + }; +}; + +// Classes +/// Class for handling detailed Sanyo A/C messages. +class IRSanyoAc88 { + public: + explicit IRSanyoAc88(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_SANYO_AC88 + void send(const uint16_t repeat = kSanyoAc88MinRepeat); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_SANYO_AC88 + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t degrees); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + void setSleep(const bool on); + bool getSleep(void) const; + void setTurbo(const bool on); + bool getTurbo(void) const; + void setFilter(const bool on); + bool getFilter(void) const; + void setSwingV(const bool on); + bool getSwingV(void) const; + void setRaw(const uint8_t newState[]); + uint8_t* getRaw(void); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + SanyoAc88Protocol _; + void checksum(void); + static uint8_t calcChecksum(const uint8_t state[], + const uint16_t length = kSanyoAcStateLength); +}; #endif // IR_SANYO_H_ diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index 933a860f1..f7bf48981 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -1548,6 +1548,34 @@ TEST(TestIRac, Sanyo) { ASSERT_TRUE(IRAcUtils::decodeToState(&ac._irsend.capture, &r, &p)); } +TEST(TestIRac, Sanyo88) { + IRSanyoAc88 ac(kGpioUnused); + IRac irac(kGpioUnused); + IRrecv capture(kGpioUnused); + const char expected[] = + "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 2 (Medium), Swing(V): On, " + "Turbo: On, Sleep: On"; + + ac.begin(); + irac.sanyo88(&ac, + true, // Power + stdAc::opmode_t::kCool, // Mode + 28, // Celsius + stdAc::fanspeed_t::kMedium, // Fan speed + stdAc::swingv_t::kAuto, // Vertical Swing + true, // Turbo + true, // Filter + 17); // Sleep + ASSERT_EQ(expected, ac.toString()); + ac._irsend.makeDecodeResult(); + EXPECT_TRUE(capture.decode(&ac._irsend.capture)); + ASSERT_EQ(SANYO_AC88, ac._irsend.capture.decode_type); + ASSERT_EQ(kSanyoAc88Bits, ac._irsend.capture.bits); + ASSERT_EQ(expected, IRAcUtils::resultAcToString(&ac._irsend.capture)); + stdAc::state_t r, p; + ASSERT_TRUE(IRAcUtils::decodeToState(&ac._irsend.capture, &r, &p)); +} + TEST(TestIRac, Sharp) { IRSharpAc ac(kGpioUnused); IRac irac(kGpioUnused); diff --git a/test/ir_Sanyo_test.cpp b/test/ir_Sanyo_test.cpp index 38d7344ef..7071309a2 100644 --- a/test/ir_Sanyo_test.cpp +++ b/test/ir_Sanyo_test.cpp @@ -11,7 +11,7 @@ // Tests for encodeSanyoLC7461(). TEST(TestEncodeSanyoLC7461, NormalEncoding) { - IRsendTest irsend(4); + IRsendTest irsend(kGpioUnused); EXPECT_EQ(0x1FFF00FF, irsend.encodeSanyoLC7461(0, 0)); EXPECT_EQ(0x3FFE01FE, irsend.encodeSanyoLC7461(1, 1)); EXPECT_EQ(0x3FFE02FD, irsend.encodeSanyoLC7461(1, 2)); @@ -25,7 +25,7 @@ TEST(TestEncodeSanyoLC7461, NormalEncoding) { // Test sending typical data only. TEST(TestEncodeSanyoLC7461, SendDataOnly) { - IRsendTest irsend(4); + IRsendTest irsend(kGpioUnused); irsend.begin(); irsend.reset(); @@ -44,7 +44,7 @@ TEST(TestEncodeSanyoLC7461, SendDataOnly) { // Test sending with different repeats. TEST(TestEncodeSanyoLC7461, SendWithRepeats) { - IRsendTest irsend(4); + IRsendTest irsend(kGpioUnused); irsend.begin(); irsend.reset(); @@ -66,8 +66,8 @@ TEST(TestEncodeSanyoLC7461, SendWithRepeats) { // Decode normal Sanyo LC7461 messages. TEST(TestDecodeSanyoLC7461, NormalDecodeWithStrict) { - IRsendTest irsend(4); - IRrecv irrecv(4); + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); irsend.begin(); // Normal Sanyo LC7461 42-bit message. @@ -115,8 +115,8 @@ TEST(TestDecodeSanyoLC7461, NormalDecodeWithStrict) { // Decode normal repeated Sanyo LC7461 messages. TEST(TestDecodeSanyoLC7461, NormalDecodeWithRepeatAndStrict) { - IRsendTest irsend(4); - IRrecv irrecv(4); + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); irsend.begin(); // Normal Sanyo LC7461 16-bit message with 1 repeat. @@ -136,8 +136,8 @@ TEST(TestDecodeSanyoLC7461, NormalDecodeWithRepeatAndStrict) { // Decode unsupported Sanyo LC7461 messages. TEST(TestDecodeSanyoLC7461, DecodeWithNonStrictValues) { - IRsendTest irsend(4); - IRrecv irrecv(4); + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); irsend.begin(); irsend.reset(); @@ -191,8 +191,8 @@ TEST(TestDecodeSanyoLC7461, DecodeWithNonStrictValues) { // Decode (non-standard) 64-bit messages. TEST(TestDecodeSanyoLC7461, Decode64BitMessages) { - IRsendTest irsend(4); - IRrecv irrecv(4); + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); irsend.begin(); irsend.reset(); @@ -211,8 +211,8 @@ TEST(TestDecodeSanyoLC7461, Decode64BitMessages) { // Decode a 'real' example via GlobalCache TEST(TestDecodeSanyoLC7461, DecodeGlobalCacheExample) { - IRsendTest irsend(4); - IRrecv irrecv(4); + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); irsend.begin(); irsend.reset(); @@ -243,8 +243,8 @@ TEST(TestDecodeSanyoLC7461, DecodeGlobalCacheExample) { // Fail to decode a non-Sanyo LC7461 example via GlobalCache TEST(TestDecodeSanyoLC7461, FailToDecodeNonSanyoLC7461Example) { - IRsendTest irsend(4); - IRrecv irrecv(4); + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); irsend.begin(); irsend.reset(); @@ -281,7 +281,7 @@ TEST(TestUtils, Housekeeping) { ASSERT_EQ("SANYO_AC88", typeToString(decode_type_t::SANYO_AC88)); ASSERT_EQ(decode_type_t::SANYO_AC88, strToDecodeType("SANYO_AC88")); ASSERT_TRUE(hasACState(decode_type_t::SANYO_AC88)); - ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::SANYO_AC88)); + ASSERT_TRUE(IRac::isProtocolSupported(decode_type_t::SANYO_AC88)); ASSERT_EQ(kSanyoAc88Bits, IRsend::defaultBits(decode_type_t::SANYO_AC88)); ASSERT_EQ(kSanyoAc88MinRepeat, IRsend::minRepeats(decode_type_t::SANYO_AC88)); } @@ -595,7 +595,8 @@ TEST(TestDecodeSanyoAc88, DecodeRealExamples) { EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits); EXPECT_FALSE(irsend.capture.repeat); EXPECT_EQ( - "", + "Power: On, Mode: 2 (Cool), Temp: 24C, Fan: 0 (Auto), Swing(V): Off, " + "Turbo: Off, Sleep: Off", IRAcUtils::resultAcToString(&irsend.capture)); } @@ -615,7 +616,8 @@ TEST(TestDecodeSanyoAc88, SyntheticSelfDecode) { EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits); EXPECT_FALSE(irsend.capture.repeat); EXPECT_EQ( - "", + "Power: On, Mode: 2 (Cool), Temp: 24C, Fan: 0 (Auto), Swing(V): Off, " + "Turbo: Off, Sleep: Off", IRAcUtils::resultAcToString(&irsend.capture)); EXPECT_EQ( "f38000d50" @@ -660,3 +662,145 @@ TEST(TestDecodeSanyoAc88, SyntheticSelfDecode) { "m500s103675", irsend.outputStr()); } + +// Tests for IRSanyoAc88 class. + +TEST(TestSanyoAc88Class, Power) { + IRSanyoAc88 ac(kGpioUnused); + ac.begin(); + + ac.on(); + EXPECT_TRUE(ac.getPower()); + + ac.off(); + EXPECT_FALSE(ac.getPower()); + + ac.setPower(true); + EXPECT_TRUE(ac.getPower()); + + ac.setPower(false); + EXPECT_FALSE(ac.getPower()); +} + +TEST(TestSanyoAc88Class, FanSpeed) { + IRSanyoAc88 ac(kGpioUnused); + ac.begin(); + + ac.setFan(kSanyoAc88FanAuto); + EXPECT_EQ(kSanyoAc88FanAuto, ac.getFan()); + + ac.setFan(kSanyoAc88FanHigh); + EXPECT_EQ(kSanyoAc88FanHigh, ac.getFan()); + + ac.setFan(kSanyoAc88FanLow); + EXPECT_EQ(kSanyoAc88FanLow, ac.getFan()); + + ac.setFan(kSanyoAc88FanMedium); + EXPECT_EQ(kSanyoAc88FanMedium, ac.getFan()); +} + +TEST(TestSanyoAc88Class, Sleep) { + IRSanyoAc88 ac(kGpioUnused); + ac.begin(); + + ac.setSleep(true); + EXPECT_TRUE(ac.getSleep()); + ac.setSleep(false); + EXPECT_FALSE(ac.getSleep()); + ac.setSleep(true); + EXPECT_TRUE(ac.getSleep()); +} + +TEST(TestSanyoAc88Class, SwingV) { + IRSanyoAc88 ac(kGpioUnused); + ac.begin(); + + ac.setSwingV(true); + EXPECT_TRUE(ac.getSwingV()); + ac.setSwingV(false); + EXPECT_FALSE(ac.getSwingV()); + ac.setSwingV(true); + EXPECT_TRUE(ac.getSwingV()); +} + +TEST(TestSanyoAc88Class, Filter) { + IRSanyoAc88 ac(kGpioUnused); + ac.begin(); + + ac.setFilter(true); + EXPECT_TRUE(ac.getFilter()); + ac.setFilter(false); + EXPECT_FALSE(ac.getFilter()); + ac.setFilter(true); + EXPECT_TRUE(ac.getFilter()); +} + +TEST(TestSanyoAc88Class, Turbo) { + IRSanyoAc88 ac(kGpioUnused); + ac.begin(); + + ac.setTurbo(true); + EXPECT_TRUE(ac.getTurbo()); + ac.setTurbo(false); + EXPECT_FALSE(ac.getTurbo()); + ac.setTurbo(true); + EXPECT_TRUE(ac.getTurbo()); +} + +TEST(TestSanyoAc88Class, Temperature) { + IRSanyoAc88 ac(kGpioUnused); + ac.begin(); + + ac.setTemp(0); + EXPECT_EQ(kSanyoAc88TempMin, ac.getTemp()); + + ac.setTemp(255); + EXPECT_EQ(kSanyoAc88TempMax, ac.getTemp()); + + ac.setTemp(kSanyoAc88TempMin); + EXPECT_EQ(kSanyoAc88TempMin, ac.getTemp()); + + ac.setTemp(kSanyoAc88TempMax); + EXPECT_EQ(kSanyoAc88TempMax, ac.getTemp()); + + ac.setTemp(kSanyoAc88TempMin - 1); + EXPECT_EQ(kSanyoAc88TempMin, ac.getTemp()); + + ac.setTemp(kSanyoAc88TempMax + 1); + EXPECT_EQ(kSanyoAc88TempMax, ac.getTemp()); + + ac.setTemp(17); + EXPECT_EQ(17, ac.getTemp()); + + ac.setTemp(21); + EXPECT_EQ(21, ac.getTemp()); + + ac.setTemp(25); + EXPECT_EQ(25, ac.getTemp()); +} + +TEST(TestSanyoAc88Class, OperatingMode) { + IRSanyoAc88 ac(kGpioUnused); + ac.begin(); + + ac.setMode(kSanyoAc88Auto); + EXPECT_EQ(kSanyoAc88Auto, ac.getMode()); + + ac.setMode(kSanyoAc88Cool); + EXPECT_EQ(kSanyoAc88Cool, ac.getMode()); + + ac.setMode(kSanyoAc88Heat); + EXPECT_EQ(kSanyoAc88Heat, ac.getMode()); + + ac.setMode(kSanyoAc88Fan); + EXPECT_EQ(kSanyoAc88Fan, ac.getMode()); + + ac.setMode(kSanyoAc88Fan + 1); + EXPECT_EQ(kSanyoAc88Auto, ac.getMode()); + + ac.setMode(0); + EXPECT_EQ(kSanyoAc88Auto, ac.getMode()); + + ac.setMode(255); + EXPECT_EQ(kSanyoAc88Auto, ac.getMode()); +} From a1a38dfe01735bb620285c638dda13ac2a65e141 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Tue, 10 Aug 2021 21:45:01 +1000 Subject: [PATCH 3/5] Sanyo88: Support the clock setting. * Add `set/getClock()`. * Add & update unit tests. For #1503 --- src/IRac.cpp | 8 +++++--- src/IRac.h | 3 ++- src/ir_Sanyo.cpp | 25 ++++++++++++++++++++----- src/ir_Sanyo.h | 13 +++++++++---- test/IRac_test.cpp | 5 +++-- test/ir_Sanyo_test.cpp | 20 ++++++++++++++++++-- 6 files changed, 57 insertions(+), 17 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index 7a37ed2eb..98356e95b 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -1818,11 +1818,13 @@ void IRac::sanyo(IRSanyoAc *ac, /// @param[in] turbo Run the device in turbo/powerful mode. /// @param[in] filter Turn on the (ion/pollen/etc) filter mode. /// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +/// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. void IRac::sanyo88(IRSanyoAc88 *ac, const bool on, const stdAc::opmode_t mode, const float degrees, const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, const bool turbo, - const bool filter, const int16_t sleep) { + const bool filter, const int16_t sleep, + const int16_t clock) { ac->begin(); ac->setPower(on); ac->setMode(ac->convertMode(mode)); @@ -1838,7 +1840,7 @@ void IRac::sanyo88(IRSanyoAc88 *ac, // No Clean setting available. // No Beep setting available. ac->setSleep(sleep >= 0); // Sleep is either on/off, so convert to boolean. - // No Clock setting available. + if (clock >= 0) ac->setClock(clock); ac->send(); } #endif // SEND_SANYO_AC88 @@ -2842,7 +2844,7 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { { IRSanyoAc88 ac(_pin, _inverted, _modulation); sanyo88(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, - send.turbo, send.filter, send.sleep); + send.turbo, send.filter, send.sleep, send.clock); break; } #endif // SEND_SANYO_AC88 diff --git a/src/IRac.h b/src/IRac.h index db9475dd1..f7c3c6dc1 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -398,7 +398,8 @@ void electra(IRElectraAc *ac, const bool on, const stdAc::opmode_t mode, const float degrees, const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, const bool turbo, - const bool filter, const int16_t sleep = -1); + const bool filter, + const int16_t sleep = -1, const int16_t clock = -1); #endif // SEND_SANYO_AC88 #if SEND_SHARP_AC void sharp(IRSharpAc *ac, const sharp_ac_remote_model_t model, diff --git a/src/ir_Sanyo.cpp b/src/ir_Sanyo.cpp index e7cfbdfed..7dbed5a58 100644 --- a/src/ir_Sanyo.cpp +++ b/src/ir_Sanyo.cpp @@ -315,8 +315,7 @@ IRSanyoAc::IRSanyoAc(const uint16_t pin, const bool inverted, const bool use_modulation) : _irsend(pin, inverted, use_modulation) { stateReset(); } -/// Reset the state of the remote to a known good state/sequence. -/// @see https://docs.google.com/spreadsheets/d/1dYfLsnYvpjV-SgO8pdinpfuBIpSzm8Q1R5SabrLeskw/edit?ts=5f0190a5#gid=1050142776&range=A2:B2 +/// Reset the state of the remote to a known state/sequence. void IRSanyoAc::stateReset(void) { static const uint8_t kReset[kSanyoAcStateLength] = { 0x6A, 0x6D, 0x51, 0x00, 0x10, 0x45, 0x00, 0x00, 0x33}; @@ -748,7 +747,7 @@ IRSanyoAc88::IRSanyoAc88(const uint16_t pin, const bool inverted, /// @see https://docs.google.com/spreadsheets/d/1dYfLsnYvpjV-SgO8pdinpfuBIpSzm8Q1R5SabrLeskw/edit?ts=5f0190a5#gid=1050142776&range=A2:B2 void IRSanyoAc88::stateReset(void) { static const uint8_t kReset[kSanyoAc88StateLength] = { - 0xAA, 0x55, 0xA0, 0x16, 0x0F, 0x21, 0x01, 0x01, 0x00, 0x00, 0x10}; + 0xAA, 0x55, 0xA0, 0x16, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x10}; std::memcpy(_.raw, kReset, kSanyoAc88StateLength); } @@ -874,6 +873,21 @@ uint8_t IRSanyoAc88::convertFan(const stdAc::fanspeed_t speed) { } } +/// Get the current clock time. +/// @return The time as the nr. of minutes past midnight. +uint16_t IRSanyoAc88::getClock(void) const { + return _.ClockHrs * 60 + _.ClockMins; +} + +/// Set the current clock time. +/// @param[in] mins_since_midnight The time as nr. of minutes past midnight. +void IRSanyoAc88::setClock(const uint16_t mins_since_midnight) { + uint16_t mins = std::min(mins_since_midnight, (uint16_t)(23 * 60 + 59)); + _.ClockMins = mins % 60; + _.ClockHrs = mins / 60; + _.ClockSecs = 0; +} + /// Convert a native fan speed into its stdAc equivalent. /// @param[in] spd The native setting to be converted. /// @return The stdAc equivalent of the native setting. @@ -933,6 +947,7 @@ stdAc::state_t IRSanyoAc88::toCommon(void) const { result.filter = _.Filter; result.turbo = _.Turbo; result.sleep = _.Sleep ? 0 : -1; + result.clock = getClock(); // Not supported. result.swingh = stdAc::swingh_t::kOff; result.econo = false; @@ -940,7 +955,6 @@ stdAc::state_t IRSanyoAc88::toCommon(void) const { result.quiet = false; result.beep = false; result.clean = false; - result.clock = -1; return result; } @@ -948,7 +962,7 @@ stdAc::state_t IRSanyoAc88::toCommon(void) const { /// @return A human readable string. String IRSanyoAc88::toString(void) const { String result = ""; - result.reserve(100); + result.reserve(115); result += addBoolToString(getPower(), kPowerStr, false); result += addModeToString(_.Mode, kSanyoAc88Auto, kSanyoAc88Cool, kSanyoAc88Heat, kSanyoAc88Auto, kSanyoAc88Fan); @@ -959,5 +973,6 @@ String IRSanyoAc88::toString(void) const { result += addBoolToString(_.SwingV, kSwingVStr); result += addBoolToString(_.Turbo, kTurboStr); result += addBoolToString(_.Sleep, kSleepStr); + result += addLabeledString(minsToString(getClock()), kClockStr); return result; } diff --git a/src/ir_Sanyo.h b/src/ir_Sanyo.h index acde7f2b3..66376328f 100644 --- a/src/ir_Sanyo.h +++ b/src/ir_Sanyo.h @@ -201,10 +201,13 @@ union SanyoAc88Protocol{ uint8_t Filter :1; uint8_t SwingV :1; uint8_t :1; - // Byte 4-9 (Timer times?) - uint8_t :8; - uint8_t :8; - uint8_t :8; + // Byte 4 + uint8_t ClockSecs :8; // Nr. of Seconds + // Byte 5 + uint8_t ClockMins :8; // Nr. of Minutes + // Byte 6 + uint8_t ClockHrs :8; // Nr. of Hours + // Byte 7-9 (Timer times?) uint8_t :8; uint8_t :8; uint8_t :8; @@ -252,6 +255,8 @@ class IRSanyoAc88 { bool getFilter(void) const; void setSwingV(const bool on); bool getSwingV(void) const; + uint16_t getClock(void) const; + void setClock(const uint16_t mins_since_midnight); void setRaw(const uint8_t newState[]); uint8_t* getRaw(void); static uint8_t convertMode(const stdAc::opmode_t mode); diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index f7bf48981..5f07dd75c 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -1554,7 +1554,7 @@ TEST(TestIRac, Sanyo88) { IRrecv capture(kGpioUnused); const char expected[] = "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 2 (Medium), Swing(V): On, " - "Turbo: On, Sleep: On"; + "Turbo: On, Sleep: On, Clock: 11:40"; ac.begin(); irac.sanyo88(&ac, @@ -1565,7 +1565,8 @@ TEST(TestIRac, Sanyo88) { stdAc::swingv_t::kAuto, // Vertical Swing true, // Turbo true, // Filter - 17); // Sleep + 17, // Sleep + 11 * 60 + 40); // Clock ASSERT_EQ(expected, ac.toString()); ac._irsend.makeDecodeResult(); EXPECT_TRUE(capture.decode(&ac._irsend.capture)); diff --git a/test/ir_Sanyo_test.cpp b/test/ir_Sanyo_test.cpp index 7071309a2..6378e7679 100644 --- a/test/ir_Sanyo_test.cpp +++ b/test/ir_Sanyo_test.cpp @@ -596,7 +596,7 @@ TEST(TestDecodeSanyoAc88, DecodeRealExamples) { EXPECT_FALSE(irsend.capture.repeat); EXPECT_EQ( "Power: On, Mode: 2 (Cool), Temp: 24C, Fan: 0 (Auto), Swing(V): Off, " - "Turbo: Off, Sleep: Off", + "Turbo: Off, Sleep: Off, Clock: 18:42", IRAcUtils::resultAcToString(&irsend.capture)); } @@ -617,7 +617,7 @@ TEST(TestDecodeSanyoAc88, SyntheticSelfDecode) { EXPECT_FALSE(irsend.capture.repeat); EXPECT_EQ( "Power: On, Mode: 2 (Cool), Temp: 24C, Fan: 0 (Auto), Swing(V): Off, " - "Turbo: Off, Sleep: Off", + "Turbo: Off, Sleep: Off, Clock: 18:42", IRAcUtils::resultAcToString(&irsend.capture)); EXPECT_EQ( "f38000d50" @@ -804,3 +804,19 @@ TEST(TestSanyoAc88Class, OperatingMode) { ac.setMode(255); EXPECT_EQ(kSanyoAc88Auto, ac.getMode()); } + +TEST(TestSanyoAc88Class, Clock) { + IRSanyoAc88 ac(kGpioUnused); + ac.begin(); + + EXPECT_EQ(0, ac.getClock()); + + ac.setClock(21 * 60 + 19); + EXPECT_EQ(21 * 60 + 19, ac.getClock()); + + ac.setClock(0); + EXPECT_EQ(0, ac.getClock()); + + ac.setClock(25 * 60 + 61); + EXPECT_EQ(23 * 60 + 59, ac.getClock()); +} From 7f38c181820e003a253b6343d3b8bc64aca9ab29 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Mon, 16 Aug 2021 22:16:34 +1000 Subject: [PATCH 4/5] Attempt to address lint error/issue. Attempt to address: ``` src/IRac.cpp:2965: Small and focused functions are preferred: IRac::sendAc() has 509 non-comment lines (error triggered by exceeding 500 lines). [readability/fn_size] [1] ``` I can't think of an easy/clean way to do this, hence avoiding the Linter error. --- src/IRac.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index 98356e95b..d8368d95e 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -2463,7 +2463,7 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { : stdAc::swingv_t::kOff; #endif // SEND_LG // Per vendor settings & setup. - switch (send.protocol) { + switch (send.protocol) { // NOLINT(readability/fn_size) #if SEND_AIRWELL case AIRWELL: { From 65369afd19e1c91ee463032dbceec4b3e0cacbe4 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Mon, 16 Aug 2021 22:34:19 +1000 Subject: [PATCH 5/5] Attempt #2 --- src/IRac.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index d8368d95e..8d3a0ecaf 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -2463,7 +2463,7 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { : stdAc::swingv_t::kOff; #endif // SEND_LG // Per vendor settings & setup. - switch (send.protocol) { // NOLINT(readability/fn_size) + switch (send.protocol) { #if SEND_AIRWELL case AIRWELL: { @@ -2962,7 +2962,7 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { return false; // Fail, didn't match anything. } return true; // Success. -} +} // NOLINT(readability/fn_size) /// Update the previous state to the current one. void IRac::markAsSent(void) {