From 1d1981feed1c589c8ba82ade6e73dd43b273b29b Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Tue, 17 May 2022 15:41:39 +1000 Subject: [PATCH 1/5] TOTO: An experimental (s)wipe at support for Toto Toilets. * Add `sendToto()` and `decodeToto()` routines. - Note: They only support the short/simple messages at present. - Codes produced from this may change. This is NOT final in any way. * Update supported devices. * Add unit tests to cover new changes. This is primarily hobbled together to collect data to see if we can reduce the size of this protocol (in bits). It won't decode the different "second" code in the longer messages yet. For #1806 --- src/IRrecv.cpp | 4 ++ src/IRrecv.h | 5 ++ src/IRremoteESP8266.h | 12 ++++- src/IRsend.cpp | 8 ++++ src/IRsend.h | 4 ++ src/IRtext.cpp | 1 + src/ir_Toto.cpp | 84 +++++++++++++++++++++++++++++++++ src/locale/defaults.h | 3 ++ test/ir_Toto_test.cpp | 106 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 src/ir_Toto.cpp create mode 100644 test/ir_Toto_test.cpp diff --git a/src/IRrecv.cpp b/src/IRrecv.cpp index eb8857475..f3590538b 100644 --- a/src/IRrecv.cpp +++ b/src/IRrecv.cpp @@ -1114,6 +1114,10 @@ bool IRrecv::decode(decode_results *results, irparams_t *save, DPRINTLN("Attempting Carrier AC 128-bit decode"); if (decodeCarrierAC128(results, offset)) return true; #endif // DECODE_CARRIER_AC128 +#if DECODE_TOTO + DPRINTLN("Attempting Toto 39-bit decode"); + if (decodeToto(results, offset)) return true; +#endif // DECODE_TOTO // Typically new protocols are added above this line. } #if DECODE_HASH diff --git a/src/IRrecv.h b/src/IRrecv.h index 13fd02b24..b22d488fd 100644 --- a/src/IRrecv.h +++ b/src/IRrecv.h @@ -822,6 +822,11 @@ class IRrecv { const uint16_t nbits = kAirtonBits, const bool strict = true); #endif // DECODE_AIRTON +#if DECODE_TOTO + bool decodeToto(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kTotoBits, + const bool strict = true); +#endif // DECODE_TOTO }; #endif // IRRECV_H_ diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h index ce89fdb33..0e431c3fe 100644 --- a/src/IRremoteESP8266.h +++ b/src/IRremoteESP8266.h @@ -882,6 +882,13 @@ #define SEND_HAIER_AC160 _IR_ENABLE_DEFAULT_ #endif // SEND_HAIER_AC160 +#ifndef DECODE_TOTO +#define DECODE_TOTO _IR_ENABLE_DEFAULT_ +#endif // DECODE_TOTO +#ifndef SEND_TOTO +#define SEND_TOTO _IR_ENABLE_DEFAULT_ +#endif // SEND_TOTO + #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 || \ @@ -1055,8 +1062,9 @@ enum decode_type_t { DAIKIN200, HAIER_AC160, // 115 CARRIER_AC128, + TOTO, // Add new entries before this one, and update it to point to the last entry. - kLastDecodeType = CARRIER_AC128, + kLastDecodeType = TOTO, }; // Message lengths & required repeat values @@ -1298,6 +1306,8 @@ const uint16_t kToshibaACStateLengthShort = kToshibaACStateLength - 2; const uint16_t kToshibaACBitsShort = kToshibaACStateLengthShort * 8; const uint16_t kToshibaACStateLengthLong = kToshibaACStateLength + 1; const uint16_t kToshibaACBitsLong = kToshibaACStateLengthLong * 8; +const uint16_t kTotoBits = 39; +const uint16_t kTotoDefaultRepeat = kSingleRepeat; const uint16_t kTranscoldBits = 24; const uint16_t kTranscoldDefaultRepeat = kNoRepeat; const uint16_t kTrotecStateLength = 9; diff --git a/src/IRsend.cpp b/src/IRsend.cpp index f4647728a..91aba5a97 100644 --- a/src/IRsend.cpp +++ b/src/IRsend.cpp @@ -571,6 +571,7 @@ uint16_t IRsend::minRepeats(const decode_type_t protocol) { case MULTIBRACKETS: case SHERWOOD: case TOSHIBA_AC: + case TOTO: return kSingleRepeat; // Special case AIRWELL: @@ -656,6 +657,8 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) { return 35; case SAMSUNG36: return 36; + case TOTO: + return 39; case CARRIER_AC40: return kCarrierAc40Bits; // 40 case DOSHISHA: @@ -1072,6 +1075,11 @@ bool IRsend::send(const decode_type_t type, const uint64_t data, sendTeco(data, nbits, min_repeat); break; #endif // SEND_TECO +#if SEND_TOTO + case TOTO: + sendToto(data, nbits, min_repeat); + break; +#endif // SEND_TOTO #if SEND_TRANSCOLD case TRANSCOLD: sendTranscold(data, nbits, min_repeat); diff --git a/src/IRsend.h b/src/IRsend.h index df9dafa49..ccc07fe23 100644 --- a/src/IRsend.h +++ b/src/IRsend.h @@ -806,6 +806,10 @@ class IRsend { void sendAirton(const uint64_t data, const uint16_t nbits = kAirtonBits, const uint16_t repeat = kAirtonDefaultRepeat); #endif // SEND_AIRTON +#if SEND_TOTO + void sendToto(const uint64_t data, const uint16_t nbits = kTotoBits, + const uint16_t repeat = kTotoDefaultRepeat); +#endif // SEND_TOTO protected: #ifdef UNIT_TEST diff --git a/src/IRtext.cpp b/src/IRtext.cpp index 959620b8c..0f681922b 100644 --- a/src/IRtext.cpp +++ b/src/IRtext.cpp @@ -399,6 +399,7 @@ IRTEXT_CONST_BLOB_DECL(kAllProtocolNamesStr) { D_STR_DAIKIN200 "\x0" D_STR_HAIER_AC160 "\x0" D_STR_CARRIER_AC128 "\x0" + D_STR_TOTO "\x0" ///< New protocol strings should be added just above this line. "\x0" ///< This string requires double null termination. }; diff --git a/src/ir_Toto.cpp b/src/ir_Toto.cpp new file mode 100644 index 000000000..9f24d8e6b --- /dev/null +++ b/src/ir_Toto.cpp @@ -0,0 +1,84 @@ +// Copyright 2022 David Conran (crankyoldgit) +/// @file +/// @brief Support for the Toto Toilet IR protocols. +/// @see https://www.d-resi.jp/dt/nishi-shinjuku/limited/imgs/pdf/book6.pdf +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1806 + +// Supports: +// Brand: Toto, Model: Washlet Toilet NJ + +#include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +// Constants +const uint16_t kTotoHdrMark = 6197; +const uint16_t kTotoHdrSpace = 2754; +const uint16_t kTotoBitMark = 600; +const uint16_t kTotoOneSpace = 1634; +const uint16_t kTotoZeroSpace = 516; +const uint16_t kTotoGap = 38000; +const uint16_t kTotoSpecialGap = 42482; + +#if SEND_TOTO +/// Send a Toto Toilet formatted message. +/// Status: ALPHA / 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. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1806 +void IRsend::sendToto(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + sendGeneric(kTotoHdrMark, kTotoHdrSpace, + kTotoBitMark, kTotoOneSpace, + kTotoBitMark, kTotoZeroSpace, + kTotoBitMark, kTotoGap, + data, nbits, 38, true, repeat, kDutyDefault); +} +#endif // SEND_TOTO + +#if DECODE_TOTO +/// Decode the supplied Toto Toilet message. +/// Status: ALPHA / Untested. +/// @param[in,out] results Ptr to the data to decode & where to store the 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 True if it can decode it, false if it can't. +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1806 +bool IRrecv::decodeToto(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kTotoBits) + return false; // We expect Toto to be a certain sized message. + + if (results->rawlen < (2 * nbits + kHeader + kFooter) * + (kTotoDefaultRepeat + 1) - 1 + offset) + return false; // We don't have enough entries to possibly match. + + + uint64_t data = 0; + uint16_t used = 0; + + for (uint16_t r = 0; r <= kTotoDefaultRepeat; r++) { // We expect a repeat. + // Match Header + Data + Footer + Gap + used = matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kTotoHdrMark, kTotoHdrSpace, + kTotoBitMark, kTotoOneSpace, + kTotoBitMark, kTotoZeroSpace, + kTotoBitMark, kTotoGap, true); + if (!used) return false; // Didn't match, so fail. + offset += used; + if (r && data != results->value) return false; // The repeat didn't match. + results->value = data; + } + // Success + results->bits = nbits; + results->decode_type = decode_type_t::TOTO; + results->command = 0; + results->address = 0; + return true; +} +#endif // DECODE_TOTO diff --git a/src/locale/defaults.h b/src/locale/defaults.h index b70179e2b..700673707 100644 --- a/src/locale/defaults.h +++ b/src/locale/defaults.h @@ -1009,6 +1009,9 @@ D_STR_INDIRECT " " D_STR_MODE #ifndef D_STR_TOSHIBA_AC #define D_STR_TOSHIBA_AC "TOSHIBA_AC" #endif // D_STR_TOSHIBA_AC +#ifndef D_STR_TOTO +#define D_STR_TOTO "TOTO" +#endif // D_STR_TOTO #ifndef D_STR_TRANSCOLD #define D_STR_TRANSCOLD "TRANSCOLD" #endif // D_STR_TRANSCOLD diff --git a/test/ir_Toto_test.cpp b/test/ir_Toto_test.cpp new file mode 100644 index 000000000..c04dbc143 --- /dev/null +++ b/test/ir_Toto_test.cpp @@ -0,0 +1,106 @@ +// Copyright 2022 crankyoldgit (David Conran) + +#include "IRac.h" +#include "IRsend.h" +#include "IRsend_test.h" +#include "IRutils.h" +#include "gtest/gtest.h" + + +// General housekeeping +TEST(TestToto, Housekeeping) { + ASSERT_EQ("TOTO", typeToString(TOTO)); + ASSERT_FALSE(hasACState(TOTO)); + ASSERT_EQ(kTotoBits, IRsend::defaultBits(decode_type_t::TOTO)); + ASSERT_EQ(kSingleRepeat, IRsend::minRepeats(decode_type_t::TOTO)); +} + +/* +// Tests for sendToto(). +// Test sending typical data only. +TEST(TestSendToto, SendDataOnly) { + IRsendTest irsend(kGpioUnused); + irsend.begin(); + + irsend.reset(); + irsend.sendToto(0x5C32CD); // Small flush. + EXPECT_EQ( + "", + irsend.outputStr()); + + irsend.reset(); +} + +// Test sending with different repeats. +TEST(TestSendToto, SendWithRepeats) { + IRsendTest irsend(kGpioUnused); + irsend.begin(); + + irsend.reset(); + irsend.sendToto(0x5C32CD, kTotoBits, 0); // 0 repeats. + EXPECT_EQ( + "", + irsend.outputStr()); + irsend.sendToto(0x5C32CD, kTotoBits, 2); // 2 repeats. + EXPECT_EQ( + "", + irsend.outputStr()); +} + +*/ +// Tests for decodeToto(). + +// Decode normal Toto messages. +TEST(TestDecodeToto, SyntheticDecode) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + irsend.begin(); + + // Normal Toto 39-bit message. + irsend.reset(); + irsend.sendToto(0x200800B0B0); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(TOTO, irsend.capture.decode_type); + EXPECT_EQ(kTotoBits, irsend.capture.bits); + EXPECT_EQ(0x200800B0B0, irsend.capture.value); + EXPECT_EQ(0, irsend.capture.address); + EXPECT_EQ(0, irsend.capture.command); +} + +// Decode real example via Issue #1806 +TEST(TestDecodeToto, RealDecode) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + irsend.begin(); + + irsend.reset(); + // Toto Full Flush from Issue #1806 + const uint16_t rawData[163] = { + 6266, 2734, + 598, 540, 598, 1626, 598, 512, 622, 516, 598, 514, 598, 510, 598, 514, + 628, 512, 596, 514, 600, 512, 598, 538, 600, 1622, 600, 512, 598, 540, + 602, 510, 598, 512, 598, 512, 624, 514, 598, 512, 598, 512, 598, 514, + 624, 512, 598, 514, 598, 1652, 596, 514, 598, 1626, 598, 1650, 598, 514, + 598, 512, 598, 540, 598, 514, 596, 1626, 626, 512, 574, 1648, 598, 1650, + 598, 514, 598, 512, 594, 544, 596, 514, + 598, 37996, + 6182, 2764, + 598, 514, 600, 1648, 600, 512, 596, 514, 598, 540, 598, 512, 600, 512, + 598, 512, 624, 514, 598, 514, 598, 512, 596, 1652, 598, 514, 598, 512, + 596, 540, 598, 514, 598, 512, 598, 512, 598, 540, 596, 516, 596, 514, 598, + 512, 574, 564, 598, 1626, 568, 542, 624, 1624, 626, 1622, 598, 514, 596, + 514, 598, 514, 596, 540, 600, 1622, 598, 512, 600, 1650, 598, 1624, 596, + 540, 600, 512, 598, 514, 596, 514, + 622}; // UNKNOWN 43BD67B3 + + irsend.sendRaw(rawData, 163, 38); + irsend.makeDecodeResult(); + + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(TOTO, irsend.capture.decode_type); + EXPECT_EQ(kTotoBits, irsend.capture.bits); + EXPECT_EQ(0x200800B0B0, irsend.capture.value); + EXPECT_EQ(0, irsend.capture.address); + EXPECT_EQ(0, irsend.capture.command); +} From 8be4c8dce8c65fd02114bd13ed1c0246a4676f8d Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Tue, 17 May 2022 16:17:32 +1000 Subject: [PATCH 2/5] Hopefully fix linter issue. --- src/IRrecv.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IRrecv.cpp b/src/IRrecv.cpp index f3590538b..24e9ca871 100644 --- a/src/IRrecv.cpp +++ b/src/IRrecv.cpp @@ -1132,7 +1132,7 @@ bool IRrecv::decode(decode_results *results, irparams_t *save, if (!resumed) // Check if we have already resumed. resume(); return false; -} +} // NOLINT(readability/fn_size) /// Convert the tolerance percentage into something valid. /// @param[in] percentage An integer percentage. From a77b6c196a87ea769f361a1b5e97c696b56d6c12 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Fri, 20 May 2022 00:43:16 +1000 Subject: [PATCH 3/5] Work in Progress: Toto improvements. * Shrink messages down to 24 bits, by expecting a 15 bit prefix. (0x2008) * Experimental support for sending longer codes. - e.g. `sendToto(0x006060001010, kTotoLongBits); // Oscillate Bidet` * Refactor sending to include prefix. * Refactor decoding to expect a prefix. * Prep work for decoding Long messages (not ready yet) For #1806 --- src/IRremoteESP8266.h | 4 ++- src/IRsend.cpp | 3 +- src/ir_Toto.cpp | 82 +++++++++++++++++++++++++++++-------------- test/ir_Toto_test.cpp | 41 +++------------------- 4 files changed, 64 insertions(+), 66 deletions(-) diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h index 0e431c3fe..9afe5ee6f 100644 --- a/src/IRremoteESP8266.h +++ b/src/IRremoteESP8266.h @@ -1306,7 +1306,9 @@ const uint16_t kToshibaACStateLengthShort = kToshibaACStateLength - 2; const uint16_t kToshibaACBitsShort = kToshibaACStateLengthShort * 8; const uint16_t kToshibaACStateLengthLong = kToshibaACStateLength + 1; const uint16_t kToshibaACBitsLong = kToshibaACStateLengthLong * 8; -const uint16_t kTotoBits = 39; +const uint16_t kTotoBits = 24; +const uint16_t kTotoShortBits = kTotoBits; +const uint16_t kTotoLongBits = kTotoShortBits * 2; const uint16_t kTotoDefaultRepeat = kSingleRepeat; const uint16_t kTranscoldBits = 24; const uint16_t kTranscoldDefaultRepeat = kNoRepeat; diff --git a/src/IRsend.cpp b/src/IRsend.cpp index 91aba5a97..84983e7f2 100644 --- a/src/IRsend.cpp +++ b/src/IRsend.cpp @@ -634,6 +634,7 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) { case MIDEA24: case NIKAI: case RCMM: + case TOTO: case TRANSCOLD: return 24; case LG: @@ -657,8 +658,6 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) { return 35; case SAMSUNG36: return 36; - case TOTO: - return 39; case CARRIER_AC40: return kCarrierAc40Bits; // 40 case DOSHISHA: diff --git a/src/ir_Toto.cpp b/src/ir_Toto.cpp index 9f24d8e6b..b4b861bbe 100644 --- a/src/ir_Toto.cpp +++ b/src/ir_Toto.cpp @@ -20,21 +20,30 @@ const uint16_t kTotoOneSpace = 1634; const uint16_t kTotoZeroSpace = 516; const uint16_t kTotoGap = 38000; const uint16_t kTotoSpecialGap = 42482; +const uint64_t kTotoPrefix = 0x2008; +const uint16_t kTotoPrefixBits = 15; #if SEND_TOTO /// Send a Toto Toilet formatted message. -/// Status: ALPHA / Untested. +/// Status: BETA / Seems to work. /// @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. /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1806 void IRsend::sendToto(const uint64_t data, const uint16_t nbits, const uint16_t repeat) { - sendGeneric(kTotoHdrMark, kTotoHdrSpace, - kTotoBitMark, kTotoOneSpace, - kTotoBitMark, kTotoZeroSpace, - kTotoBitMark, kTotoGap, - data, nbits, 38, true, repeat, kDutyDefault); + if (nbits <= kTotoShortBits) { // Short code message. + sendGeneric(kTotoHdrMark, kTotoHdrSpace, + kTotoBitMark, kTotoOneSpace, + kTotoBitMark, kTotoZeroSpace, + kTotoBitMark, kTotoGap, + (kTotoPrefix << nbits) | data, nbits + kTotoPrefixBits, + 38, true, repeat, kDutyDefault); + } else { // Long code message + // This is really two messages sent at least one extra time each. + sendToto(GETBITS64(data, 0, kTotoShortBits), kTotoShortBits, repeat + 1); + sendToto(data >> kTotoShortBits, nbits - kTotoShortBits, repeat + 1); + } } #endif // SEND_TOTO @@ -50,35 +59,56 @@ void IRsend::sendToto(const uint64_t data, const uint16_t nbits, /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1806 bool IRrecv::decodeToto(decode_results *results, uint16_t offset, const uint16_t nbits, const bool strict) { - if (strict && nbits != kTotoBits) - return false; // We expect Toto to be a certain sized message. + if (strict && nbits != kTotoShortBits && nbits != kTotoLongBits) + return false; // We expect Toto to be a certain sized messages. - if (results->rawlen < (2 * nbits + kHeader + kFooter) * - (kTotoDefaultRepeat + 1) - 1 + offset) - return false; // We don't have enough entries to possibly match. + const uint16_t repeats = (nbits > kTotoShortBits) ? kTotoDefaultRepeat + 1 + : kTotoDefaultRepeat; + const uint16_t ksections = (nbits > kTotoShortBits) ? 2 : 1; + if (results->rawlen < (2 * (nbits + kTotoPrefixBits) + kHeader + kFooter) * + (repeats + 1) - 1 + offset) + return false; // We don't have enough entries to possibly match. - uint64_t data = 0; uint16_t used = 0; - for (uint16_t r = 0; r <= kTotoDefaultRepeat; r++) { // We expect a repeat. - // Match Header + Data + Footer + Gap - used = matchGeneric(results->rawbuf + offset, &data, - results->rawlen - offset, nbits, - kTotoHdrMark, kTotoHdrSpace, - kTotoBitMark, kTotoOneSpace, - kTotoBitMark, kTotoZeroSpace, - kTotoBitMark, kTotoGap, true); - if (!used) return false; // Didn't match, so fail. - offset += used; - if (r && data != results->value) return false; // The repeat didn't match. - results->value = data; + // Long messages have two sections, short have only one. + for (uint16_t section = 1; section <= ksections; section++) { + results->value <<= (nbits / ksections); + uint64_t data = 0; + uint64_t repeat_data = 0; + for (uint16_t r = 0; r <= repeats; r++) { // We expect a repeat. + uint64_t prefix = 0; + // Header + Prefix + used = matchGeneric(results->rawbuf + offset, &prefix, + results->rawlen - offset, kTotoPrefixBits, + kTotoHdrMark, kTotoHdrSpace, + kTotoBitMark, kTotoOneSpace, + kTotoBitMark, kTotoZeroSpace, + 0, 0, false); // No Footer + if (!used) return false; // Didn't match, so fail. + offset += used; + if (strict && (prefix != kTotoPrefix)) return false; + // Data + Footer + Gap + used = matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits / ksections, + 0, 0, // No Header + kTotoBitMark, kTotoOneSpace, + kTotoBitMark, kTotoZeroSpace, + kTotoBitMark, kTotoGap, true); + if (!used) return false; // Didn't match, so fail. + offset += used; + + if (r && data != repeat_data) return false; // Repeat didn't match. + repeat_data |= data; + } + results->value |= data; } // Success results->bits = nbits; results->decode_type = decode_type_t::TOTO; - results->command = 0; - results->address = 0; + results->command = GETBITS64(results->value, 0, kTotoBits); + results->address = results->value >> kTotoBits; return true; } #endif // DECODE_TOTO diff --git a/test/ir_Toto_test.cpp b/test/ir_Toto_test.cpp index c04dbc143..93c65182d 100644 --- a/test/ir_Toto_test.cpp +++ b/test/ir_Toto_test.cpp @@ -15,39 +15,6 @@ TEST(TestToto, Housekeeping) { ASSERT_EQ(kSingleRepeat, IRsend::minRepeats(decode_type_t::TOTO)); } -/* -// Tests for sendToto(). -// Test sending typical data only. -TEST(TestSendToto, SendDataOnly) { - IRsendTest irsend(kGpioUnused); - irsend.begin(); - - irsend.reset(); - irsend.sendToto(0x5C32CD); // Small flush. - EXPECT_EQ( - "", - irsend.outputStr()); - - irsend.reset(); -} - -// Test sending with different repeats. -TEST(TestSendToto, SendWithRepeats) { - IRsendTest irsend(kGpioUnused); - irsend.begin(); - - irsend.reset(); - irsend.sendToto(0x5C32CD, kTotoBits, 0); // 0 repeats. - EXPECT_EQ( - "", - irsend.outputStr()); - irsend.sendToto(0x5C32CD, kTotoBits, 2); // 2 repeats. - EXPECT_EQ( - "", - irsend.outputStr()); -} - -*/ // Tests for decodeToto(). // Decode normal Toto messages. @@ -63,9 +30,9 @@ TEST(TestDecodeToto, SyntheticDecode) { ASSERT_TRUE(irrecv.decode(&irsend.capture)); EXPECT_EQ(TOTO, irsend.capture.decode_type); EXPECT_EQ(kTotoBits, irsend.capture.bits); - EXPECT_EQ(0x200800B0B0, irsend.capture.value); + EXPECT_EQ(0xB0B0, irsend.capture.value); EXPECT_EQ(0, irsend.capture.address); - EXPECT_EQ(0, irsend.capture.command); + EXPECT_EQ(0xB0B0, irsend.capture.command); } // Decode real example via Issue #1806 @@ -100,7 +67,7 @@ TEST(TestDecodeToto, RealDecode) { ASSERT_TRUE(irrecv.decode(&irsend.capture)); EXPECT_EQ(TOTO, irsend.capture.decode_type); EXPECT_EQ(kTotoBits, irsend.capture.bits); - EXPECT_EQ(0x200800B0B0, irsend.capture.value); + EXPECT_EQ(0xB0B0, irsend.capture.value); EXPECT_EQ(0, irsend.capture.address); - EXPECT_EQ(0, irsend.capture.command); + EXPECT_EQ(0xB0B0, irsend.capture.command); } From 5a4db023da8b2b16a1db60fe9416359b01a72a0a Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Sun, 22 May 2022 11:18:14 +1000 Subject: [PATCH 4/5] Toto: Bit order swap & finish support for long codes * Change bit order to LSB per https://github.com/crankyoldgit/IRremoteESP8266/issues/1806#issuecomment-1132417205 * Finish adding support for long (48bit) codes. * Update unit tests accordingly. - Add tests for long (48 bit) codes. Fingers crossed, this should do it. Fixes #1806 --- src/IRrecv.cpp | 5 ++- src/ir_Toto.cpp | 45 +++++++++++++------ test/ir_Toto_test.cpp | 102 ++++++++++++++++++++++++++++++++++++++---- 3 files changed, 128 insertions(+), 24 deletions(-) diff --git a/src/IRrecv.cpp b/src/IRrecv.cpp index 24e9ca871..f6e065eaa 100644 --- a/src/IRrecv.cpp +++ b/src/IRrecv.cpp @@ -1115,8 +1115,9 @@ bool IRrecv::decode(decode_results *results, irparams_t *save, if (decodeCarrierAC128(results, offset)) return true; #endif // DECODE_CARRIER_AC128 #if DECODE_TOTO - DPRINTLN("Attempting Toto 39-bit decode"); - if (decodeToto(results, offset)) return true; + DPRINTLN("Attempting Toto 48/24-bit decode"); + if (decodeToto(results, offset, kTotoLongBits) || // Long needs to be first + decodeToto(results, offset, kTotoShortBits)) return true; #endif // DECODE_TOTO // Typically new protocols are added above this line. } diff --git a/src/ir_Toto.cpp b/src/ir_Toto.cpp index b4b861bbe..2997e760e 100644 --- a/src/ir_Toto.cpp +++ b/src/ir_Toto.cpp @@ -20,7 +20,7 @@ const uint16_t kTotoOneSpace = 1634; const uint16_t kTotoZeroSpace = 516; const uint16_t kTotoGap = 38000; const uint16_t kTotoSpecialGap = 42482; -const uint64_t kTotoPrefix = 0x2008; +const uint64_t kTotoPrefix = 0x0802; const uint16_t kTotoPrefixBits = 15; #if SEND_TOTO @@ -37,12 +37,13 @@ void IRsend::sendToto(const uint64_t data, const uint16_t nbits, kTotoBitMark, kTotoOneSpace, kTotoBitMark, kTotoZeroSpace, kTotoBitMark, kTotoGap, - (kTotoPrefix << nbits) | data, nbits + kTotoPrefixBits, - 38, true, repeat, kDutyDefault); - } else { // Long code message + (data << kTotoPrefixBits) | kTotoPrefix, + nbits + kTotoPrefixBits, + 38, false, repeat, kDutyDefault); + } else { // Long code message // This is really two messages sent at least one extra time each. - sendToto(GETBITS64(data, 0, kTotoShortBits), kTotoShortBits, repeat + 1); sendToto(data >> kTotoShortBits, nbits - kTotoShortBits, repeat + 1); + sendToto(GETBITS64(data, 0, kTotoShortBits), kTotoShortBits, repeat + 1); } } #endif // SEND_TOTO @@ -65,6 +66,7 @@ bool IRrecv::decodeToto(decode_results *results, uint16_t offset, const uint16_t repeats = (nbits > kTotoShortBits) ? kTotoDefaultRepeat + 1 : kTotoDefaultRepeat; const uint16_t ksections = (nbits > kTotoShortBits) ? 2 : 1; + const uint16_t ksection_bits = nbits / ksections; if (results->rawlen < (2 * (nbits + kTotoPrefixBits) + kHeader + kFooter) * (repeats + 1) - 1 + offset) @@ -74,7 +76,7 @@ bool IRrecv::decodeToto(decode_results *results, uint16_t offset, // Long messages have two sections, short have only one. for (uint16_t section = 1; section <= ksections; section++) { - results->value <<= (nbits / ksections); + results->value <<= ksection_bits; uint64_t data = 0; uint64_t repeat_data = 0; for (uint16_t r = 0; r <= repeats; r++) { // We expect a repeat. @@ -85,30 +87,45 @@ bool IRrecv::decodeToto(decode_results *results, uint16_t offset, kTotoHdrMark, kTotoHdrSpace, kTotoBitMark, kTotoOneSpace, kTotoBitMark, kTotoZeroSpace, - 0, 0, false); // No Footer + 0, 0, false, // No Footer + kUseDefTol, kMarkExcess, false); if (!used) return false; // Didn't match, so fail. offset += used; if (strict && (prefix != kTotoPrefix)) return false; // Data + Footer + Gap used = matchGeneric(results->rawbuf + offset, &data, - results->rawlen - offset, nbits / ksections, + results->rawlen - offset, ksection_bits, 0, 0, // No Header kTotoBitMark, kTotoOneSpace, kTotoBitMark, kTotoZeroSpace, - kTotoBitMark, kTotoGap, true); + kTotoBitMark, kTotoGap, true, + kUseDefTol, kMarkExcess, false); if (!used) return false; // Didn't match, so fail. offset += used; - - if (r && data != repeat_data) return false; // Repeat didn't match. - repeat_data |= data; + if (strict) { + if (r && data != repeat_data) return false; // Repeat didn't match. + // Integrity check. + uint8_t result = 0; + uint64_t check = data; + uint16_t bits = 0; + // Loop over the data 8 bits at a time. + while (bits < ksection_bits) { + result ^= (check & 0b111111111); // Xor with the last 8 bits. + bits += 8; + check >>= 8; + } + if (result) return false; // Intergrity check failed. + } + repeat_data = data; } results->value |= data; } // Success results->bits = nbits; results->decode_type = decode_type_t::TOTO; - results->command = GETBITS64(results->value, 0, kTotoBits); - results->address = results->value >> kTotoBits; + results->command = GETBITS64(results->value, 0, ksection_bits - 8); + results->address = GETBITS64(results->value, ksection_bits, + ksection_bits - 8); return true; } #endif // DECODE_TOTO diff --git a/test/ir_Toto_test.cpp b/test/ir_Toto_test.cpp index 93c65182d..050bd01e6 100644 --- a/test/ir_Toto_test.cpp +++ b/test/ir_Toto_test.cpp @@ -18,25 +18,25 @@ TEST(TestToto, Housekeeping) { // Tests for decodeToto(). // Decode normal Toto messages. -TEST(TestDecodeToto, SyntheticDecode) { +TEST(TestDecodeToto, SyntheticShortDecode) { IRsendTest irsend(kGpioUnused); IRrecv irrecv(kGpioUnused); irsend.begin(); - // Normal Toto 39-bit message. + // Short Toto 24-bit message. irsend.reset(); - irsend.sendToto(0x200800B0B0); + irsend.sendToto(0x0D0D00); irsend.makeDecodeResult(); ASSERT_TRUE(irrecv.decode(&irsend.capture)); EXPECT_EQ(TOTO, irsend.capture.decode_type); EXPECT_EQ(kTotoBits, irsend.capture.bits); - EXPECT_EQ(0xB0B0, irsend.capture.value); + EXPECT_EQ(0x0D0D00, irsend.capture.value); EXPECT_EQ(0, irsend.capture.address); - EXPECT_EQ(0xB0B0, irsend.capture.command); + EXPECT_EQ(0x0D00, irsend.capture.command); } // Decode real example via Issue #1806 -TEST(TestDecodeToto, RealDecode) { +TEST(TestDecodeToto, RealShortDecode) { IRsendTest irsend(kGpioUnused); IRrecv irrecv(kGpioUnused); irsend.begin(); @@ -67,7 +67,93 @@ TEST(TestDecodeToto, RealDecode) { ASSERT_TRUE(irrecv.decode(&irsend.capture)); EXPECT_EQ(TOTO, irsend.capture.decode_type); EXPECT_EQ(kTotoBits, irsend.capture.bits); - EXPECT_EQ(0xB0B0, irsend.capture.value); + EXPECT_EQ(0x0D0D00, irsend.capture.value); EXPECT_EQ(0, irsend.capture.address); - EXPECT_EQ(0xB0B0, irsend.capture.command); + EXPECT_EQ(0x0D00, irsend.capture.command); +} + +// Decode real example via Issue #1806 +TEST(TestDecodeToto, RealLongDecode) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + irsend.begin(); + + irsend.reset(); + // Oscillate Bidet Function from Issue #1806 + const uint16_t rawData[491] = { + 6262, 2738, + 600, 538, 596, 1624, 598, 512, 652, 486, 628, 484, 600, 510, 596, 516, + 650, 488, 622, 488, 626, 484, 598, 540, 600, 1624, 602, 510, 596, 540, + 602, 510, 598, 514, 598, 512, 626, 512, 598, 512, 598, 512, 598, 514, + 626, 510, 598, 514, 598, 512, 598, 1650, 600, 1622, 600, 538, 596, 514, + 600, 510, 596, 514, 596, 542, 598, 514, 598, 1624, 626, 1622, 598, 512, + 600, 512, 620, 516, 598, 512, 598, 514, + 598, 40244, + 6184, 2764, + 598, 514, 598, 1650, 598, 514, 596, 516, 596, 542, 598, 512, 596, 516, + 600, 510, 624, 512, 598, 512, 596, 516, 598, 1652, 600, 512, 598, 512, + 598, 540, 596, 518, 594, 514, 598, 512, 596, 542, 598, 512, 596, 514, + 598, 512, 598, 540, 598, 514, 598, 1622, 624, 1626, 598, 514, 598, 512, + 626, 512, 596, 514, 596, 514, 596, 540, 598, 1624, 600, 1650, 600, 512, + 600, 510, 598, 512, 596, 540, 598, 512, + 600, 40244, + 6186, 2760, + 600, 512, 596, 1626, 624, 514, 598, 512, 600, 512, 598, 512, 626, 512, + 598, 512, 598, 512, 596, 540, 600, 510, 600, 1622, 600, 538, 596, 514, + 596, 514, 598, 514, 624, 514, 596, 516, 596, 514, 600, 512, 622, 514, 600, + 512, 600, 510, 600, 538, 602, 1622, 600, 1648, 626, 512, 572, 512, 598, + 514, 650, 488, 598, 512, 594, 516, 598, 1652, 598, 1626, 598, 514, 624, + 514, 596, 512, 600, 512, 598, 540, + 596, 40246, + 6184, 2736, + 598, 538, 598, 1624, 598, 512, 598, 540, 598, 512, 600, 512, 596, 514, + 600, 538, 626, 486, 598, 514, 596, 514, 652, 1596, 594, 516, 624, 514, + 594, 516, 598, 514, 622, 490, 596, 540, 596, 514, 624, 488, 594, 514, 598, + 538, 598, 512, 598, 514, 594, 516, 596, 540, 600, 1622, 572, 566, 600, + 512, 570, 542, 598, 512, 598, 540, 600, 512, 594, 516, 598, 1652, 600, + 512, 598, 514, 596, 514, 624, 514, + 598, 42468, + 6182, 2764, + 624, 490, 594, 1652, 600, 512, 596, 514, 596, 540, 600, 512, 598, 516, + 596, 514, 598, 538, 598, 516, 598, 512, 570, 1678, 596, 514, 596, 514, + 598, 512, 624, 512, 594, 516, 596, 514, 600, 510, 626, 512, 596, 516, + 594, 516, 598, 538, 598, 512, 596, 514, 596, 514, 596, 1652, 600, 514, + 594, 516, 622, 514, 600, 512, 598, 514, 596, 514, 624, 514, 596, 1626, + 598, 514, 622, 516, 594, 516, 598, 514, + 596, 42496, + 6184, 2764, + 596, 516, 598, 1624, 626, 512, 598, 512, 596, 516, 600, 512, 624, 514, + 598, 514, 594, 516, 596, 516, 620, 516, 600, 1624, 596, 540, 598, 514, + 600, 512, 596, 516, 596, 542, 594, 516, 598, 514, 594, 516, 624, 512, 598, + 512, 596, 514, 596, 514, 624, 514, 596, 516, 598, 1650, 594, 518, 596, + 514, 596, 516, 596, 542, 596, 514, 596, 516, 596, 514, 596, 1652, 598, + 514, 596, 514, 624, 514, 622, 488, 596}; // UNKNOWN 4AC5E8B3 + + + irsend.sendRaw(rawData, 491, 38); + irsend.makeDecodeResult(); + + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(TOTO, irsend.capture.decode_type); + EXPECT_EQ(kTotoLongBits, irsend.capture.bits); + EXPECT_EQ(0x60600080800, irsend.capture.value); + EXPECT_EQ(0x0600, irsend.capture.address); + EXPECT_EQ(0x0800, irsend.capture.command); +} + +TEST(TestDecodeToto, SyntheticLongDecode) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + irsend.begin(); + + // Long Toto 48-bit message. + irsend.reset(); + irsend.sendToto(0x60600080800, kTotoLongBits); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(TOTO, irsend.capture.decode_type); + EXPECT_EQ(kTotoLongBits, irsend.capture.bits); + EXPECT_EQ(0x60600080800, irsend.capture.value); + EXPECT_EQ(0x0600, irsend.capture.address); + EXPECT_EQ(0x0800, irsend.capture.command); } From f14e57d1bee2a48a1ac0e873a6d6f4510b29d578 Mon Sep 17 00:00:00 2001 From: David Conran Date: Mon, 23 May 2022 00:01:54 +1000 Subject: [PATCH 5/5] Code review cleanup --- src/IRremoteESP8266.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h index 9afe5ee6f..027440764 100644 --- a/src/IRremoteESP8266.h +++ b/src/IRremoteESP8266.h @@ -883,10 +883,10 @@ #endif // SEND_HAIER_AC160 #ifndef DECODE_TOTO -#define DECODE_TOTO _IR_ENABLE_DEFAULT_ +#define DECODE_TOTO _IR_ENABLE_DEFAULT_ #endif // DECODE_TOTO #ifndef SEND_TOTO -#define SEND_TOTO _IR_ENABLE_DEFAULT_ +#define SEND_TOTO _IR_ENABLE_DEFAULT_ #endif // SEND_TOTO #if (DECODE_ARGO || DECODE_DAIKIN || DECODE_FUJITSU_AC || DECODE_GREE || \