IRremote
ir_OpenLASIR.hpp
Go to the documentation of this file.
1 /*
2  * ir_OpenLASIR.hpp
3  *
4  * Contains functions for receiving and sending the OpenLASIR IR Protocol.
5  * OpenLASIR is a modified NEC protocol with 8-bit validated address (with inverted complement)
6  * and 16-bit command (no error check on command). This is the opposite of NEC Extended,
7  * which has 16-bit address and 8-bit validated command.
8  *
9  * Protocol details:
10  * - Bits 0-7: Address (8 bits, Block ID)
11  * - Bits 8-15: ~Address (8-bit inverted copy, for error checking)
12  * - Bits 16-31: Command (16 bits, no error check)
13  * - Same timing as NEC: 38 kHz carrier, 9ms leading mark, 4.5ms leading space,
14  * 562.5us bit mark, 1687.5us "1" space, 562.5us "0" space, 562.5us stop bit
15  * - LSB first, pulse distance encoding
16  * - Special NEC-style repeat frame (9ms mark + 2.25ms space + stop bit)
17  *
18  * See: https://github.com/danielweidman/OpenLASIR
19  *
20  * This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote.
21  *
22  ************************************************************************************
23  * MIT License
24  *
25  * Copyright (c) 2025-2026 Daniel Weidmann, Armin Joachimsmeyer
26  *
27  * Permission is hereby granted, free of charge, to any person obtaining a copy
28  * of this software and associated documentation files (the "Software"), to deal
29  * in the Software without restriction, including without limitation the rights
30  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
31  * copies of the Software, and to permit persons to whom the Software is furnished
32  * to do so, subject to the following conditions:
33  *
34  * The above copyright notice and this permission notice shall be included in all
35  * copies or substantial portions of the Software.
36  *
37  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
38  * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
39  * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
40  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
41  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
42  * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
43  *
44  ************************************************************************************
45  */
46 #ifndef _IR_OPENLASIR_HPP
47 #define _IR_OPENLASIR_HPP
48 
49 // This block must be located after the includes of other *.hpp files
50 //#define LOCAL_DEBUG // This enables debug output only for this file - only for development
51 #include "LocalDebugLevelStart.h"
52 
56 //==============================================================================
57 // OpenLASIR - Modified NEC Extended Protocol
58 // 8-bit validated address + 16-bit command
59 //==============================================================================
60 /*
61  * OpenLASIR uses the same timing as NEC but rearranges the address/command structure:
62  * - 8-bit address with 8-bit inverted complement (for error checking)
63  * - 16-bit command with no error check
64  * Bits 0-7: Block ID (8 bits) ← Address low byte
65  * Bits 8-15: ~Block ID (8 bits) ← Address high byte (inverted, for error check)
66  * Bits 16-23: Device ID (8 bits) ← Command bits 0-7
67  * Bits 24-28: Mode (5 bits) ← Command bits 8-12
68  * Bits 29-31: Data (color, etc.) (3 bits) ← Command bits 13-15
69  *
70  * This is the opposite of NEC Extended, which uses 16-bit address and 8-bit validated command.
71  *
72  * The timing constants are shared with NEC (defined in ir_NEC.hpp):
73  * NEC_UNIT, NEC_HEADER_MARK, NEC_HEADER_SPACE, NEC_BIT_MARK,
74  * NEC_ONE_SPACE, NEC_ZERO_SPACE, NEC_REPEAT_HEADER_SPACE, etc.
75  *
76  * IRP notation:
77  * {38.0k,564}<1,-1|1,-3>(16,-8,A:8,~A:8,C:16,1,^108m,(16,-4,1,^108m)*)
78  * A = 8-bit address (Block ID)
79  * ~A = inverted address (error check)
80  * C = 16-bit command (Device ID + Mode + Data)
81  */
82 
83 // OpenLASIR address and command bit widths
84 #define OPENLASIR_ADDRESS_BITS 16 // 8 bit address + 8 bit inverted address
85 #define OPENLASIR_COMMAND_BITS 16 // 16 bit command, no error check
86 #define OPENLASIR_BITS (OPENLASIR_ADDRESS_BITS + OPENLASIR_COMMAND_BITS) // 32 bits total
87 
88 /*
89  * Constants for Mode
90  */
91 #define OPENLASIR_MODE_LASER_TAG_FIRE 0 // Laser tag shot. Data = color.
92 #define OPENLASIR_MODE_USER_PRESENCE_ANNOUNCEMENT 1 // User device saying "I'm here." No response expected.
93 #define OPENLASIR_MODE_BASE_STATION_PRESENCE_ANNOUNCEMENT 2 // Fixed base station saying "I'm here."
94 #define OPENLASIR_MODE_USER_TO_USER_HANDSHAKE_INITIATION 3 // User badge initiates handshake with another user badge.
95 #define OPENLASIR_MODE_USER_TO_USER_HANDSHAKE_RESPONSE 4 // Response to a user-to-user handshake initiation.
96 #define OPENLASIR_MODE_USER_TO_BASE_STATION_HANDSHAKE_INITIATION 5 // User badge initiates handshake with a base station.
97 #define OPENLASIR_MODE_USER_TO_BASE_STATION_HANDSHAKE_RESPONSE 6 // Base station responds to a user-initiated handshake.
98 #define OPENLASIR_MODE_BASE_STATION_TO_USER_HANDSHAKE_INITIATION 7 // Base station initiates handshake with a user badge.
99 #define OPENLASIR_MODE_BASE_STATION_TO_USER_HANDSHAKE_RESPONSE 8 // User badge responds to a base-station-initiated handshake.
100 #define OPENLASIR_MODE_COLOR_SET_TEMPORARY 9 // Tell a badge to display a color temporarily.
101 #define OPENLASIR_MODE_COLOR_SET_PERMANENT 10 // Tell a badge to display a color and "remember" it according to some device-specific logic.
102 #define OPENLASIR_MODE_GENERAL_INTERACT 11 // Tell a device to execute a general "interact" action. The specific behavior is defined by the receiver.
103 // modes 12 to 31 are reserved for future use
104 
105 /*
106  * Constants for Data / Color
107  */
108 #define OPENLASIR_COLOR_CYAN 0 // (0, 255, 255)
109 #define OPENLASIR_COLOR_MAGENTA 1 // (255, 0, 255)
110 #define OPENLASIR_COLOR_YELLOW 2 // (255, 255, 0)
111 #define OPENLASIR_COLOR_GREEN 3 // (0, 255, 0)
112 #define OPENLASIR_COLOR_RED 4 // (255, 0, 0)
113 #define OPENLASIR_COLOR_BLUE 5 // (0, 0, 255)
114 #define OPENLASIR_COLOR_ORANGE 6 // (255, 165, 0)
115 #define OPENLASIR_COLOR_WHITE 7 // (255, 255, 255)
116 
117 // Timing is identical to NEC (reuse NEC_* constants from ir_NEC.hpp)
118 
119 /************************************
120  * Start of send and decode functions
121  ************************************/
122 
128  sendNECRepeat();
129 }
130 
137 }
138 
148 uint32_t IRsend::computeOpenLASIRRawDataAndChecksum(uint8_t aAddress, uint16_t aCommand) {
149  LongUnion tRawData;
150 
151  // Address: 8 bit address + 8 bit inverted address for error checking
152  tRawData.UByte.LowByte = aAddress;
153  tRawData.UByte.MidLowByte = ~aAddress;
154 
155  // Command: full 16 bits, no error check
156  tRawData.UWord.HighWord = aCommand;
157 
158  return tRawData.ULong;
159 }
160 
170 uint16_t IRsend::computeOpenLASIRRawCommand(uint8_t aDeviceID, uint8_t aMode, uint8_t aData) {
171  WordUnion tRawCommand;
172 
173  tRawCommand.UByte.LowByte = aDeviceID;
174  tRawCommand.UByte.HighByte = (aMode & 0x1F) | ((aData & 0x07) << 5);
175 
176  return tRawCommand.UWord;
177 }
178 
187 void IRsend::sendOpenLASIR(uint8_t aAddress, uint16_t aCommand, int_fast8_t aNumberOfRepeats) {
190 }
191 
202 void IRsend::sendOpenLASIR(uint8_t aAddress, uint8_t aDeviceID, uint8_t aMode, uint8_t aData, int_fast8_t aNumberOfRepeats) {
203  sendPulseDistanceWidth_P(&NECProtocolConstants,
204  computeOpenLASIRRawDataAndChecksum(aAddress, computeOpenLASIRRawCommand(aDeviceID, aMode, aData)),
206 }
207 
213 void IRsend::sendOpenLASIRRaw(uint32_t aRawData, int_fast8_t aNumberOfRepeats) {
214  sendPulseDistanceWidth_P(&NECProtocolConstants, aRawData, OPENLASIR_BITS, aNumberOfRepeats);
215 }
216 
230  /*
231  * First check for right data length
232  * Next check start bit
233  * Next try the decode
234  */
235 
236  // Check we have the right amount of data (68). The +4 is for initial gap, start bit mark and space + stop bit mark.
237  if (decodedIRData.rawlen != ((2 * OPENLASIR_BITS) + 4) && (decodedIRData.rawlen != 4)) {
238  DEBUG_PRINT(F("OpenLASIR: Data length="));
240  DEBUG_PRINTLN(F(" is not 68 or 4"));
241  return false;
242  }
243 
244  // Check header "mark" - this must be done for repeat and data
246  return false;
247  }
248 
249  // Check for repeat - here we have another header space length
250  if (decodedIRData.rawlen == 4) {
251  // Only claim this repeat if the last decoded protocol was OpenLASIR
258  return true;
259  }
260  return false;
261  }
262 
263  // Check command header space
265  DEBUG_PRINTLN(F("OpenLASIR: Header space length is wrong"));
266  return false;
267  }
268 
269  // Decode the pulse distance data using NEC timing
270  decodePulseDistanceWidthData_P(&NECProtocolConstants, OPENLASIR_BITS);
271 
272  // Success - now interpret the 32 raw bits
273  LongUnion tValue;
275 
276  // Validate address: second byte must be inverted first byte
277  if (tValue.UByte.LowByte != (uint8_t)(~tValue.UByte.MidLowByte)) {
278  // Address validation failed - this is not a valid OpenLASIR frame
279  DEBUG_PRINTLN(F("OpenLASIR: Address validation failed"));
280  return false;
281  }
282 
283  /*
284  * Disambiguation with standard NEC:
285  * If the command also has a valid inverted pattern (upper 8 bits == ~lower 8 bits),
286  * then this looks like a standard NEC frame, not OpenLASIR.
287  * By design, OpenLASIR uses 16-bit commands that should NOT have this property,
288  * so we let the NEC decoder handle these frames instead.
289  * (See OpenLASIR spec: "This is to prevent OpenLASIR packets from causing issues
290  * with NEC devices except in very rare cases of collisions.")
291  */
292  if (tValue.UByte.MidHighByte == (uint8_t)(~tValue.UByte.HighByte)) {
293  DEBUG_PRINTLN(F("OpenLASIR: Command has valid inverse - looks like standard NEC, skipping"));
294  return false;
295  }
296 
297  // Valid OpenLASIR frame
299  decodedIRData.address = tValue.UByte.LowByte; // 8-bit validated address (Block ID)
300  decodedIRData.command = tValue.UWord.HighWord; // 16-bit command (Device ID + Mode + Data)
302 
303  // Check for repeat (same frame sent again within repeat window)
305 
306  return true;
307 }
308 
310 #include "LocalDebugLevelEnd.h"
311 
312 #endif // _IR_OPENLASIR_HPP
IRData::address
uint16_t address
Decoded address, Distance protocol (tMarkTicksLong (if tMarkTicksLong == 0, then tMarkTicksShort) << ...
Definition: IRremoteInt.h:164
MICROS_PER_TICK
#define MICROS_PER_TICK
microseconds per clock interrupt tick
Definition: IRremote.hpp:133
LongUnion
Union to specify parts / manifestations of a 32 bit Long without casts and shifts.
Definition: LongUnion.h:59
IRrecv::lastDecodedProtocol
decode_type_t lastDecodedProtocol
Definition: IRremoteInt.h:404
IRData::numberOfBits
uint16_t numberOfBits
Number of bits received for data (address + command + parity) - to determine protocol length if diffe...
Definition: IRremoteInt.h:173
DEBUG_PRINT
#define DEBUG_PRINT(...)
Definition: LocalDebugLevelStart.h:79
IRsend::aNumberOfRepeats
void int_fast8_t aNumberOfRepeats
Definition: IRremoteInt.h:528
WordUnion
Union to specify parts / manifestations of a 16 bit Word without casts and shifts.
Definition: LongUnion.h:36
IRrecv::decodePulseDistanceWidthData_P
void decodePulseDistanceWidthData_P(PulseDistanceWidthProtocolConstants const *aProtocolConstantsPGM, uint_fast8_t aNumberOfBits, IRRawlenType aStartOffset=3)
Definition: IRReceive.hpp:1112
WordUnion::UByte
struct WordUnion::@2 UByte
WordUnion::HighByte
uint8_t HighByte
Definition: LongUnion.h:39
LongUnion::UByte
struct LongUnion::@4 UByte
NEC_BIT_MARK
#define NEC_BIT_MARK
Definition: ir_NEC.hpp:101
OPENLASIR_BITS
#define OPENLASIR_BITS
Definition: ir_OpenLASIR.hpp:86
IRDATA_FLAGS_IS_REPEAT
#define IRDATA_FLAGS_IS_REPEAT
The gap between the preceding frame is as smaller than the maximum gap expected for a repeat....
Definition: IRProtocol.h:125
IRsend::sendOpenLASIRRaw
void sendOpenLASIRRaw(uint32_t aRawData, int_fast8_t aNumberOfRepeats=NO_REPEATS)
Send raw 32-bit OpenLASIR data.
Definition: ir_OpenLASIR.hpp:213
LongUnion::LowByte
uint8_t LowByte
Definition: LongUnion.h:61
LongUnion::HighByte
uint8_t HighByte
Definition: LongUnion.h:64
IRsend::computeOpenLASIRRawDataAndChecksum
uint32_t computeOpenLASIRRawDataAndChecksum(uint8_t aAddress, uint16_t aCommand)
Compute the raw 32-bit data for an OpenLASIR frame from 8-bit address and 16-bit command.
Definition: ir_OpenLASIR.hpp:148
IRrecv::checkForRepeatSpaceTicksAndSetFlag
void checkForRepeatSpaceTicksAndSetFlag(uint16_t aMaximumRepeatSpaceTicks)
Definition: IRReceive.hpp:1248
matchSpace
bool matchSpace(uint16_t aMeasuredTicks, uint16_t aMatchValueMicros)
Compensate for spaces shortened by demodulator hardware.
Definition: IRReceive.hpp:1375
IRsend::computeOpenLASIRRawCommand
uint16_t computeOpenLASIRRawCommand(uint8_t aDeviceID, uint8_t aMode, uint8_t aData)
Compute the raw 32-bit data for an OpenLASIR frame from 8-bit address, 8-bit DeviceID,...
Definition: ir_OpenLASIR.hpp:170
LocalDebugLevelStart.h
LongUnion::MidLowByte
uint8_t MidLowByte
Definition: LongUnion.h:62
IRsend::sendPulseDistanceWidth_P
void sendPulseDistanceWidth_P(PulseDistanceWidthProtocolConstants const *aProtocolConstantsPGM, IRDecodedRawDataType aData, uint_fast8_t aNumberOfBits, int_fast8_t aNumberOfRepeats)
Definition: IRSend.hpp:1158
LongUnion::HighWord
uint16_t HighWord
Definition: LongUnion.h:81
IRrecv::decodedIRData
IRData decodedIRData
Definition: IRremoteInt.h:401
IRData::flags
uint8_t flags
IRDATA_FLAGS_IS_REPEAT, IRDATA_FLAGS_WAS_OVERFLOW etc. See IRDATA_FLAGS_* definitions above.
Definition: IRremoteInt.h:174
IRData::command
uint16_t command
Decoded command, Distance protocol (tMarkTicksShort << 8) | tSpaceTicksShort.
Definition: IRremoteInt.h:165
IRsend::sendOpenLASIRRepeat
void sendOpenLASIRRepeat()
Send special OpenLASIR repeat frame (same as NEC repeat frame).
Definition: ir_OpenLASIR.hpp:127
IRData::decodedRawData
IRDecodedRawDataType decodedRawData
Up to 32/64 bit decoded raw data, to be used for send<protocol>Raw functions.
Definition: IRremoteInt.h:167
OPENLASIR
@ OPENLASIR
Definition: IRProtocol.h:104
matchMark
bool matchMark(uint16_t aMeasuredTicks, uint16_t aMatchValueMicros)
Compensate for marks exceeded by demodulator hardware.
Definition: IRReceive.hpp:1327
irparams_struct::rawbuf
IRRawbufType rawbuf[RAW_BUFFER_LENGTH]
raw data / tick counts per mark/space. With 8 bit we can only store up to 12.7 ms....
Definition: IRremoteInt.h:147
LongUnion::ULong
uint32_t ULong
Definition: LongUnion.h:95
IRrecv::irparams
irparams_struct irparams
Definition: IRremoteInt.h:400
IRrecv::lastDecodedCommand
uint16_t lastDecodedCommand
Definition: IRremoteInt.h:406
DEBUG_PRINTLN
#define DEBUG_PRINTLN(...)
Definition: LocalDebugLevelStart.h:80
IRDATA_FLAGS_IS_LSB_FIRST
#define IRDATA_FLAGS_IS_LSB_FIRST
Definition: IRProtocol.h:134
NEC_HEADER_SPACE
#define NEC_HEADER_SPACE
Definition: ir_NEC.hpp:99
NEC_REPEAT_HEADER_SPACE
#define NEC_REPEAT_HEADER_SPACE
Definition: ir_NEC.hpp:105
sendOpenLASIRSpecialRepeat
void sendOpenLASIRSpecialRepeat()
Static function variant of IRsend::sendOpenLASIRRepeat For use in ProtocolConstants.
Definition: ir_OpenLASIR.hpp:135
IRData::rawlen
IRRawlenType rawlen
Counter of entries in rawbuf of last received frame.
Definition: IRremoteInt.h:182
LongUnion::MidHighByte
uint8_t MidHighByte
Definition: LongUnion.h:63
IRrecv::decodeOpenLASIR
bool decodeOpenLASIR()
Decode an OpenLASIR frame.
Definition: ir_OpenLASIR.hpp:229
IRsend::sendNECRepeat
void sendNECRepeat()
Send special NEC repeat frame Repeat commands should be sent in a 110 ms raster.
Definition: ir_NEC.hpp:132
LongUnion::UWord
struct LongUnion::@6 UWord
WordUnion::LowByte
uint8_t LowByte
Definition: LongUnion.h:38
NEC_HEADER_MARK
#define NEC_HEADER_MARK
Definition: ir_NEC.hpp:98
WordUnion::UWord
uint16_t UWord
Definition: LongUnion.h:47
IRData::protocol
decode_type_t protocol
UNKNOWN, NEC, SONY, RC5, PULSE_DISTANCE, ...
Definition: IRremoteInt.h:163
IRsend::sendOpenLASIR
void sendOpenLASIR(uint8_t aAddress, uint16_t aCommand, int_fast8_t aNumberOfRepeats)
Send an OpenLASIR frame with special NEC-style repeats.
Definition: ir_OpenLASIR.hpp:187
IRrecv::lastDecodedAddress
uint16_t lastDecodedAddress
Definition: IRremoteInt.h:405
IRsend::aCommand
void aCommand
Definition: IRremoteInt.h:617
LocalDebugLevelEnd.h
sendNECSpecialRepeat
void sendNECSpecialRepeat()
Static function variant of IRsend::sendNECRepeat For use in ProtocolConstants.
Definition: ir_NEC.hpp:143
NEC_MAXIMUM_REPEAT_DISTANCE
#define NEC_MAXIMUM_REPEAT_DISTANCE
Definition: ir_NEC.hpp:112