mirror of
https://github.com/crankyoldgit/IRremoteESP8266.git
synced 2026-01-12 00:05:10 +08:00
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:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/IRrecv.h
11
src/IRrecv.h
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
113
src/ir_MilesTag2.cpp
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
129
test/ir_Milestag2_test.cpp
Normal 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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user