Skip to content

Commit

Permalink
Initial support of a "Lasertag" toy protocol (#374)
Browse files Browse the repository at this point in the history
- sendLasertag() & decodeLasertag() for issue #366
- Add an option of a fixed 'delta' to the core matching functions.
- Tweak getRClevel() to allow custom tolerances, excess levels, and fixed ranges.
- Unit tests for Lasertag etc.
- Update example code & tools for this protocol.
  • Loading branch information
crankyoldgit authored Dec 13, 2017
1 parent d6f86d3 commit 7bd411a
Show file tree
Hide file tree
Showing 11 changed files with 579 additions and 51 deletions.
6 changes: 6 additions & 0 deletions examples/IRMQTTServer/IRMQTTServer.ino
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ void handleRoot() {
"<option value='13'>Dish</option>"
"<option value='6'>JVC</option>"
"<option value='10'>LG</option>"
"<option value='36'>Lasertag</option>"
"<option value='35'>MagiQuest</option>"
"<option value='34'>Midea</option>"
"<option value='12'>Mitsubishi</option>"
Expand Down Expand Up @@ -1008,6 +1009,11 @@ void sendIRCode(int const ir_type, uint64_t const code, char const * code_str,
bits = MAGIQUEST_BITS;
irsend.sendMagiQuest(code, bits, repeat);
break;
case LASERTAG: // 36
if (bits == 0)
bits = LASERTAG_BITS;
irsend.sendLasertag(code, bits, repeat);
break;
}

// Release the lock.
Expand Down
44 changes: 28 additions & 16 deletions src/IRrecv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,11 @@ bool IRrecv::decode(decode_results *results, irparams_t *save) {
return true;
}
#endif
#if DECODE_LASERTAG
DPRINTLN("Attempting Lasertag decode");
if (decodeLasertag(results))
return true;
#endif
#if DECODE_HASH
// decodeHash returns a hash on any input.
// Thus, it needs to be last in the list.
Expand All @@ -445,60 +450,66 @@ bool IRrecv::decode(decode_results *results, irparams_t *save) {
// Args:
// usecs: Nr. of uSeconds.
// tolerance: Percent as an integer. e.g. 10 is 10%
// delta: A non-scaling amount to reduce usecs by.
// Returns:
// Nr. of ticks.
uint32_t IRrecv::ticksLow(uint32_t usecs, uint8_t tolerance) {
uint32_t IRrecv::ticksLow(uint32_t usecs, uint8_t tolerance, uint16_t delta) {
// max() used to ensure the result can't drop below 0 before the cast.
return((uint32_t) std::max((int32_t) (usecs * (1.0 - tolerance / 100.0)), 0));
return((uint32_t) std::max(
(int32_t) (usecs * (1.0 - tolerance / 100.0) - delta), 0));
}

// Calculate the upper bound of the nr. of ticks.
//
// Args:
// usecs: Nr. of uSeconds.
// tolerance: Percent as an integer. e.g. 10 is 10%
// delta: A non-scaling amount to increase usecs by.
// Returns:
// Nr. of ticks.
uint32_t IRrecv::ticksHigh(uint32_t usecs, uint8_t tolerance) {
return((uint32_t) (usecs * (1.0 + tolerance / 100.0)) + 1);
uint32_t IRrecv::ticksHigh(uint32_t usecs, uint8_t tolerance, uint16_t delta) {
return((uint32_t) (usecs * (1.0 + tolerance / 100.0)) + 1 + delta);
}

// Check if we match a pulse(measured) with the desired within
// +/-tolerance percent.
// +/-tolerance percent and/or +/- a fixed delta range.
//
// Args:
// measured: The recorded period of the signal pulse.
// desired: The expected period (in useconds) we are matching against.
// tolerance: A percentage expressed as an integer. e.g. 10 is 10%.
// delta: A non-scaling (+/-) error margin (in useconds).
//
// Returns:
// Boolean: true if it matches, false if it doesn't.
bool IRrecv::match(uint32_t measured, uint32_t desired,
uint8_t tolerance) {
uint8_t tolerance, uint16_t delta) {
measured *= RAWTICK; // Convert to uSecs.
DPRINT("Matching: ");
DPRINT(ticksLow(desired, tolerance));
DPRINT(ticksLow(desired, tolerance, delta));
DPRINT(" <= ");
DPRINT(measured);
DPRINT(" <= ");
DPRINTLN(ticksHigh(desired, tolerance));
return (measured >= ticksLow(desired, tolerance) &&
measured <= ticksHigh(desired, tolerance));
DPRINTLN(ticksHigh(desired, tolerance, delta));
return (measured >= ticksLow(desired, tolerance, delta) &&
measured <= ticksHigh(desired, tolerance, delta));
}


// Check if we match a pulse(measured) of at least desired within
// +/-tolerance percent.
// tolerance percent and/or a fixed delta margin.
//
// Args:
// measured: The recorded period of the signal pulse.
// desired: The expected period (in useconds) we are matching against.
// tolerance: A percentage expressed as an integer. e.g. 10 is 10%.
// delta: A non-scaling amount to reduce usecs by.

//
// Returns:
// Boolean: true if it matches, false if it doesn't.
bool IRrecv::matchAtLeast(uint32_t measured, uint32_t desired,
uint8_t tolerance) {
uint8_t tolerance, uint16_t delta) {
measured *= RAWTICK; // Convert to uSecs.
DPRINT("Matching ATLEAST ");
DPRINT(measured);
Expand All @@ -507,17 +518,18 @@ bool IRrecv::matchAtLeast(uint32_t measured, uint32_t desired,
DPRINT(". Matching: ");
DPRINT(measured);
DPRINT(" >= ");
DPRINT(ticksLow(std::min(desired, MS_TO_USEC(irparams.timeout)), tolerance));
DPRINT(ticksLow(std::min(desired, MS_TO_USEC(irparams.timeout)), tolerance,
delta));
DPRINT(" [min(");
DPRINT(ticksLow(desired, tolerance));
DPRINT(ticksLow(desired, tolerance, delta));
DPRINT(", ");
DPRINT(ticksLow(MS_TO_USEC(irparams.timeout), tolerance));
DPRINT(ticksLow(MS_TO_USEC(irparams.timeout), tolerance, delta));
DPRINTLN(")]");
// We really should never get a value of 0, except as the last value
// in the buffer. If that is the case, then assume infinity and return true.
if (measured == 0) return true;
return measured >= ticksLow(std::min(desired, MS_TO_USEC(irparams.timeout)),
tolerance);
tolerance, delta);
}

// Check if we match a mark signal(measured) with the desired within
Expand Down
19 changes: 13 additions & 6 deletions src/IRrecv.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class IRrecv {
void setUnknownThreshold(uint16_t length);
#endif
static bool match(uint32_t measured, uint32_t desired,
uint8_t tolerance = TOLERANCE);
uint8_t tolerance = TOLERANCE, uint16_t delta = 0);
static bool matchMark(uint32_t measured, uint32_t desired,
uint8_t tolerance = TOLERANCE, int16_t excess = MARK_EXCESS);
static bool matchSpace(uint32_t measured, uint32_t desired,
Expand All @@ -138,10 +138,12 @@ class IRrecv {
// These are called by decode
void copyIrParams(volatile irparams_t *src, irparams_t *dst);
int16_t compare(uint16_t oldval, uint16_t newval);
static uint32_t ticksLow(uint32_t usecs, uint8_t tolerance = TOLERANCE);
static uint32_t ticksHigh(uint32_t usecs, uint8_t tolerance = TOLERANCE);
static uint32_t ticksLow(uint32_t usecs, uint8_t tolerance = TOLERANCE,
uint16_t delta = 0);
static uint32_t ticksHigh(uint32_t usecs, uint8_t tolerance = TOLERANCE,
uint16_t delta = 0);
bool matchAtLeast(uint32_t measured, uint32_t desired,
uint8_t tolerance = TOLERANCE);
uint8_t tolerance = TOLERANCE, uint16_t delta = 0);
match_result_t 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,
Expand Down Expand Up @@ -169,9 +171,10 @@ class IRrecv {
uint16_t nbits = MITSUBISHI_BITS,
bool strict = true);
#endif
#if (DECODE_RC5 || DECODE_R6)
#if (DECODE_RC5 || DECODE_R6 || DECODE_LASERTAG)
int16_t getRClevel(decode_results *results, uint16_t *offset, uint16_t *used,
uint16_t bitTime);
uint16_t bitTime, uint8_t tolerance = TOLERANCE,
int16_t excess = MARK_EXCESS, uint16_t delta = 0);
#endif
#if DECODE_RC5
bool decodeRC5(decode_results *results, uint16_t nbits = RC5X_BITS,
Expand Down Expand Up @@ -257,6 +260,10 @@ class IRrecv {
uint16_t nbits = FUJITSU_AC_BITS,
bool strict = false);
#endif
#if DECODE_LASERTAG
bool decodeLasertag(decode_results *results, uint16_t nbits = LASERTAG_BITS,
bool strict = true);
#endif
};

#endif // IRRECV_H_
8 changes: 7 additions & 1 deletion src/IRremoteESP8266.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@
#define DECODE_MIDEA true
#define SEND_MIDEA true

#define DECODE_LASERTAG true
#define SEND_LASERTAG true

#if (DECODE_ARGO || DECODE_DAIKIN || DECODE_FUJITSU_AC || DECODE_GREE || \
DECODE_KELVINATOR || DECODE_MITSUBISHI_AC || DECODE_TOSHIBA_AC || \
DECODE_TROTEC)
Expand Down Expand Up @@ -197,7 +200,8 @@ enum decode_type_t {
TOSHIBA_AC,
FUJITSU_AC,
MIDEA,
MAGIQUEST
MAGIQUEST,
LASERTAG
};

// Message lengths & required repeat values
Expand Down Expand Up @@ -266,6 +270,8 @@ enum decode_type_t {
#define MAGIQUEST_BITS 56U
#define MIDEA_BITS 48U
#define MIDEA_MIN_REPEAT 0U
#define LASERTAG_BITS 13U
#define LASERTAG_MIN_REPEAT 0U

// Turn on Debugging information by uncommenting the following line.
// #define DEBUG 1
Expand Down
4 changes: 4 additions & 0 deletions src/IRsend.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ class IRsend {
uint16_t repeat = 0);
uint64_t encodeMagiQuest(uint32_t wand_id, uint16_t magnitude);
#endif
#if SEND_LASERTAG
void sendLasertag(uint64_t data, uint16_t nbits = LASERTAG_BITS,
uint16_t repeat = LASERTAG_MIN_REPEAT);
#endif

protected:
#ifdef UNIT_TEST
Expand Down
1 change: 1 addition & 0 deletions src/IRutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ std::string typeToString(const decode_type_t protocol,
case JVC: result = "JVC"; break;
case KELVINATOR: result = "KELVINATOR"; break;
case LG: result = "LG"; break;
case LASERTAG: result = "LASERTAG"; break;
case MAGIQUEST: result = "MAGIQUEST"; break;
case MIDEA: result = "MIDEA"; break;
case MITSUBISHI: result = "MITSUBISHI"; break;
Expand Down
125 changes: 125 additions & 0 deletions src/ir_Lasertag.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright 2017 David Conran

#include <algorithm>
#include "IRrecv.h"
#include "IRsend.h"
#include "IRtimer.h"
#include "IRutils.h"

// LL AAA SSSSS EEEEEEE RRRRRR TTTTTTT AAA GGGG
// LL AAAAA SS EE RR RR TTT AAAAA GG GG
// LL AA AA SSSSS EEEEE RRRRRR TTT AA AA GG
// LL AAAAAAA SS EE RR RR TTT AAAAAAA GG GG
// LLLLLLL AA AA SSSSS EEEEEEE RR RR TTT AA AA GGGGGG

// Constants
#define MIN_LASERTAG_SAMPLES 13U
#define LASERTAG_TICK 333U
#define LASERTAG_MIN_GAP 100000UL // Completely made up amount.
#define LASERTAG_TOLERANCE 0U // Percentage error margin
#define LASERTAG_EXCESS 0U // See MARK_EXCESS
#define LASERTAG_DELTA 150U // Use instead of EXCESS and TOLERANCE.
const int16_t kSPACE = 1;
const int16_t kMARK = 0;

#if SEND_LASERTAG
// Send a Lasertag packet.
// This protocol is pretty much just raw Manchester encoding.
//
// Args:
// data: The message you wish to send.
// nbits: Bit size of the protocol you want to send.
// repeat: Nr. of extra times the data will be sent.
//
// Status: STABLE / Working.
//

void IRsend::sendLasertag(uint64_t data, uint16_t nbits, uint16_t repeat) {
if (nbits > sizeof(data) * 8)
return; // We can't send something that big.

// Set 36kHz IR carrier frequency & a 1/4 (25%) duty cycle.
// NOTE: duty cycle is not confirmed. Just guessing based on RC5/6 protocols.
enableIROut(36, 25);

for (uint16_t i = 0; i <= repeat; i++) {
// Data
for (uint64_t mask = 1ULL << (nbits - 1); mask; mask >>= 1)
if (data & mask) { // 1
space(LASERTAG_TICK); // 1 is space, then mark.
mark(LASERTAG_TICK);
} else { // 0
mark(LASERTAG_TICK); // 0 is mark, then space.
space(LASERTAG_TICK);
}
// Footer
space(LASERTAG_MIN_GAP);
}
}
#endif // SEND_LASERTAG

#if DECODE_LASERTAG
// Decode the supplied Lasertag message.
// This protocol is pretty much just raw Manchester encoding.
//
// Args:
// results: Ptr to the data to decode and where to store the decode result.
// nbits: The number of data bits to expect.
// strict: Flag indicating if we should perform strict matching.
// Returns:
// boolean: True if it can decode it, false if it can't.
//
// Status: BETA / Appears to be working 90% of the time.
//
// Ref:
// http://www.sbprojects.com/knowledge/ir/rc5.php
// https://en.wikipedia.org/wiki/RC-5
// https://en.wikipedia.org/wiki/Manchester_code
bool IRrecv::decodeLasertag(decode_results *results, uint16_t nbits,
bool strict) {
if (results->rawlen < MIN_LASERTAG_SAMPLES) return false;

// Compliance
if (strict && nbits != LASERTAG_BITS) return false;

uint16_t offset = OFFSET_START;
uint16_t used = 0;
uint64_t data = 0;
uint16_t actual_bits = 0;

// No Header

// Data
for (; offset <= results->rawlen; actual_bits++) {
int16_t levelA = getRClevel(results, &offset, &used, LASERTAG_TICK,
LASERTAG_TOLERANCE, LASERTAG_EXCESS,
LASERTAG_DELTA);
int16_t levelB = getRClevel(results, &offset, &used, LASERTAG_TICK,
LASERTAG_TOLERANCE, LASERTAG_EXCESS,
LASERTAG_DELTA);
if (levelA == kSPACE && levelB == kMARK) {
data = (data << 1) | 1; // 1
} else {
if (levelA == kMARK && levelB == kSPACE) {
data <<= 1; // 0
} else {
break;
}
}
}
// Footer (None)

// Compliance
if (actual_bits < nbits) return false; // Less data than we expected.
if (strict && actual_bits != LASERTAG_BITS) return false;

// Success
results->decode_type = LASERTAG;
results->value = data;
results->address = data & 0xF; // Unit
results->command = data >> 4; // Team
results->repeat = false;
results->bits = actual_bits;
return true;
}
#endif // DECODE_LASERTAG
Loading

0 comments on commit 7bd411a

Please sign in to comment.