Skip to content

Commit

Permalink
Initial support of a "Lasertag" toy protocol
Browse files Browse the repository at this point in the history
* sendLasertag() & decodeLasertag() for issue #366
* Tweak getRClevel() to allow custom tolerances and excess levels.
* Unit tests for Lasertag etc.
* Update example code & tools for this protocol.
  • Loading branch information
crankyoldgit committed Dec 5, 2017
1 parent 66b9a9b commit 65a03c0
Show file tree
Hide file tree
Showing 10 changed files with 411 additions and 31 deletions.
1 change: 1 addition & 0 deletions examples/IRMQTTServer/IRMQTTServer.ino
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ void handleRoot() {
"<option value='35'>MagiQuest</option>"
"<option value='34'>Midea</option>"
"<option value='12'>Mitsubishi</option>"
"<option value='35'>Lasertag</option>"
"<option selected='selected' value='3'>NEC</option>" // Default
"<option value='29'>Nikai</option>"
"<option value='5'>Panasonic</option>"
Expand Down
5 changes: 5 additions & 0 deletions src/IRrecv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,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 Down
9 changes: 7 additions & 2 deletions src/IRrecv.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,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);
#endif
#if DECODE_RC5
bool decodeRC5(decode_results *results, uint16_t nbits = RC5X_BITS,
Expand Down Expand Up @@ -252,6 +253,10 @@ class IRrecv {
bool decodeMidea(decode_results *results, uint16_t nbits = MIDEA_BITS,
bool strict = true);
#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 @@ -264,6 +268,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
122 changes: 122 additions & 0 deletions src/ir_Lasertag.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// 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 TOLERANCE // Percentage error margin
#define LASERTAG_EXCESS 20U // See MARK_EXCESS
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: Alpha / Untested.
//

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: So Alpha, it hurts.
//
// 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);
int16_t levelB = getRClevel(results, &offset, &used, LASERTAG_TICK,
LASERTAG_TOLERANCE, LASERTAG_EXCESS);
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
63 changes: 38 additions & 25 deletions src/ir_RC5_RC6.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,8 @@
#define RC6_36_TOGGLE_MASK 0x8000U // (The 16th bit)

// Common (getRClevel())
#define MARK 0U
#define SPACE 1U

const int16_t kMARK = 0;
const int16_t kSPACE = 1;

#if SEND_RC5
// Send a Philips RC-5/RC-5X packet.
Expand Down Expand Up @@ -282,7 +281,7 @@ void IRsend::sendRC6(uint64_t data, uint16_t nbits, uint16_t repeat) {
}
#endif // SEND_RC6

#if (DECODE_RC5 || DECODE_RC6)
#if (DECODE_RC5 || DECODE_RC6 || DECODE_LASERTAG)
// Gets one undecoded level at a time from the raw buffer.
// The RC5/6 decoding is easier if the data is broken into time intervals.
// E.g. if the buffer has MARK for 2 time intervals and SPACE for 1,
Expand All @@ -300,41 +299,55 @@ void IRsend::sendRC6(uint64_t data, uint16_t nbits, uint16_t repeat) {
// Ref:
// https://en.wikipedia.org/wiki/Manchester_code
int16_t IRrecv::getRClevel(decode_results *results, uint16_t *offset,
uint16_t *used, uint16_t bitTime) {
if (*offset >= results->rawlen)
return SPACE; // After end of recorded buffer, assume SPACE.
uint16_t *used, uint16_t bitTime,
uint8_t tolerance, int16_t excess) {
DPRINT("DEBUG: getRClevel: offset = ");
DPRINTLN(uint64ToString(*offset));
if (*offset > results->rawlen) {
DPRINTLN("DEBUG: getRClevel: SPACE, past end of rawbuf");
return kSPACE; // After end of recorded buffer, assume SPACE.
}
uint16_t width = results->rawbuf[*offset];
// If the value of offset is odd, it's a MARK. Even, it's a SPACE.
uint16_t val = ((*offset) % 2) ? MARK : SPACE;
uint16_t val = ((*offset) % 2) ? kMARK : kSPACE;
// Check to see if we have hit an inter-message gap (> 20ms).
if (val == SPACE && width > 20000)
return SPACE;
int16_t correction = (val == MARK) ? MARK_EXCESS : -MARK_EXCESS;
if (val == kSPACE && width > 20000) {
DPRINTLN("DEBUG: getRClevel: SPACE, hit end of mesg gap.");
return kSPACE;
}
int16_t correction = (val == kMARK) ? excess : -excess;

// Calculate the look-ahead for our current position in the buffer.
uint16_t avail;
// Note: We want to match in greedy order as the other way leads to
// mismatches due to overlaps induced by the correction and tolerance
// values.
if (match(width, 3 * bitTime + correction))
if (match(width, 3 * bitTime + correction, tolerance)) {
avail = 3;
else if (match(width, 2 * bitTime + correction))
} else if (match(width, 2 * bitTime + correction, tolerance)) {
avail = 2;
else if (match(width, bitTime + correction))
} else if (match(width, bitTime + correction, tolerance)) {
avail = 1;
else
} else {
DPRINTLN("DEBUG: getRClevel: Unexpected width. Exiting.");
return -1; // The width is not what we expected.
}

(*used)++; // Count another one of the avail slots as used.
if (*used >= avail) { // Are we out of look-ahead/avail slots?
// Yes, so reset the used counter, and move the offset ahead.
*used = 0;
(*offset)++;
}
if (val == kMARK) {
DPRINTLN("DEBUG: getRClevel: MARK");
} else {
DPRINTLN("DEBUG: getRClevel: SPACE");
}

return val;
}
#endif // (DECODE_RC5 || DECODE_RC6)
#endif // (DECODE_RC5 || DECODE_RC6 || DECODE_LASERTAG)

#if DECODE_RC5
// Decode the supplied RC-5/RC5X message.
Expand Down Expand Up @@ -371,14 +384,14 @@ bool IRrecv::decodeRC5(decode_results *results, uint16_t nbits, bool strict) {

// Header
// Get start bit #1.
if (getRClevel(results, &offset, &used, RC5_T1) != MARK) return false;
if (getRClevel(results, &offset, &used, RC5_T1) != kMARK) return false;
// Get field/start bit #2 (inverted bit-7 of the command if RC-5X protocol)
uint16_t actual_bits = 1;
int16_t levelA = getRClevel(results, &offset, &used, RC5_T1);
int16_t levelB = getRClevel(results, &offset, &used, RC5_T1);
if (levelA == SPACE && levelB == MARK) { // Matched a 1.
if (levelA == kSPACE && levelB == kMARK) { // Matched a 1.
is_rc5x = false;
} else if (levelA == MARK && levelB == SPACE) { // Matched a 0.
} else if (levelA == kMARK && levelB == kSPACE) { // Matched a 0.
if (nbits <= RC5_BITS) return false; // Field bit must be '1' for RC5.
is_rc5x = true;
data = 1;
Expand All @@ -390,9 +403,9 @@ bool IRrecv::decodeRC5(decode_results *results, uint16_t nbits, bool strict) {
for (; offset < results->rawlen; actual_bits++) {
int16_t levelA = getRClevel(results, &offset, &used, RC5_T1);
int16_t levelB = getRClevel(results, &offset, &used, RC5_T1);
if (levelA == SPACE && levelB == MARK)
if (levelA == kSPACE && levelB == kMARK)
data = (data << 1) | 1; // 1
else if (levelA == MARK && levelB == SPACE)
else if (levelA == kMARK && levelB == kSPACE)
data <<= 1; // 0
else
break;
Expand Down Expand Up @@ -472,8 +485,8 @@ bool IRrecv::decodeRC6(decode_results *results, uint16_t nbits, bool strict) {
uint16_t used = 0;

// Get the start bit. e.g. 1.
if (getRClevel(results, &offset, &used, tick) != MARK) return false;
if (getRClevel(results, &offset, &used, tick) != SPACE) return false;
if (getRClevel(results, &offset, &used, tick) != kMARK) return false;
if (getRClevel(results, &offset, &used, tick) != kSPACE) return false;

uint16_t actual_bits;
uint64_t data = 0;
Expand All @@ -491,9 +504,9 @@ bool IRrecv::decodeRC6(decode_results *results, uint16_t nbits, bool strict) {
if (actual_bits == 3 &&
levelB != getRClevel(results, &offset, &used, tick))
return false;
if (levelA == MARK && levelB == SPACE) // reversed compared to RC5
if (levelA == kMARK && levelB == kSPACE) // reversed compared to RC5
data = (data << 1) | 1; // 1
else if (levelA == SPACE && levelB == MARK)
else if (levelA == kSPACE && levelB == kMARK)
data <<= 1; // 0
else
break;
Expand Down
14 changes: 12 additions & 2 deletions test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ TESTS = IRutils_test IRsend_test ir_NEC_test ir_GlobalCache_test \
ir_RC5_RC6_test ir_Panasonic_test ir_Dish_test ir_Whynter_test \
ir_Aiwa_test ir_Denon_test ir_Sanyo_test ir_Daikin_test ir_Coolix_test \
ir_Gree_test IRrecv_test ir_Pronto_test ir_Fujitsu_test ir_Nikai_test \
ir_Toshiba_test ir_Midea_test ir_Magiquest_test
ir_Toshiba_test ir_Midea_test ir_Magiquest_test ir_Lasertag_test

# All Google Test headers. Usually you shouldn't change this
# definition.
Expand Down Expand Up @@ -73,7 +73,8 @@ PROTOCOLS = ir_NEC.o ir_Sony.o ir_Samsung.o ir_JVC.o ir_RCMM.o ir_RC5_RC6.o \
ir_LG.o ir_Mitsubishi.o ir_Fujitsu.o ir_Sharp.o ir_Sanyo.o ir_Denon.o ir_Dish.o \
ir_Panasonic.o ir_Whynter.o ir_Coolix.o ir_Aiwa.o ir_Sherwood.o \
ir_Kelvinator.o ir_Daikin.o ir_Gree.o ir_Pronto.o ir_Nikai.o ir_Toshiba.o \
ir_Midea.o ir_Magiquest.o
ir_Midea.o ir_Magiquest.o ir_Lasertag.o

# Common object files
COMMON_OBJ = IRutils.o IRtimer.o IRsend.o IRrecv.o ir_GlobalCache.o \
$(PROTOCOLS) gtest_main.a
Expand Down Expand Up @@ -376,4 +377,13 @@ ir_Magiquest_test.o : ir_Magiquest_test.cpp $(COMMON_TEST_DEPS) $(GTEST_HEADERS)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -I$(USER_DIR) -c ir_Magiquest_test.cpp

ir_Magiquest_test : $(COMMON_OBJ) ir_Magiquest_test.o
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -lpthread $^ -o $@

ir_Lasertag.o : $(USER_DIR)/ir_Lasertag.cpp $(USER_DIR)/ir_RC5_RC6.cpp $(GTEST_HEADERS)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $(USER_DIR)/ir_Lasertag.cpp

ir_Lasertag_test.o : ir_Lasertag_test.cpp $(USER_DIR)/ir_RC5_RC6.cpp $(COMMON_TEST_DEPS) $(GTEST_HEADERS)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -I$(USER_DIR) -c ir_Lasertag_test.cpp

ir_Lasertag_test : $(COMMON_OBJ) ir_Lasertag_test.o ir_RC5_RC6.o
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -lpthread $^ -o $@
Loading

0 comments on commit 65a03c0

Please sign in to comment.