From 667ed876412307648b723767ba9cd004e562cb42 Mon Sep 17 00:00:00 2001 From: Victor Mukayev Date: Sat, 16 Jan 2021 11:53:32 +0300 Subject: [PATCH] Alpha support for Milestag2 (#1380) I've implemented MilesTag2 Lasertag protocol described here: http://hosting.cmalton.me.uk/chrism/lasertag/MT2Proto.pdf decoding actually meaning of bit's in protocol is on users side, they could just macro it for example: ```cpp #define MT_SHOT(player, team, damage) (unsigned long)(((player & 127) << 6) | ((team & 3 ) << 4) | (damage & 15 )) #define MT_PLAYER_VALUE(shot_data) (unsigned int)((shot_data >> 6) & 127) #define MT_TEAM_VALUE(shot_data) (unsigned short)((shot_data >> 4) & 3) #define MT_DAMAGE_VALUE(shot_data) (unsigned short)(shot_data & 15) ``` Tested it on ESP8266 and ESP32 and it works fine for me. But I don't have actual commercial Lasertag taggers and receivers with MilesTag2, to test against real world conditions. But I think this should be a good start. * Extend `matchData()`, `matchBytes()`, & `matchGeneric()` to handle no trailing space. * Add basic unit tests * Unit tests - Housekeeping - Sending Shot & Msg sizes. - Self-decoding - Failure when packets are not formatted correctly. Note: No real-world test data as yet. Co-authored-by: crankyoldgit X-Ref #1360 --- src/IRrecv.cpp | 72 +++++++++++++++------ src/IRrecv.h | 11 +++- src/IRremoteESP8266.h | 13 +++- src/IRsend.cpp | 7 ++ src/IRsend.h | 7 ++ src/IRtext.cpp | 1 + src/ir_MilesTag2.cpp | 113 ++++++++++++++++++++++++++++++++ src/locale/defaults.h | 3 + test/IRrecv_test.cpp | 68 +++++++++++++++++++ test/ir_Milestag2_test.cpp | 129 +++++++++++++++++++++++++++++++++++++ 10 files changed, 401 insertions(+), 23 deletions(-) create mode 100644 src/ir_MilesTag2.cpp create mode 100644 test/ir_Milestag2_test.cpp diff --git a/src/IRrecv.cpp b/src/IRrecv.cpp index 5125dbe0..2efd15df 100644 --- a/src/IRrecv.cpp +++ b/src/IRrecv.cpp @@ -596,6 +596,14 @@ bool IRrecv::decode(decode_results *results, irparams_t *save, DPRINTLN("Attempting NEC decode"); if (decodeNEC(results, offset)) return true; #endif +#if DECODE_MILESTAG2 + DPRINTLN("Attempting MilesTag2 decode"); + // Try decodeMilestag2() before decodeSony() because the protocols are + // similar in timings & structure, but the Miles one differs in nbits + // so this one should be tried first to try to reduce false detection + if (decodeMilestag2(results, offset, kMilesTag2MsgBits) || + decodeMilestag2(results, offset, kMilesTag2ShotBits)) return true; +#endif #if DECODE_SONY DPRINTLN("Attempting Sony decode"); if (decodeSony(results, offset)) return true; @@ -1202,30 +1210,49 @@ bool IRrecv::decodeHash(decode_results *results) { /// @param[in] excess Nr. of uSeconds. (Def: kMarkExcess) /// @param[in] MSBfirst Bit order to save the data in. (Def: true) /// true is Most Significant Bit First Order, false is Least Significant First +/// @param[in] expectlastspace Do we expect a space at the end of the message? /// @return A match_result_t structure containing the success (or not), the /// data value, and how many buffer entries were used. match_result_t IRrecv::matchData( volatile uint16_t *data_ptr, const uint16_t nbits, const uint16_t onemark, const uint32_t onespace, const uint16_t zeromark, const uint32_t zerospace, - const uint8_t tolerance, const int16_t excess, const bool MSBfirst) { + const uint8_t tolerance, const int16_t excess, const bool MSBfirst, + const bool expectlastspace) { match_result_t result; result.success = false; // Fail by default. result.data = 0; - for (result.used = 0; result.used < nbits * 2; - result.used += 2, data_ptr += 2) { - // Is the bit a '1'? - if (matchMark(*data_ptr, onemark, tolerance, excess) && - matchSpace(*(data_ptr + 1), onespace, tolerance, excess)) { - result.data = (result.data << 1) | 1; - } else if (matchMark(*data_ptr, zeromark, tolerance, excess) && - matchSpace(*(data_ptr + 1), zerospace, tolerance, excess)) { - result.data <<= 1; // The bit is a '0'. - } else { - if (!MSBfirst) result.data = reverseBits(result.data, result.used / 2); - return result; // It's neither, so fail. + if (expectlastspace) { // We are expecting data with a final space. + for (result.used = 0; result.used < nbits * 2; + result.used += 2, data_ptr += 2) { + // Is the bit a '1'? + if (matchMark(*data_ptr, onemark, tolerance, excess) && + matchSpace(*(data_ptr + 1), onespace, tolerance, excess)) { + result.data = (result.data << 1) | 1; + } else if (matchMark(*data_ptr, zeromark, tolerance, excess) && + matchSpace(*(data_ptr + 1), zerospace, tolerance, excess)) { + result.data <<= 1; // The bit is a '0'. + } else { + if (!MSBfirst) result.data = reverseBits(result.data, result.used / 2); + return result; // It's neither, so fail. + } + } + result.success = true; + } else { // We are expecting data without a final space. + // Match all but the last bit, as it may not match easily. + result = matchData(data_ptr, nbits ? nbits - 1 : 0, onemark, onespace, + zeromark, zerospace, tolerance, excess, true, true); + if (result.success) { + // Is the bit a '1'? + if (matchMark(*(data_ptr + result.used), onemark, tolerance, excess)) + result.data = (result.data << 1) | 1; + else if (matchMark(*(data_ptr + result.used), zeromark, tolerance, + excess)) + result.data <<= 1; // The bit is a '0'. + else + result.success = false; + if (result.success) result.used++; } } - result.success = true; if (!MSBfirst) result.data = reverseBits(result.data, nbits); return result; } @@ -1245,20 +1272,23 @@ match_result_t IRrecv::matchData( /// @param[in] excess Nr. of uSeconds. (Def: kMarkExcess) /// @param[in] MSBfirst Bit order to save the data in. (Def: true) /// true is Most Significant Bit First Order, false is Least Significant First +/// @param[in] expectlastspace Do we expect a space at the end of the message? /// @return If successful, how many buffer entries were used. Otherwise 0. uint16_t IRrecv::matchBytes(volatile uint16_t *data_ptr, uint8_t *result_ptr, const uint16_t remaining, const uint16_t nbytes, const uint16_t onemark, const uint32_t onespace, const uint16_t zeromark, const uint32_t zerospace, const uint8_t tolerance, const int16_t excess, - const bool MSBfirst) { + const bool MSBfirst, const bool expectlastspace) { // Check if there is enough capture buffer to possibly have the desired bytes. - if (remaining < nbytes * 8 * 2) return 0; // Nope, so abort. + if (remaining + expectlastspace < (nbytes * 8 * 2) + 1) + return 0; // Nope, so abort. uint16_t offset = 0; for (uint16_t byte_pos = 0; byte_pos < nbytes; byte_pos++) { + bool lastspace = (byte_pos + 1 == nbytes) ? expectlastspace : true; match_result_t result = matchData(data_ptr + offset, 8, onemark, onespace, zeromark, zerospace, tolerance, excess, - MSBfirst); + MSBfirst, lastspace); if (result.success == false) return 0; // Fail result_ptr[byte_pos] = (uint8_t)result.data; offset += result.used; @@ -1316,8 +1346,10 @@ uint16_t IRrecv::_matchGeneric(volatile uint16_t *data_ptr, const bool MSBfirst) { // If we are expecting byte sizes, check it's a factor of 8 or fail. if (!use_bits && nbits % 8 != 0) return 0; + // Calculate if we expect a trailing space in the data section. + const bool kexpectspace = footermark || (onespace != zerospace); // Calculate how much remaining buffer is required. - uint16_t min_remaining = nbits * 2; + uint16_t min_remaining = nbits * 2 - (kexpectspace ? 0 : 1); if (hdrmark) min_remaining++; if (hdrspace) min_remaining++; @@ -1340,7 +1372,7 @@ uint16_t IRrecv::_matchGeneric(volatile uint16_t *data_ptr, match_result_t result = IRrecv::matchData(data_ptr + offset, nbits, onemark, onespace, zeromark, zerospace, tolerance, - excess, MSBfirst); + excess, MSBfirst, kexpectspace); if (!result.success) return 0; *result_bits_ptr = result.data; offset += result.used; @@ -1349,7 +1381,7 @@ uint16_t IRrecv::_matchGeneric(volatile uint16_t *data_ptr, remaining - offset, nbits / 8, onemark, onespace, zeromark, zerospace, tolerance, - excess, MSBfirst); + excess, MSBfirst, kexpectspace); if (!data_used) return 0; offset += data_used; } diff --git a/src/IRrecv.h b/src/IRrecv.h index 6cc31fb8..7db98a04 100644 --- a/src/IRrecv.h +++ b/src/IRrecv.h @@ -192,14 +192,16 @@ class IRrecv { const uint16_t zeromark, const uint32_t zerospace, const uint8_t tolerance = kUseDefTol, const int16_t excess = kMarkExcess, - const bool MSBfirst = true); + const bool MSBfirst = true, + const bool expectlastspace = true); uint16_t matchBytes(volatile uint16_t *data_ptr, uint8_t *result_ptr, const uint16_t remaining, const uint16_t nbytes, const uint16_t onemark, const uint32_t onespace, const uint16_t zeromark, const uint32_t zerospace, const uint8_t tolerance = kUseDefTol, const int16_t excess = kMarkExcess, - const bool MSBfirst = true); + const bool MSBfirst = true, + const bool expectlastspace = true); uint16_t matchGeneric(volatile uint16_t *data_ptr, uint64_t *result_ptr, const uint16_t remaining, const uint16_t nbits, @@ -509,6 +511,11 @@ class IRrecv { const uint16_t nbits = kLasertagBits, const bool strict = true); #endif +#if DECODE_MILESTAG2 + bool decodeMilestag2(decode_results *results, uint16_t offset = kStartOffset, + const uint16_t nbits = kMilesTag2ShotBits, + const bool strict = true); +#endif #if DECODE_CARRIER_AC bool decodeCarrierAC(decode_results *results, uint16_t offset = kStartOffset, const uint16_t nbits = kCarrierAcBits, diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h index a70ce0b2..2b78b436 100644 --- a/src/IRremoteESP8266.h +++ b/src/IRremoteESP8266.h @@ -719,6 +719,13 @@ #define SEND_ELITESCREENS _IR_ENABLE_DEFAULT_ #endif // SEND_ELITESCREENS +#ifndef DECODE_MILESTAG2 +#define DECODE_MILESTAG2 _IR_ENABLE_DEFAULT_ +#endif // DECODE_MILESTAG2 +#ifndef SEND_MILESTAG2 +#define SEND_MILESTAG2 _IR_ENABLE_DEFAULT_ +#endif // SEND_MILESTAG2 + #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 || \ @@ -867,8 +874,9 @@ enum decode_type_t { MIRAGE, ELITESCREENS, // 95 PANASONIC_AC32, + MILESTAG2, // Add new entries before this one, and update it to point to the last entry. - kLastDecodeType = PANASONIC_AC32, + kLastDecodeType = MILESTAG2, }; // Message lengths & required repeat values @@ -1094,6 +1102,9 @@ const uint16_t kZepealBits = 16; const uint16_t kZepealMinRepeat = 4; const uint16_t kVoltasBits = 80; const uint16_t kVoltasStateLength = 10; +const uint16_t kMilesTag2ShotBits = 14; +const uint16_t kMilesTag2MsgBits = 24; +const uint16_t kMilesMinRepeat = 0; // Legacy defines. (Deprecated) diff --git a/src/IRsend.cpp b/src/IRsend.cpp index 4e0bffd7..bf550997 100644 --- a/src/IRsend.cpp +++ b/src/IRsend.cpp @@ -712,6 +712,8 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) { return kHitachiAc424Bits; case KELVINATOR: return kKelvinatorBits; + case MILESTAG2: + return kMilesTag2ShotBits; case MIRAGE: return kMirageBits; case MITSUBISHI_AC: @@ -897,6 +899,11 @@ bool IRsend::send(const decode_type_t type, const uint64_t data, sendMidea24(data, nbits, min_repeat); break; #endif // SEND_MIDEA24 +#if SEND_MILESTAG2 + case MILESTAG2: + sendMilestag2(data, nbits, min_repeat); + break; +#endif // SEND_MILESTAG2 #if SEND_MITSUBISHI case MITSUBISHI: sendMitsubishi(data, nbits, min_repeat); diff --git a/src/IRsend.h b/src/IRsend.h index 8eeee47e..5cbf39a5 100644 --- a/src/IRsend.h +++ b/src/IRsend.h @@ -682,6 +682,13 @@ class IRsend { const uint16_t nbits = kEliteScreensBits, const uint16_t repeat = kEliteScreensDefaultRepeat); #endif // SEND_ELITESCREENS +#if SEND_MILESTAG2 + // Since There 2 types of transmissions + // (14bits for Shooting by default, you can set 24 bit for msg delivery) + void sendMilestag2(const uint64_t data, + const uint16_t nbits = kMilesTag2ShotBits, + const uint16_t repeat = kMilesMinRepeat); +#endif // SEND_MILESTAG2 protected: #ifdef UNIT_TEST diff --git a/src/IRtext.cpp b/src/IRtext.cpp index 9dff4fa3..8e60585b 100644 --- a/src/IRtext.cpp +++ b/src/IRtext.cpp @@ -278,5 +278,6 @@ const PROGMEM char *kAllProtocolNamesStr = D_STR_MIRAGE "\x0" D_STR_ELITESCREENS "\x0" D_STR_PANASONIC_AC32 "\x0" + D_STR_MILESTAG2 "\x0" ///< New protocol strings should be added just above this line. "\x0"; ///< This string requires double null termination. diff --git a/src/ir_MilesTag2.cpp b/src/ir_MilesTag2.cpp new file mode 100644 index 00000000..013b2fbe --- /dev/null +++ b/src/ir_MilesTag2.cpp @@ -0,0 +1,113 @@ +// Copyright 2021 Victor Mukayev (vitos1k) +// Copyright 2021 David Conran (crankyoldgit) + +/// @file +/// @brief Support for the MilesTag2 IR protocol for LaserTag gaming +/// @see http://hosting.cmalton.me.uk/chrism/lasertag/MT2Proto.pdf +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1360 + +// Supports: +// Brand: Milestag2, Model: Various + +// TODO(vitos1k): This implementation would support only +// short SHOT packets(14bits) and MSGs = 24bits. Support +// for long MSGs > 24bits is TODO + +#include +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + +// Constants +// Shot packets have this bit as `0` +const uint16_t kMilesTag2ShotMask = 1 << (kMilesTag2ShotBits - 1); +// Msg packets have this bit as `1` +const uint32_t kMilesTag2MsgMask = 1 << (kMilesTag2MsgBits - 1); +const uint8_t kMilesTag2MsgTerminator = 0xE8; +const uint16_t kMilesTag2HdrMark = 2400; /// uSeconds. +const uint16_t kMilesTag2Space = 600; /// uSeconds. +const uint16_t kMilesTag2OneMark = 1200; /// uSeconds. +const uint16_t kMilesTag2ZeroMark = 600; /// uSeconds. +const uint16_t kMilesTag2RptLength = 32000; /// uSeconds. +const uint16_t kMilesTag2StdFreq = 38000; /// Hz. +const uint16_t kMilesTag2StdDuty = 25; /// Percentage. + +#if SEND_MILESTAG2 +/// Send a MilesTag2 formatted Shot/Msg packet. +/// Status: ALPHA / Probably works but needs testing with a real device. +/// @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. +void IRsend::sendMilestag2(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + sendGeneric( + kMilesTag2HdrMark, kMilesTag2Space, // Header + kMilesTag2OneMark, kMilesTag2Space, // 1 bit + kMilesTag2ZeroMark, kMilesTag2Space, // 0 bit + 0, // No footer mark + kMilesTag2RptLength, data, nbits, kMilesTag2StdFreq, true, // MSB First + repeat, kMilesTag2StdDuty); +} +#endif // SEND_MILESTAG2 + +#if DECODE_MILESTAG2 +/// Decode the supplied MilesTag2 message. +/// Status: ALPHA / Probably works but needs testing with a real device. +/// @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/1360 +bool IRrecv::decodeMilestag2(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + uint64_t data = 0; + // Header + Data + Optional Footer + if (!matchGeneric(results->rawbuf + offset, &data, + results->rawlen - offset, nbits, + kMilesTag2HdrMark, kMilesTag2Space, + kMilesTag2OneMark, kMilesTag2Space, + kMilesTag2ZeroMark, kMilesTag2Space, + 0, kMilesTag2RptLength, true)) return false; + + // Compliance + if (strict) { + switch (nbits) { + case kMilesTag2ShotBits: + // Is it a valid shot packet? + if (data & kMilesTag2ShotMask) return false; + break; + case kMilesTag2MsgBits: + // Is it a valid msg packet? i.e. Msg bit set & Terminator present. + if (!(data & kMilesTag2MsgMask) || + ((data & 0xFF) != kMilesTag2MsgTerminator)) + return false; + break; + default: + DPRINT("incorrect nbits:"); + DPRINTLN(nbits); + return false; // The request doesn't strictly match the protocol defn. + } + } + + // Success + results->bits = nbits; + results->value = data; + results->decode_type = decode_type_t::MILESTAG2; + switch (nbits) { + case kMilesTag2ShotBits: + results->command = data & 0x3F; // Team & Damage + results->address = data >> 6; // Player ID. + break; + case kMilesTag2MsgBits: + results->command = (data >> 8) & 0xFF; // Message data + results->address = (data >> 16) & 0x7F; // Message ID + break; + default: + results->command = 0; + results->address = 0; + } + return true; +} +#endif // DECODE_MILESTAG2 diff --git a/src/locale/defaults.h b/src/locale/defaults.h index e5cd476d..0746dd24 100644 --- a/src/locale/defaults.h +++ b/src/locale/defaults.h @@ -628,6 +628,9 @@ #ifndef D_STR_MIDEA24 #define D_STR_MIDEA24 "MIDEA24" #endif // D_STR_MIDEA24 +#ifndef D_STR_MILESTAG2 +#define D_STR_MILESTAG2 "MILESTAG2" +#endif // D_STR_MILESTAG2 #ifndef D_STR_MIRAGE #define D_STR_MIRAGE "MIRAGE" #endif // D_STR_MIRAGE diff --git a/test/IRrecv_test.cpp b/test/IRrecv_test.cpp index 08649978..76a0d51d 100644 --- a/test/IRrecv_test.cpp +++ b/test/IRrecv_test.cpp @@ -1014,6 +1014,74 @@ TEST(TestMatchGeneric, MissingHeaderFooter) { EXPECT_EQ(kentries - 2, entries_used); } +TEST(TestMatchGeneric, MissingFooterMarkEncoded) { + IRsendTest irsend(0); + IRrecv irrecv(1); + irsend.begin(); + + const uint16_t kentries = 10; + uint16_t data[kentries] = { // Mark encoded data. + 8000, // Header mark + 4000, // Header space + 2000, 500, // Bit #0 (1) + 1000, 500, // Bit #1 (0) + 2000, 500, // Bit #2 (1) + 1000, 500}; // Bit #3 (0) + // (No Footer) + + uint16_t offset = kStartOffset; + irsend.reset(); + + // Send it with the "trailing data space." + irsend.sendRaw(data, kentries, 38000); + irsend.makeDecodeResult(); + uint16_t entries_used = 0; + + uint64_t result_data = 0; + + // No footer match + entries_used = irrecv.matchGeneric( + irsend.capture.rawbuf + offset, &result_data, + irsend.capture.rawlen - offset, + 4, // nbits + 8000, 4000, // Header + 2000, 500, // one mark & space + 1000, 500, // zero mark & space + 0, 0, // NO Footer + true, // atleast on the footer space. + 1, // 1% Tolerance + 0, // No excess margin + true); // MSB first. + ASSERT_NE(0, entries_used); + EXPECT_EQ(0b1010, result_data); + EXPECT_EQ(irsend.capture.rawlen- kStartOffset, kentries); + EXPECT_EQ(irsend.capture.rawlen - kStartOffset - 1, entries_used); + EXPECT_EQ(kentries - 1, entries_used); + + // Now send it again, but make it appear like a real capture. + // i.e. The trailing space is removed. + irsend.reset(); + irsend.sendRaw(data, kentries - 1, 38000); + irsend.makeDecodeResult(); + entries_used = irrecv.matchGeneric( + irsend.capture.rawbuf + offset, &result_data, + irsend.capture.rawlen - offset, + 4, // nbits + 8000, 4000, // Header + 2000, 500, // one mark & space + 1000, 500, // zero mark & space + 0, 0, // NO Footer + true, // atleast on the footer space. + 1, // 1% Tolerance + 0, // No excess margin + true); // MSB first. + ASSERT_NE(0, entries_used); + EXPECT_EQ(0b1010, result_data); + EXPECT_EQ(irsend.capture.rawlen - kStartOffset, kentries - 1); + EXPECT_EQ(irsend.capture.rawlen - kStartOffset, entries_used); + EXPECT_EQ(kentries - 1, entries_used); +} + TEST(TestMatchGeneric, BitOrdering) { IRsendTest irsend(0); IRrecv irrecv(1); diff --git a/test/ir_Milestag2_test.cpp b/test/ir_Milestag2_test.cpp new file mode 100644 index 00000000..542852da --- /dev/null +++ b/test/ir_Milestag2_test.cpp @@ -0,0 +1,129 @@ +// Copyright 2021 David Conran + +#include "IRac.h" +#include "IRrecv.h" +#include "IRrecv_test.h" +#include "IRsend.h" +#include "IRsend_test.h" +#include "IRutils.h" +#include "gtest/gtest.h" + +TEST(TestUtils, Housekeeping) { + ASSERT_EQ("MILESTAG2", typeToString(decode_type_t::MILESTAG2)); + ASSERT_EQ(decode_type_t::MILESTAG2, strToDecodeType("MILESTAG2")); + ASSERT_FALSE(hasACState(decode_type_t::MILESTAG2)); + ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::MILESTAG2)); + ASSERT_EQ(kMilesTag2ShotBits, IRsend::defaultBits(decode_type_t::MILESTAG2)); +} + +// Test sending typical data only. +TEST(TestSendMilestag2, SendDataOnly) { + IRsendTest irsend(kGpioUnused); + irsend.begin(); + + // Shot packet + irsend.reset(); + irsend.sendMilestag2(0x379); + EXPECT_EQ( + "f38000d25" + "m2400s600" + "m600s600m600s600m600s600m600s600m1200s600m1200s600m600s600m1200s600" + "m1200s600m1200s600m1200s600m600s600m600s600m1200s32600", + irsend.outputStr()); + + irsend.reset(); + irsend.sendMilestag2(0x379, kMilesTag2ShotBits); + EXPECT_EQ( + "f38000d25" + "m2400s600" + "m600s600m600s600m600s600m600s600m1200s600m1200s600m600s600m1200s600" + "m1200s600m1200s600m1200s600m600s600m600s600m1200s32600", + irsend.outputStr()); + + // Msg packet + irsend.reset(); + irsend.sendMilestag2(0x8123E8, kMilesTag2MsgBits); + EXPECT_EQ( + "f38000d25" + "m2400s600" + "m1200s600m600s600m600s600m600s600m600s600m600s600m600s600m1200s600" + "m600s600m600s600m1200s600m600s600m600s600m600s600m1200s600m1200s600" + "m1200s600m1200s600m1200s600m600s600m1200s600m600s600m600s600m600s32600", + irsend.outputStr()); +} + +TEST(TestDecodeMilestag2, SyntheticSelfDecode) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + + irsend.begin(); + // Shot packet + irsend.reset(); + irsend.sendMilestag2(0x379); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(MILESTAG2, irsend.capture.decode_type); + EXPECT_EQ(kMilesTag2ShotBits, irsend.capture.bits); + EXPECT_EQ(0x379, irsend.capture.value); + EXPECT_EQ(0xD, irsend.capture.address); + EXPECT_EQ(0x39, irsend.capture.command); + + // Msg packet + irsend.reset(); + irsend.sendMilestag2(0x8123E8, kMilesTag2MsgBits); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(MILESTAG2, irsend.capture.decode_type); + EXPECT_EQ(kMilesTag2MsgBits, irsend.capture.bits); + EXPECT_EQ(0x8123E8, irsend.capture.value); + EXPECT_EQ(0x1, irsend.capture.address); + EXPECT_EQ(0x23, irsend.capture.command); +} + +TEST(TestDecodeMilestag2, FailToDecodeBadData) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + + irsend.begin(); + // Shot packet with the Shot packet bit incorrectly set. + irsend.reset(); + irsend.sendMilestag2(0xFF79); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_NE(MILESTAG2, irsend.capture.decode_type); + + // Msg packet with the Msg packet bit cleared. + irsend.reset(); + irsend.sendMilestag2(0x0123E8, kMilesTag2MsgBits); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_NE(MILESTAG2, irsend.capture.decode_type); + + // Msg packet with a bad Msg terminator. + irsend.reset(); + irsend.sendMilestag2(0x8123E9, kMilesTag2MsgBits); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_NE(MILESTAG2, irsend.capture.decode_type); +} + +TEST(TestDecodeMilestag2, RealSelfDecodeExample) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + // Ref: https://github.com/crankyoldgit/IRremoteESP8266/pull/1380#issuecomment-761159985 + const uint16_t rawData[29] = { + 2440, 602, + 608, 600, 606, 600, 606, 600, 602, 606, 1208, 602, 1216, 596, 604, 600, + 1214, 598, 1212, 600, 1208, 604, 1208, 602, 606, 600, 610, 596, 1210}; + + irsend.begin(); + irsend.reset(); + irsend.sendRaw(rawData, 29, 38000); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(MILESTAG2, irsend.capture.decode_type); + EXPECT_EQ(kMilesTag2ShotBits, irsend.capture.bits); + EXPECT_EQ(0x379, irsend.capture.value); + EXPECT_EQ(0xD, irsend.capture.address); + EXPECT_EQ(0x39, irsend.capture.command); +}