From 8009d196e46feec16d8657811514a04ae42d134e Mon Sep 17 00:00:00 2001 From: David Date: Mon, 20 May 2019 11:11:20 +1000 Subject: [PATCH] Support for Lixil Inax Toilet protocol. * Add send/decode routines & unit tests. * Update example code. Fixes #706 --- examples/IRMQTTServer/IRMQTTServer.ino | 9 ++ src/IRrecv.cpp | 4 + src/IRrecv.h | 4 + src/IRremoteESP8266.h | 9 +- src/IRsend.cpp | 5 ++ src/IRsend.h | 4 + src/IRutils.cpp | 5 ++ src/ir_Inax.cpp | 98 ++++++++++++++++++++ test/Makefile | 13 ++- test/ir_Inax_test.cpp | 119 +++++++++++++++++++++++++ tools/Makefile | 5 +- 11 files changed, 271 insertions(+), 4 deletions(-) create mode 100644 src/ir_Inax.cpp create mode 100644 test/ir_Inax_test.cpp diff --git a/examples/IRMQTTServer/IRMQTTServer.ino b/examples/IRMQTTServer/IRMQTTServer.ino index a27c0cec9..3f92292f2 100644 --- a/examples/IRMQTTServer/IRMQTTServer.ino +++ b/examples/IRMQTTServer/IRMQTTServer.ino @@ -591,6 +591,7 @@ void handleRoot(void) { "" "" "" + "" "" "" "" @@ -2571,6 +2572,14 @@ bool sendIRCode(IRsend *irsend, int const ir_type, irsend->sendPanasonic64(code, bits, repeat); break; #endif +#if SEND_INAX + case INAX: // 64 + if (bits == 0) + bits = kInaxBits; + repeat = std::max(repeat, kInaxMinRepeat); + irsend->sendInax(code, bits, repeat); + break; +#endif #if SEND_JVC case JVC: // 6 if (bits == 0) diff --git a/src/IRrecv.cpp b/src/IRrecv.cpp index 6ad4937b0..70748ddff 100644 --- a/src/IRrecv.cpp +++ b/src/IRrecv.cpp @@ -527,6 +527,10 @@ bool IRrecv::decode(decode_results *results, irparams_t *save) { DPRINTLN("Attempting SHARP_AC decode"); if (decodeSharpAc(results)) return true; #endif +#if DECODE_INAX + DPRINTLN("Attempting Inax decode"); + if (decodeInax(results)) return true; +#endif // DECODE_INAX #if DECODE_HASH // decodeHash returns a hash on any input. // Thus, it needs to be last in the list. diff --git a/src/IRrecv.h b/src/IRrecv.h index f6f40ad0d..9b50eb186 100644 --- a/src/IRrecv.h +++ b/src/IRrecv.h @@ -213,6 +213,10 @@ class IRrecv { bool decodeLG(decode_results *results, uint16_t nbits = kLgBits, bool strict = false); #endif +#if DECODE_INAX + bool decodeInax(decode_results *results, const uint16_t nbits = kInaxBits, + const bool strict = true); +#endif // DECODE_INAX #if DECODE_JVC bool decodeJVC(decode_results *results, uint16_t nbits = kJvcBits, bool strict = true); diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h index ce45f74be..80fc974e6 100644 --- a/src/IRremoteESP8266.h +++ b/src/IRremoteESP8266.h @@ -132,6 +132,9 @@ #define DECODE_FUJITSU_AC true #define SEND_FUJITSU_AC true +#define DECODE_INAX true +#define SEND_INAX true + #define DECODE_DAIKIN true #define SEND_DAIKIN true @@ -317,8 +320,10 @@ enum decode_type_t { MITSUBISHI_HEAVY_152, // 60 DAIKIN216, SHARP_AC, + GOODWEATHER, + INAX, // Add new entries before this one, and update it to point to the last entry. - kLastDecodeType = SHARP_AC, + kLastDecodeType = INAX, }; // Message lengths & required repeat values @@ -374,6 +379,8 @@ const uint16_t kHitachiAc1StateLength = 13; const uint16_t kHitachiAc1Bits = kHitachiAc1StateLength * 8; const uint16_t kHitachiAc2StateLength = 53; const uint16_t kHitachiAc2Bits = kHitachiAc2StateLength * 8; +const uint16_t kInaxBits = 24; +const uint16_t kInaxMinRepeat = kSingleRepeat; const uint16_t kJvcBits = 16; const uint16_t kKelvinatorStateLength = 16; const uint16_t kKelvinatorBits = kKelvinatorStateLength * 8; diff --git a/src/IRsend.cpp b/src/IRsend.cpp index 22c0c874b..7421b6047 100644 --- a/src/IRsend.cpp +++ b/src/IRsend.cpp @@ -536,6 +536,11 @@ bool IRsend::send(decode_type_t type, uint64_t data, uint16_t nbits) { sendGree(data, nbits); break; #endif +#if SEND_INAX + case INAX: + sendInax(data, nbits); + break; +#endif // SEND_INAX #if SEND_JVC case JVC: sendJVC(data, nbits); diff --git a/src/IRsend.h b/src/IRsend.h index 53374333e..a36f69ad8 100644 --- a/src/IRsend.h +++ b/src/IRsend.h @@ -275,6 +275,10 @@ class IRsend { void sendFujitsuAC(unsigned char data[], uint16_t nbytes, uint16_t repeat = kFujitsuAcMinRepeat); #endif +#if SEND_INAX + void sendInax(const uint64_t data, const uint16_t nbits = kInaxBits, + const uint16_t repeat = kInaxMinRepeat); +#endif // SEND_INAX #if SEND_GLOBALCACHE void sendGC(uint16_t buf[], uint16_t len); #endif diff --git a/src/IRutils.cpp b/src/IRutils.cpp index cdc105e62..d8760ea07 100644 --- a/src/IRutils.cpp +++ b/src/IRutils.cpp @@ -132,6 +132,8 @@ decode_type_t strToDecodeType(const char * const str) { return decode_type_t::HITACHI_AC1; else if (!strcmp(str, "HITACHI_AC2")) return decode_type_t::HITACHI_AC2; + else if (!strcmp(str, "INAX")) + return decode_type_t::INAX; else if (!strcmp(str, "JVC")) return decode_type_t::JVC; else if (!strcmp(str, "KELVINATOR")) @@ -362,6 +364,9 @@ std::string typeToString(const decode_type_t protocol, const bool isRepeat) { case HITACHI_AC2: result = F("HITACHI_AC2"); break; + case INAX: + result = F("INAX"); + break; case JVC: result = F("JVC"); break; diff --git a/src/ir_Inax.cpp b/src/ir_Inax.cpp new file mode 100644 index 000000000..32a79eb86 --- /dev/null +++ b/src/ir_Inax.cpp @@ -0,0 +1,98 @@ +// Copyright 2019 David Conran (crankyoldgit) + +#include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +// Support for an IR controlled Robot Toilet +// +// Brand: Lixil +// Model: Inax +// Type: DT-BA283 + +// Documentation: +// https://www.lixil-manual.com/GCW-1365-16050/GCW-1365-16050.pdf + +// Constants +// Ref: +// https://github.com/markszabo/IRremoteESP8266/issues/706 +const uint16_t kInaxTick = 500; +const uint16_t kInaxHdrMark = 9000; +const uint16_t kInaxHdrSpace = 4500; +const uint16_t kInaxBitMark = 560; +const uint16_t kInaxOneSpace = 1675; +const uint16_t kInaxZeroSpace = kInaxBitMark; +const uint16_t kInaxMinGap = 40000; + +#if SEND_INAX +// Send a Inax Toilet formatted message. +// +// Args: +// data: The message to be sent. +// nbits: The bit size of the message being sent. typically kInaxBits. +// repeat: The number of times the message is to be repeated. +// +// Status: BETA / Should be working. +// +// Ref: https://github.com/markszabo/IRremoteESP8266/issues/706 +void IRsend::sendInax(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + sendGeneric(kInaxHdrMark, kInaxHdrSpace, + kInaxBitMark, kInaxOneSpace, + kInaxBitMark, kInaxZeroSpace, + kInaxBitMark, kInaxMinGap, + data, nbits, 38, true, repeat, kDutyDefault); +} +#endif + +#if DECODE_INAX +// Decode the supplied Inax Toilet 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 kInaxBits. +// 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: BETA / Should be Working. +// +bool IRrecv::decodeInax(decode_results *results, const uint16_t nbits, + const bool strict) { + if (results->rawlen < 2 * nbits + kHeader + kFooter - 1) + return false; // Can't possibly be a valid Inax message. + if (strict && nbits != kInaxBits) + return false; // We expect Inax to be a certain sized message. + + uint64_t data = 0; + uint16_t offset = kStartOffset; + + // Header + if (!matchMark(results->rawbuf[offset++], kInaxHdrMark)) return false; + if (!matchSpace(results->rawbuf[offset++], kInaxHdrSpace)) return false; + // Data + match_result_t data_result = + matchData(&(results->rawbuf[offset]), nbits, kInaxBitMark, + kInaxOneSpace, kInaxBitMark, kInaxZeroSpace); + if (data_result.success == false) return false; + data = data_result.data; + offset += data_result.used; + // Footer + if (!matchMark(results->rawbuf[offset++], kInaxBitMark)) return false; + if (offset < results->rawlen && + !matchAtLeast(results->rawbuf[offset], kInaxMinGap)) + return false; + + // Compliance + + // Success + results->bits = nbits; + results->value = data; + results->decode_type = INAX; + results->command = 0; + results->address = 0; + return true; +} +#endif diff --git a/test/Makefile b/test/Makefile index 78b42a4f6..e977e9422 100644 --- a/test/Makefile +++ b/test/Makefile @@ -38,7 +38,7 @@ TESTS = IRutils_test IRsend_test ir_NEC_test ir_GlobalCache_test \ ir_Carrier_test ir_Haier_test ir_Hitachi_test ir_GICable_test \ ir_Whirlpool_test ir_Lutron_test ir_Electra_test ir_Pioneer_test \ ir_MWM_test ir_Vestel_test ir_Teco_test ir_Tcl_test ir_Lego_test IRac_test \ - ir_MitsubishiHeavy_test ir_Trotec_test ir_Argo_test + ir_MitsubishiHeavy_test ir_Trotec_test ir_Argo_test ir_Inax_test # All Google Test headers. Usually you shouldn't change this # definition. @@ -83,7 +83,7 @@ PROTOCOLS = ir_NEC.o ir_Sony.o ir_Samsung.o ir_JVC.o ir_RCMM.o ir_RC5_RC6.o \ ir_Midea.o ir_Magiquest.o ir_Lasertag.o ir_Carrier.o ir_Haier.o \ ir_Hitachi.o ir_GICable.o ir_Whirlpool.o ir_Lutron.o ir_Electra.o \ ir_Pioneer.o ir_MWM.o ir_Vestel.o ir_Teco.o ir_Tcl.o ir_Lego.o ir_Argo.o \ - ir_Trotec.o ir_MitsubishiHeavy.o + ir_Trotec.o ir_MitsubishiHeavy.o ir_Inax.o # All the IR Protocol header files. PROTOCOLS_H = $(USER_DIR)/ir_Argo.h \ @@ -576,3 +576,12 @@ ir_Trotec_test.o : ir_Trotec_test.cpp $(COMMON_TEST_DEPS) $(GTEST_HEADERS) ir_Trotec_test : $(COMMON_OBJ) ir_Trotec_test.o $(CXX) $(CPPFLAGS) $(CXXFLAGS) -lpthread $^ -o $@ + +ir_Inax.o : $(USER_DIR)/ir_Inax.cpp $(COMMON_DEPS) + $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(INCLUDES) -c $(USER_DIR)/ir_Inax.cpp + +ir_Inax_test.o : ir_Inax_test.cpp $(COMMON_TEST_DEPS) $(GTEST_HEADERS) + $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(INCLUDES) -c ir_Inax_test.cpp + +ir_Inax_test : $(COMMON_OBJ) ir_Inax_test.o + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -lpthread $^ -o $@ diff --git a/test/ir_Inax_test.cpp b/test/ir_Inax_test.cpp new file mode 100644 index 000000000..b182cce32 --- /dev/null +++ b/test/ir_Inax_test.cpp @@ -0,0 +1,119 @@ +// Copyright 2019 crankyoldgit (David Conran) + +#include "IRsend.h" +#include "IRsend_test.h" +#include "IRutils.h" +#include "gtest/gtest.h" + + +// General housekeeping +TEST(TestInax, Housekeeping) { + ASSERT_EQ("INAX", typeToString(INAX)); + ASSERT_FALSE(hasACState(INAX)); +} + +// Tests for sendInax(). +// Test sending typical data only. +TEST(TestSendInax, SendDataOnly) { + IRsendTest irsend(0); + irsend.begin(); + + irsend.reset(); + irsend.sendInax(0x5C32CD); // Small flush. + EXPECT_EQ( + "f38000d50" + "m9000s4500" + "m560s560m560s1675m560s560m560s1675m560s1675m560s1675m560s560m560s560" + "m560s560m560s560m560s1675m560s1675m560s560m560s560m560s1675m560s560" + "m560s1675m560s1675m560s560m560s560m560s1675m560s1675m560s560m560s1675" + "m560s40000" + "m9000s4500" + "m560s560m560s1675m560s560m560s1675m560s1675m560s1675m560s560m560s560" + "m560s560m560s560m560s1675m560s1675m560s560m560s560m560s1675m560s560" + "m560s1675m560s1675m560s560m560s560m560s1675m560s1675m560s560m560s1675" + "m560s40000", + irsend.outputStr()); + + irsend.reset(); +} + +// Test sending with different repeats. +TEST(TestSendInax, SendWithRepeats) { + IRsendTest irsend(0); + irsend.begin(); + + irsend.reset(); + irsend.sendInax(0x5C32CD, kInaxBits, 0); // 0 repeats. + EXPECT_EQ( + "f38000d50" + "m9000s4500" + "m560s560m560s1675m560s560m560s1675m560s1675m560s1675m560s560m560s560" + "m560s560m560s560m560s1675m560s1675m560s560m560s560m560s1675m560s560" + "m560s1675m560s1675m560s560m560s560m560s1675m560s1675m560s560m560s1675" + "m560s40000", + irsend.outputStr()); + irsend.sendInax(0x5C32CD, kInaxBits, 2); // 2 repeats. + EXPECT_EQ( + "f38000d50" + "m9000s4500" + "m560s560m560s1675m560s560m560s1675m560s1675m560s1675m560s560m560s560" + "m560s560m560s560m560s1675m560s1675m560s560m560s560m560s1675m560s560" + "m560s1675m560s1675m560s560m560s560m560s1675m560s1675m560s560m560s1675" + "m560s40000" + "m9000s4500" + "m560s560m560s1675m560s560m560s1675m560s1675m560s1675m560s560m560s560" + "m560s560m560s560m560s1675m560s1675m560s560m560s560m560s1675m560s560" + "m560s1675m560s1675m560s560m560s560m560s1675m560s1675m560s560m560s1675" + "m560s40000" + "m9000s4500" + "m560s560m560s1675m560s560m560s1675m560s1675m560s1675m560s560m560s560" + "m560s560m560s560m560s1675m560s1675m560s560m560s560m560s1675m560s560" + "m560s1675m560s1675m560s560m560s560m560s1675m560s1675m560s560m560s1675" + "m560s40000", + irsend.outputStr()); +} + +// Tests for decodeInax(). + +// Decode normal Inax messages. +TEST(TestDecodeInax, SyntheticDecode) { + IRsendTest irsend(0); + IRrecv irrecv(0); + irsend.begin(); + + // Normal Inax 24-bit message. + irsend.reset(); + irsend.sendInax(0x5C32CD); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(INAX, irsend.capture.decode_type); + EXPECT_EQ(kInaxBits, irsend.capture.bits); + EXPECT_EQ(0x5C32CD, irsend.capture.value); + EXPECT_EQ(0, irsend.capture.address); + EXPECT_EQ(0, irsend.capture.command); +} + +// Decode real example via Issue #704 +TEST(TestDecodeInax, DecodeExamples) { + IRsendTest irsend(0); + IRrecv irrecv(0); + irsend.begin(); + + irsend.reset(); + // Inax Small Flush from Issue #309 + uint16_t smallFlushRawData[51] = { + 8996, 4474, 568, 556, 560, 1676, 568, 556, 562, 1676, 562, 1678, 566, + 1674, 566, 558, 560, 560, 566, 556, 566, 556, 560, 1678, 562, 1676, 566, + 556, 562, 560, 564, 1672, 566, 556, 562, 1676, 562, 1678, 562, 560, 564, + 558, 564, 1674, 560, 1678, 564, 560, 566, 1670, 562}; + + irsend.sendRaw(smallFlushRawData, 51, 38); + irsend.makeDecodeResult(); + + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(INAX, irsend.capture.decode_type); + EXPECT_EQ(kInaxBits, irsend.capture.bits); + EXPECT_EQ(0x5C32CD, irsend.capture.value); + EXPECT_EQ(0, irsend.capture.address); + EXPECT_EQ(0, irsend.capture.command); +} diff --git a/tools/Makefile b/tools/Makefile index ef2377c3d..680bfdd0a 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -50,7 +50,7 @@ PROTOCOLS = ir_NEC.o ir_Sony.o ir_Samsung.o ir_JVC.o ir_RCMM.o ir_RC5_RC6.o \ ir_Magiquest.o ir_Lasertag.o ir_Carrier.o ir_Haier.o ir_Hitachi.o \ ir_GICable.o ir_Whirlpool.o ir_Lutron.o ir_Electra.o ir_Pioneer.o \ ir_MWM.o ir_Vestel.o ir_Teco.o ir_Tcl.o ir_Lego.o \ - ir_MitsubishiHeavy.o + ir_MitsubishiHeavy.o ir_Inax.o # Common object files COMMON_OBJ = IRutils.o IRtimer.o IRsend.o IRrecv.o $(PROTOCOLS) @@ -104,6 +104,9 @@ ir_Samsung.o : $(USER_DIR)/ir_Samsung.cpp $(USER_DIR)/ir_Samsung.h $(COMMON_DEPS ir_Kelvinator.o : $(USER_DIR)/ir_Kelvinator.cpp $(USER_DIR)/ir_Kelvinator.h $(COMMON_DEPS) $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(INCLUDES) -c $(USER_DIR)/ir_Kelvinator.cpp +ir_Inax.o : $(USER_DIR)/ir_Inax.cpp $(COMMON_DEPS) + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $(USER_DIR)/ir_Inax.cpp + ir_JVC.o : $(USER_DIR)/ir_JVC.cpp $(COMMON_DEPS) $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $(USER_DIR)/ir_JVC.cpp