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 <david@xyzzy.com.au>


X-Ref #1360
This commit is contained in:
Victor Mukayev
2021-01-16 11:53:32 +03:00
committed by GitHub
parent 2b508b3a35
commit 667ed87641
10 changed files with 401 additions and 23 deletions

View File

@@ -596,6 +596,14 @@ bool IRrecv::decode(decode_results *results, irparams_t *save,
DPRINTLN("Attempting NEC decode"); DPRINTLN("Attempting NEC decode");
if (decodeNEC(results, offset)) return true; if (decodeNEC(results, offset)) return true;
#endif #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 #if DECODE_SONY
DPRINTLN("Attempting Sony decode"); DPRINTLN("Attempting Sony decode");
if (decodeSony(results, offset)) return true; 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] excess Nr. of uSeconds. (Def: kMarkExcess)
/// @param[in] MSBfirst Bit order to save the data in. (Def: true) /// @param[in] MSBfirst Bit order to save the data in. (Def: true)
/// true is Most Significant Bit First Order, false is Least Significant First /// 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 /// @return A match_result_t structure containing the success (or not), the
/// data value, and how many buffer entries were used. /// data value, and how many buffer entries were used.
match_result_t IRrecv::matchData( match_result_t IRrecv::matchData(
volatile uint16_t *data_ptr, const uint16_t nbits, const uint16_t onemark, 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 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; match_result_t result;
result.success = false; // Fail by default. result.success = false; // Fail by default.
result.data = 0; result.data = 0;
for (result.used = 0; result.used < nbits * 2; if (expectlastspace) { // We are expecting data with a final space.
result.used += 2, data_ptr += 2) { for (result.used = 0; result.used < nbits * 2;
// Is the bit a '1'? result.used += 2, data_ptr += 2) {
if (matchMark(*data_ptr, onemark, tolerance, excess) && // Is the bit a '1'?
matchSpace(*(data_ptr + 1), onespace, tolerance, excess)) { if (matchMark(*data_ptr, onemark, tolerance, excess) &&
result.data = (result.data << 1) | 1; matchSpace(*(data_ptr + 1), onespace, tolerance, excess)) {
} else if (matchMark(*data_ptr, zeromark, tolerance, excess) && result.data = (result.data << 1) | 1;
matchSpace(*(data_ptr + 1), zerospace, tolerance, excess)) { } else if (matchMark(*data_ptr, zeromark, tolerance, excess) &&
result.data <<= 1; // The bit is a '0'. matchSpace(*(data_ptr + 1), zerospace, tolerance, excess)) {
} else { result.data <<= 1; // The bit is a '0'.
if (!MSBfirst) result.data = reverseBits(result.data, result.used / 2); } else {
return result; // It's neither, so fail. 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); if (!MSBfirst) result.data = reverseBits(result.data, nbits);
return result; return result;
} }
@@ -1245,20 +1272,23 @@ match_result_t IRrecv::matchData(
/// @param[in] excess Nr. of uSeconds. (Def: kMarkExcess) /// @param[in] excess Nr. of uSeconds. (Def: kMarkExcess)
/// @param[in] MSBfirst Bit order to save the data in. (Def: true) /// @param[in] MSBfirst Bit order to save the data in. (Def: true)
/// true is Most Significant Bit First Order, false is Least Significant First /// 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. /// @return If successful, how many buffer entries were used. Otherwise 0.
uint16_t IRrecv::matchBytes(volatile uint16_t *data_ptr, uint8_t *result_ptr, uint16_t IRrecv::matchBytes(volatile uint16_t *data_ptr, uint8_t *result_ptr,
const uint16_t remaining, const uint16_t nbytes, const uint16_t remaining, const uint16_t nbytes,
const uint16_t onemark, const uint32_t onespace, const uint16_t onemark, const uint32_t onespace,
const uint16_t zeromark, const uint32_t zerospace, const uint16_t zeromark, const uint32_t zerospace,
const uint8_t tolerance, const int16_t excess, 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. // 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; uint16_t offset = 0;
for (uint16_t byte_pos = 0; byte_pos < nbytes; byte_pos++) { 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, match_result_t result = matchData(data_ptr + offset, 8, onemark, onespace,
zeromark, zerospace, tolerance, excess, zeromark, zerospace, tolerance, excess,
MSBfirst); MSBfirst, lastspace);
if (result.success == false) return 0; // Fail if (result.success == false) return 0; // Fail
result_ptr[byte_pos] = (uint8_t)result.data; result_ptr[byte_pos] = (uint8_t)result.data;
offset += result.used; offset += result.used;
@@ -1316,8 +1346,10 @@ uint16_t IRrecv::_matchGeneric(volatile uint16_t *data_ptr,
const bool MSBfirst) { const bool MSBfirst) {
// If we are expecting byte sizes, check it's a factor of 8 or fail. // If we are expecting byte sizes, check it's a factor of 8 or fail.
if (!use_bits && nbits % 8 != 0) return 0; 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. // 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 (hdrmark) min_remaining++;
if (hdrspace) 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, match_result_t result = IRrecv::matchData(data_ptr + offset, nbits,
onemark, onespace, onemark, onespace,
zeromark, zerospace, tolerance, zeromark, zerospace, tolerance,
excess, MSBfirst); excess, MSBfirst, kexpectspace);
if (!result.success) return 0; if (!result.success) return 0;
*result_bits_ptr = result.data; *result_bits_ptr = result.data;
offset += result.used; offset += result.used;
@@ -1349,7 +1381,7 @@ uint16_t IRrecv::_matchGeneric(volatile uint16_t *data_ptr,
remaining - offset, nbits / 8, remaining - offset, nbits / 8,
onemark, onespace, onemark, onespace,
zeromark, zerospace, tolerance, zeromark, zerospace, tolerance,
excess, MSBfirst); excess, MSBfirst, kexpectspace);
if (!data_used) return 0; if (!data_used) return 0;
offset += data_used; offset += data_used;
} }

View File

@@ -192,14 +192,16 @@ class IRrecv {
const uint16_t zeromark, const uint32_t zerospace, const uint16_t zeromark, const uint32_t zerospace,
const uint8_t tolerance = kUseDefTol, const uint8_t tolerance = kUseDefTol,
const int16_t excess = kMarkExcess, 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, uint16_t matchBytes(volatile uint16_t *data_ptr, uint8_t *result_ptr,
const uint16_t remaining, const uint16_t nbytes, const uint16_t remaining, const uint16_t nbytes,
const uint16_t onemark, const uint32_t onespace, const uint16_t onemark, const uint32_t onespace,
const uint16_t zeromark, const uint32_t zerospace, const uint16_t zeromark, const uint32_t zerospace,
const uint8_t tolerance = kUseDefTol, const uint8_t tolerance = kUseDefTol,
const int16_t excess = kMarkExcess, 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, uint16_t matchGeneric(volatile uint16_t *data_ptr,
uint64_t *result_ptr, uint64_t *result_ptr,
const uint16_t remaining, const uint16_t nbits, const uint16_t remaining, const uint16_t nbits,
@@ -509,6 +511,11 @@ class IRrecv {
const uint16_t nbits = kLasertagBits, const uint16_t nbits = kLasertagBits,
const bool strict = true); const bool strict = true);
#endif #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 #if DECODE_CARRIER_AC
bool decodeCarrierAC(decode_results *results, uint16_t offset = kStartOffset, bool decodeCarrierAC(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kCarrierAcBits, const uint16_t nbits = kCarrierAcBits,

View File

@@ -719,6 +719,13 @@
#define SEND_ELITESCREENS _IR_ENABLE_DEFAULT_ #define SEND_ELITESCREENS _IR_ENABLE_DEFAULT_
#endif // SEND_ELITESCREENS #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 || \ #if (DECODE_ARGO || DECODE_DAIKIN || DECODE_FUJITSU_AC || DECODE_GREE || \
DECODE_KELVINATOR || DECODE_MITSUBISHI_AC || DECODE_TOSHIBA_AC || \ DECODE_KELVINATOR || DECODE_MITSUBISHI_AC || DECODE_TOSHIBA_AC || \
DECODE_TROTEC || DECODE_HAIER_AC || DECODE_HITACHI_AC || \ DECODE_TROTEC || DECODE_HAIER_AC || DECODE_HITACHI_AC || \
@@ -867,8 +874,9 @@ enum decode_type_t {
MIRAGE, MIRAGE,
ELITESCREENS, // 95 ELITESCREENS, // 95
PANASONIC_AC32, PANASONIC_AC32,
MILESTAG2,
// Add new entries before this one, and update it to point to the last entry. // 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 // Message lengths & required repeat values
@@ -1094,6 +1102,9 @@ const uint16_t kZepealBits = 16;
const uint16_t kZepealMinRepeat = 4; const uint16_t kZepealMinRepeat = 4;
const uint16_t kVoltasBits = 80; const uint16_t kVoltasBits = 80;
const uint16_t kVoltasStateLength = 10; const uint16_t kVoltasStateLength = 10;
const uint16_t kMilesTag2ShotBits = 14;
const uint16_t kMilesTag2MsgBits = 24;
const uint16_t kMilesMinRepeat = 0;
// Legacy defines. (Deprecated) // Legacy defines. (Deprecated)

View File

@@ -712,6 +712,8 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) {
return kHitachiAc424Bits; return kHitachiAc424Bits;
case KELVINATOR: case KELVINATOR:
return kKelvinatorBits; return kKelvinatorBits;
case MILESTAG2:
return kMilesTag2ShotBits;
case MIRAGE: case MIRAGE:
return kMirageBits; return kMirageBits;
case MITSUBISHI_AC: case MITSUBISHI_AC:
@@ -897,6 +899,11 @@ bool IRsend::send(const decode_type_t type, const uint64_t data,
sendMidea24(data, nbits, min_repeat); sendMidea24(data, nbits, min_repeat);
break; break;
#endif // SEND_MIDEA24 #endif // SEND_MIDEA24
#if SEND_MILESTAG2
case MILESTAG2:
sendMilestag2(data, nbits, min_repeat);
break;
#endif // SEND_MILESTAG2
#if SEND_MITSUBISHI #if SEND_MITSUBISHI
case MITSUBISHI: case MITSUBISHI:
sendMitsubishi(data, nbits, min_repeat); sendMitsubishi(data, nbits, min_repeat);

View File

@@ -682,6 +682,13 @@ class IRsend {
const uint16_t nbits = kEliteScreensBits, const uint16_t nbits = kEliteScreensBits,
const uint16_t repeat = kEliteScreensDefaultRepeat); const uint16_t repeat = kEliteScreensDefaultRepeat);
#endif // SEND_ELITESCREENS #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: protected:
#ifdef UNIT_TEST #ifdef UNIT_TEST

View File

@@ -278,5 +278,6 @@ const PROGMEM char *kAllProtocolNamesStr =
D_STR_MIRAGE "\x0" D_STR_MIRAGE "\x0"
D_STR_ELITESCREENS "\x0" D_STR_ELITESCREENS "\x0"
D_STR_PANASONIC_AC32 "\x0" D_STR_PANASONIC_AC32 "\x0"
D_STR_MILESTAG2 "\x0"
///< New protocol strings should be added just above this line. ///< New protocol strings should be added just above this line.
"\x0"; ///< This string requires double null termination. "\x0"; ///< This string requires double null termination.

113
src/ir_MilesTag2.cpp Normal file
View File

@@ -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 <algorithm>
#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

View File

@@ -628,6 +628,9 @@
#ifndef D_STR_MIDEA24 #ifndef D_STR_MIDEA24
#define D_STR_MIDEA24 "MIDEA24" #define D_STR_MIDEA24 "MIDEA24"
#endif // D_STR_MIDEA24 #endif // D_STR_MIDEA24
#ifndef D_STR_MILESTAG2
#define D_STR_MILESTAG2 "MILESTAG2"
#endif // D_STR_MILESTAG2
#ifndef D_STR_MIRAGE #ifndef D_STR_MIRAGE
#define D_STR_MIRAGE "MIRAGE" #define D_STR_MIRAGE "MIRAGE"
#endif // D_STR_MIRAGE #endif // D_STR_MIRAGE

View File

@@ -1014,6 +1014,74 @@ TEST(TestMatchGeneric, MissingHeaderFooter) {
EXPECT_EQ(kentries - 2, entries_used); 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) { TEST(TestMatchGeneric, BitOrdering) {
IRsendTest irsend(0); IRsendTest irsend(0);
IRrecv irrecv(1); IRrecv irrecv(1);

129
test/ir_Milestag2_test.cpp Normal file
View File

@@ -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);
}