diff --git a/src/IRrecv.cpp b/src/IRrecv.cpp index 24ea0b021..de677777d 100644 --- a/src/IRrecv.cpp +++ b/src/IRrecv.cpp @@ -860,6 +860,10 @@ bool IRrecv::decode(decode_results *results, irparams_t *save, DPRINTLN("Attempting Sanyo AC decode"); if (decodeSanyoAc(results, offset)) return true; #endif // DECODE_SANYO_AC +#if DECODE_VOLTAS + DPRINTLN("Attempting Voltas decode"); + if (decodeVoltas(results)) return true; +#endif // DECODE_VOLTAS // Typically new protocols are added above this line. } #if DECODE_HASH diff --git a/src/IRrecv.h b/src/IRrecv.h index 62cdeed6b..7fa13c5fd 100644 --- a/src/IRrecv.h +++ b/src/IRrecv.h @@ -262,6 +262,12 @@ class IRrecv { const bool GEThomas = true); void crudeNoiseFilter(decode_results *results, const uint16_t floor = 0); bool decodeHash(decode_results *results); +#if DECODE_VOLTAS + bool decodeVoltas(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kVoltasBits, + const bool strict = true); +#endif // DECODE_VOLTAS #if (DECODE_NEC || DECODE_SHERWOOD || DECODE_AIWA_RC_T501 || DECODE_SANYO) bool decodeNEC(decode_results *results, uint16_t offset = kStartOffset, const uint16_t nbits = kNECBits, const bool strict = true); diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h index 8e3efd86b..e8266aaa4 100644 --- a/src/IRremoteESP8266.h +++ b/src/IRremoteESP8266.h @@ -670,6 +670,13 @@ #define SEND_ZEPEAL _IR_ENABLE_DEFAULT_ #endif // SEND_ZEPEAL +#ifndef DECODE_VOLTAS +#define DECODE_VOLTAS _IR_ENABLE_DEFAULT_ +#endif // DECODE_VOLTAS +#ifndef SEND_VOLTAS +#define SEND_VOLTAS _IR_ENABLE_DEFAULT_ +#endif // SEND_VOLTAS + #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 || \ @@ -681,7 +688,8 @@ DECODE_NEOCLIMA || DECODE_DAIKIN176 || DECODE_DAIKIN128 || \ DECODE_AMCOR || DECODE_DAIKIN152 || DECODE_MITSUBISHI136 || \ DECODE_MITSUBISHI112 || DECODE_HITACHI_AC424 || DECODE_HITACHI_AC3 || \ - DECODE_HITACHI_AC344 || DECODE_CORONA_AC || DECODE_SANYO_AC) + DECODE_HITACHI_AC344 || DECODE_CORONA_AC || DECODE_SANYO_AC || \ + DECODE_VOLTAS) // Add any DECODE to the above if it uses result->state (see kStateSizeMax) // you might also want to add the protocol to hasACState function #define DECODE_AC true // We need some common infrastructure for decoding A/Cs. @@ -810,8 +818,9 @@ enum decode_type_t { MIDEA24, ZEPEAL, SANYO_AC, + VOLTAS, // 90 // Add new entries before this one, and update it to point to the last entry. - kLastDecodeType = SANYO_AC, + kLastDecodeType = VOLTAS, }; // Message lengths & required repeat values @@ -1023,6 +1032,8 @@ const uint16_t kWhynterBits = 32; const uint8_t kVestelAcBits = 56; const uint16_t kZepealBits = 16; const uint16_t kZepealMinRepeat = 4; +const uint16_t kVoltasBits = 80; +const uint16_t kVoltasStateLength = 10; // Legacy defines. (Deprecated) diff --git a/src/IRsend.cpp b/src/IRsend.cpp index e3537381f..c2bcd90ca 100644 --- a/src/IRsend.cpp +++ b/src/IRsend.cpp @@ -731,6 +731,8 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) { return kToshibaACBits; case TROTEC: return kTrotecBits; + case VOLTAS: + return kVoltasBits; case WHIRLPOOL_AC: return kWhirlpoolAcBits; // No default amount of bits. @@ -1001,6 +1003,11 @@ bool IRsend::send(const decode_type_t type, const uint64_t data, bool IRsend::send(const decode_type_t type, const uint8_t *state, const uint16_t nbytes) { switch (type) { +#if SEND_VOLTAS + case VOLTAS: + sendVoltas(state, nbytes); + break; +#endif // SEND_VOLTAS #if SEND_AMCOR case AMCOR: sendAmcor(state, nbytes); diff --git a/src/IRsend.h b/src/IRsend.h index 0b237ecc0..31d979a85 100644 --- a/src/IRsend.h +++ b/src/IRsend.h @@ -635,6 +635,11 @@ class IRsend { const uint16_t nbits = kZepealBits, const uint16_t repeat = kZepealMinRepeat); #endif +#if SEND_VOLTAS + void sendVoltas(const unsigned char data[], + const uint16_t nbytes = kVoltasStateLength, + const uint16_t repeat = kNoRepeat); +#endif // SEND_VOLTAS protected: #ifdef UNIT_TEST diff --git a/src/IRtext.cpp b/src/IRtext.cpp index 80e39b0a1..7e692a7b5 100644 --- a/src/IRtext.cpp +++ b/src/IRtext.cpp @@ -266,5 +266,6 @@ const PROGMEM char *kAllProtocolNamesStr = D_STR_MIDEA24 "\x0" D_STR_ZEPEAL "\x0" D_STR_SANYO_AC "\x0" + D_STR_VOLTAS "\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 360b990e2..25a1fe13a 100644 --- a/src/IRutils.cpp +++ b/src/IRutils.cpp @@ -166,6 +166,7 @@ bool hasACState(const decode_type_t protocol) { case TCL112AC: case TOSHIBA_AC: case TROTEC: + case VOLTAS: case WHIRLPOOL_AC: return true; default: diff --git a/src/ir_Voltas.cpp b/src/ir_Voltas.cpp new file mode 100644 index 000000000..f0fe910f7 --- /dev/null +++ b/src/ir_Voltas.cpp @@ -0,0 +1,69 @@ +// Copyright 2020 David Conran (crankyoldgit) +// Copyright 2020 manj9501 +/// @file +/// @brief Support for Voltas A/C protocol +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1238 + +// Supports: +// Brand: Voltas, Model: 122LZF 4011252 Window A/C + +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +// Constants +const uint16_t kVoltasBitMark = 1026; ///< uSeconds. +const uint16_t kVoltasOneSpace = 2553; ///< uSeconds. +const uint16_t kVoltasZeroSpace = 554; ///< uSeconds. +const uint16_t kVoltasFreq = 38000; ///< Hz. + +#if SEND_VOLTAS +/// Send a Voltas formatted message. +/// Status: ALPHA / Untested. +/// @param[in] data An array of bytes containing the IR command. +/// It is assumed to be in MSB order for this code. +/// e.g. +/// @code +/// uint8_t data[kVoltasStateLength] = {0x33, 0x28, 0x88, 0x1A, 0x3B, 0x3B, +/// 0x3B, 0x11, 0x00, 0x40}; +/// @endcode +/// @param[in] nbytes Nr. of bytes of data in the array. (>=kVoltasStateLength) +/// @param[in] repeat Nr. of times the message is to be repeated. +void IRsend::sendVoltas(const uint8_t data[], const uint16_t nbytes, + const uint16_t repeat) { + sendGeneric(0, 0, + kVoltasBitMark, kVoltasOneSpace, + kVoltasBitMark, kVoltasZeroSpace, + kVoltasBitMark, kDefaultMessageGap, + data, nbytes, + kVoltasFreq, true, repeat, kDutyDefault); +} +#endif // SEND_VOLTAS + +#if DECODE_VOLTAS +/// Decode the supplied Voltas message. +/// Status: ALPHA / Untested. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// @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. +bool IRrecv::decodeVoltas(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (strict && nbits != kVoltasBits) return false; + + // Data + Footer + if (!matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, nbits, + 0, 0, // No header + kVoltasBitMark, kVoltasOneSpace, + kVoltasBitMark, kVoltasZeroSpace, + kVoltasBitMark, kDefaultMessageGap, true)) return false; + + // Success + results->decode_type = decode_type_t::VOLTAS; + results->bits = nbits; + return true; +} +#endif // DECODE_VOLTAS diff --git a/src/locale/defaults.h b/src/locale/defaults.h index 7fc0990e1..2f9bef17d 100644 --- a/src/locale/defaults.h +++ b/src/locale/defaults.h @@ -730,6 +730,9 @@ #ifndef D_STR_VESTEL_AC #define D_STR_VESTEL_AC "VESTEL_AC" #endif // D_STR_VESTEL_AC +#ifndef D_STR_VOLTAS +#define D_STR_VOLTAS "VOLTAS" +#endif // D_STR_VOLTAS #ifndef D_STR_WHIRLPOOL_AC #define D_STR_WHIRLPOOL_AC "WHIRLPOOL_AC" #endif // D_STR_WHIRLPOOL_AC diff --git a/test/ir_Voltas_test.cpp b/test/ir_Voltas_test.cpp new file mode 100644 index 000000000..8931fcab3 --- /dev/null +++ b/test/ir_Voltas_test.cpp @@ -0,0 +1,67 @@ +// Copyright 2020 crankyoldgit + +#include "IRac.h" +#include "IRrecv.h" +#include "IRrecv_test.h" +#include "IRsend.h" +#include "IRsend_test.h" +#include "gtest/gtest.h" + +// Tests for decodeVoltas(). + +TEST(TestDecodeVoltas, RealExample) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + const uint16_t rawData[161] = { + 1002, 584, 1000, 586, 1000, 2568, 1002, 2570, 1002, 586, 998, 588, 1000, + 2568, 1002, 2570, 1002, 2572, 1002, 584, 1002, 586, 1000, 584, 1000, 586, + 1002, 2568, 1004, 584, 1000, 586, 1002, 2568, 1002, 584, 1002, 584, 1004, + 584, 1000, 2568, 1002, 586, 1000, 586, 998, 590, 998, 584, 1002, 584, + 1000, 586, 1000, 2570, 1002, 2568, 1004, 584, 1000, 584, 1002, 584, 1002, + 582, 1004, 584, 1002, 2568, 1002, 2570, 1004, 2570, 1000, 586, 1002, 2568, + 1004, 2568, 1006, 584, 1000, 584, 1002, 2568, 1002, 2570, 1002, 2568, + 1002, 586, 1002, 2570, 1000, 2570, 1002, 588, 998, 586, 1000, 2568, 1004, + 2568, 1004, 2568, 1002, 588, 998, 2570, 1002, 2568, 1004, 586, 1002, 584, + 1000, 586, 1000, 2570, 1000, 586, 1000, 584, 1002, 586, 1000, 2568, 1004, + 584, 1000, 586, 1000, 586, 1002, 584, 1002, 586, 1000, 586, 1000, 586, + 1000, 586, 1000, 2568, 1002, 2568, 1002, 2568, 1004, 586, 1000, 584, + 1000, 2570, 1004, 2568, 1004, 584, 1002}; + const uint8_t expected[kVoltasStateLength] = { + 0x33, 0x84, 0x88, 0x18, 0x3B, 0x3B, 0x3B, 0x11, 0x00, 0xE6}; + + irsend.begin(); + irsend.reset(); + irsend.sendRaw(rawData, 161, 38); + irsend.makeDecodeResult(); + + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + ASSERT_EQ(decode_type_t::VOLTAS, irsend.capture.decode_type); + ASSERT_EQ(kVoltasBits, irsend.capture.bits); + EXPECT_STATE_EQ(expected, irsend.capture.state, irsend.capture.bits); +} + +TEST(TestDecodeVoltas, SyntheticExample) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + irsend.begin(); + irsend.reset(); + const uint8_t expected[kVoltasStateLength] = { + 0x33, 0x84, 0x88, 0x18, 0x3B, 0x3B, 0x3B, 0x11, 0x00, 0xE6}; + // power + irsend.sendVoltas(expected); + irsend.makeDecodeResult(); + + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(decode_type_t::VOLTAS, irsend.capture.decode_type); + EXPECT_EQ(kVoltasBits, irsend.capture.bits); + EXPECT_STATE_EQ(expected, irsend.capture.state, irsend.capture.bits); +} + +TEST(TestUtils, Housekeeping) { + ASSERT_EQ("VOLTAS", typeToString(decode_type_t::VOLTAS)); + ASSERT_EQ(decode_type_t::VOLTAS, strToDecodeType("VOLTAS")); + ASSERT_TRUE(hasACState(decode_type_t::VOLTAS)); + ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::VOLTAS)); + ASSERT_EQ(kVoltasBits, IRsend::defaultBits(decode_type_t::VOLTAS)); + ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::VOLTAS)); +}