Open Lasertag And Stuff InfraRed protocol (OpenLASIR)

June 11, 2026 · View on GitHub

OpenLASIR is an communication standard for DEFCON-style electronic badges. If anyone actually adopts it, it might allow badge creators to make devices compatible with one another. It defines packet types for a laser tag game, user and base station presence announcement, handshakes between users and base stations, and color setting commands.

The first-party Laser* Tag Badge DS for 2026 (and 2025 badges via a firmware update) will support OpenLASIR (as well as a separate proprietary protocol) for the lasertag-related features. If there is interest from other creators, it might also be updated to support the other functionalities described here. OpenLASIR is also supported on the offical DEF CON Singapore 1 conference badge!


The IR Layer (NEC-inspired for compatibility)

You can skip this part if you just want to build with Arduino or MicroPython; see Example Code further down

Each OpenLASIR packet is a 32-bit frame sent over a 38 kHz IR carrier. The timing and carrier are borrowed from the NEC IR protocol, so existing NEC-capable hardware and libraries can send and receive OpenLASIR frames. Apart from that timing and carrier, OpenLASIR defines its own packet structure.

Packet Structure

  Bits  0-7:   Block ID              (8 bits)
  Bits  8-15:  ~Block ID             (8 bits)  ← inverted copy of Block ID, used as an error check
  Bits 16-23:  Device ID             (8 bits)
  Bits 24-28:  Mode                  (5 bits)
  Bits 29-31:  Data (color, etc.)    (3 bits)

In plain terms:

  • The first byte is the Block ID.
  • The second byte is an inverted copy of the Block ID, used only as an error check.
  • The third byte is the Device ID.
  • The fourth byte holds the Mode (5 bits) and the Data (3 bits, e.g. color).

The included Python sample code groups these fields into an "address" and a "command" for compatibility with NEC libraries: the "address" is the Block ID (with its inverted error-check byte), and the "command" holds the Device ID, Mode, and Data.

Relationship to NEC (optional)

You only need this section if you're mapping OpenLASIR onto an existing NEC library. The only things OpenLASIR shares with NEC are the carrier frequency, bit timing, and packet size:

FeatureStandard NECNEC ExtendedOpenLASIR
Address bits8168
Address error check8-bit inverted copyNone (uses both bytes for address)8-bit inverted copy
Command bits8816
Command error check8-bit inverted copy8-bit inverted copyNone
Total payload8 addr + 8 cmd = 16 useful bits16 addr + 8 cmd = 24 useful bits8 addr + 16 cmd = 24 useful bits
Carrier frequency38 kHz38 kHz38 kHz
Leading mark9 ms9 ms9 ms
Leading space4.5 ms4.5 ms4.5 ms
Bit mark562.5 us562.5 us562.5 us
"1" space1.6875 ms1.6875 ms1.6875 ms
"0" space562.5 us562.5 us562.5 us
Stop bit562.5 us mark562.5 us mark562.5 us mark

Standard NEC puts an error-check byte on the command; NEC Extended drops the address error check to get 16 address bits. OpenLASIR keeps the inverted Block ID byte as an error check (like the NEC address byte) but uses the remaining two bytes freely for Device ID, Mode, and Data. Keeping that inverted Block ID byte is what lets OpenLASIR avoid interfering with NEC devices (except in very rare collisions).

Packet Types

This table defines specific packet types/modes that are defined by the standard.

A given device does NOT need to support all packet types in order to be considered compliant with this standard.

Mode #NameDescription
0laser_tag_fireLaser tag shot. Data = color.
1user_presence_announcementUser device saying "I'm here." No response expected.
2base_station_presence_announcementFixed base station saying "I'm here."
3user_to_user_handshake_initiationUser badge initiates handshake with another user badge.
4user_to_user_handshake_responseResponse to a user-to-user handshake initiation.
5user_to_base_station_handshake_initiationUser badge initiates handshake with a base station.
6user_to_base_station_handshake_responseBase station responds to a user-initiated handshake.
7base_station_to_user_handshake_initiationBase station initiates handshake with a user badge.
8base_station_to_user_handshake_responseUser badge responds to a base-station-initiated handshake.
9color_set_temporaryTell a badge to display a color temporarily.
10color_set_permanentTell a badge to display a color and "remember" it according to some device-specific logic.
11general_interactTell a device to execute a general "interact" action. The specific behavior is defined by the receiver.
12-31(reserved)Reserved for future use. Let me know if you have some ideas that would fit with the rest of the packet structure.

Colors

The 3-bit data field encodes one of 8 colors (used directly for laser_tag_fire, color_set_temporary, and color_set_permanent; meaning may vary for other modes):

ValueColorRGB
0Cyan(0, 255, 255)
1Magenta(255, 0, 255)
2Yellow(255, 255, 0)
3Green(0, 255, 0)
4Red(255, 0, 0)
5Blue(0, 0, 255)
6Orange(255, 165, 0)
7White(255, 255, 255)

Packet Types in Detail

laser_tag_fire (Mode 0)

Indicates firing a laser tag shot.

The data field carries the shooter's color, so the tagged badge knows what color hit it (so it can display that color momentarily).

The first-party Laser* Tag Badge will support this protocol. If you just want to make your device compatible with Laser* Tag Badge, this is the only packet type you need to support.

Presence Announcements (Modes 1-2)

Simple "I exist" broadcasts. No response is expected.

  • user_presence_announcement (Mode 1): A badge saying hi. Example use case: track which devices you've been near, trigger animations in an environment when a user with badge approaches, etc.
  • base_station_presence_announcement (Mode 2): A fixed transmitter announcing itself. Example use case: badges react when entering a zone.

The three data bits can be used to relay some information about the badge or base station's state.

Handshake Packets (Modes 3-8)

These come in initiation/response pairs. One device sends an initiation, and any device that receives it (and is in a ready state) replies with the corresponding response. This way both devices have confirmation they "saw" each other.

InitiationResponseUse Case
user_to_user_handshake_initiation (3)user_to_user_handshake_response (4)Example use case: game where you try to meet and talk to as many other people with compatible badge as possible.
user_to_base_station_handshake_initiation (5)user_to_base_station_handshake_response (6)Badge checks in at a base station. Both sides can log it.
base_station_to_user_handshake_initiation (7)base_station_to_user_handshake_response (8)Same as above, but initiated by base station.

The three data bits can be used to relay some information about the badge or base station's state.

Color Set Packets (Modes 9-10)

Tell another badge to change its LED color.

  • color_set_temporary (Mode 9): Request a device to display a color for a short period.
  • color_set_permanent (Mode 10): Request a device to display a color for a longer period/until otherwise ordered.

general_interact (Mode 11)

A general-purpose "interact with this device" packet. It tells the receiver to do so "something", like how an "interact with object" button in a video game. What something means depends entirely on the receiving device. This is intentionally open-ended.

The 3-bit data field is available for device-defined use (e.g. to select between different interaction types on the same device).

Must be human-triggered (e.g. a button press).


Base Stations

Some of the packet types refer to the concept of a "base station". This can be any sort of fixed beacon or device that is not intended to belong to an end user. One potential embodiment is a series of beacons placed in different physical locations that announce their presence so that individual badges can react. Or, through the handshake message types, individual badges could interact.

If multiple projects that use base stations as location markers are set up, we may try to produce a public mapping of base station device ID to name/description, so projects can share base stations.

Rules and Rate Limits

In order to be compliant with OpenLASIR, please respect these rules and rate limits. IR transmissions are slow and all (for this protocol at least) done at the same 940nm wavelength, so it is easy for devices to interfere with each other if they are constantly transmitting.

Laser Tag Rules

  1. Request a Block/Device ID before using one (or use one of the "free use" IDs -- see the allocation table below).
  2. Normal transmit power (single standard IR LED, under 1.5W): Fire no more than once per second, and no more than 30 times per minute. Fire must be triggered by a human in some form (e.g. pushing a button).
  3. High transmit power (high-power COB LED, multiple LEDs, etc.): Fire no more than once per minute, and no more than 15 times per hour. Fire must still be triggered by a human in some form (e.g. pushing a button).
  4. One-off transmit-only devices are allowed.
  5. Taggable devices when tagged:, a device should:
    • Indicate the color of the shot that hit it (light up, flash, etc.)
    • Disable itself for 2 at least seconds before being able to fire again

Transmit Rate Limits

Packet TypeTriggerNormal Power LimitHigh Power Limit
laser_tag_fireManual1/sec, 30/min1/min, 15/hr
user_presence_announcementManual or automatic10/min3/min
base_station_presence_announcementManual or automatic10/min3/min
color_set_temporaryManual or automatic10/min3/min
base_station_to_user_handshake_initiationManual or automatic10/min3/min
user_to_user_handshake_initiationManual1/sec, 30/min3/min
user_to_base_station_handshake_initiationManual1/sec, 30/min3/min
general_interactManual1/sec, 30/min3/min
*_response packetsIn response to initiation only----

Response packets (user_to_user_handshake_response, user_to_base_station_handshake_response, base_station_to_user_handshake_response) should only be sent in direct response to receiving the corresponding initiation packet.


Block IDs and Device IDs

Every OpenLASIR packet contains a Block ID (8 bits) and a Device ID (8 bits).

  • Block ID identifies the project, creator, or badge type. There are also some blocks shared between different projects, and one "free-for-all" block.
  • Device ID identifies a specific device within that block.

A single Block ID provides 256 Device IDs (0-255). Creators producing multiple badges should request a Block ID and assign each device a unique Device ID within it. Creators a small number of devices may instead be assigned individual Device IDs within a shared block.

How to Get a Block ID or Device ID

To request a Block ID or Device ID:

  1. Open a GitHub Issue on this repository with a description of your project and the number of devices you plan to produce, OR
  2. Post in the Laser* Tag Badge Discord at dani.pink/discord

Allocation Table

Assigned Block IDs and Device IDs are tracked in the table below. "Free Use" blocks may be used without requesting an assignment; note that IDs within these blocks are shared and may collide with other users.

Block IDBlock NameDevices
0Free-for-all!Antisocial or mysterious anonymous people can use any device ID in this block without reservation, but devices won't be properly identified and laser tag scoring won't count for leaderboard
1One-Offs0: TamaBadge
1: irBot
2: Array BlastIR
2One-Offsto be assigned
3One-Offsto be assigned
4One-Offsto be assigned
5One-Offsto be assigned
6-31Laser* Tag Badge (Official)various
32Shared / Assigned IR Filesvarious
33Shared / Misc Toolsto be assigned
34Shared / Misc Base Stationsto be assigned
35-64DEF CON Singapore Official Badge--
65OTS Crew Badge--
66-255(Unassigned)--

As assignments are made, the table above will be updated to project creators can appropriately attribute individual IR interactions to specific projects/devices.


Potential scenarios

"I want to make by own one-off high power laser blaster/cannon/grenade!"

  • Request a Device ID via GitHub Issue or Discord, so you will show up on the leaderboard (strongly recommended). Otherise you may use the free-for-all block.
  • Implement support for the "laser_tag_fire" packet type.
  • Follow the rate limits appropriate to your transmit power level.

"I am making and distributing my own badge and I want it to be able to play laser tag"

  • Request a Block ID (or multiple for >256 devices). Assign each individual badge a unique Device ID within the block(s).
  • You do not need to register each individual Device ID if you are issued your own Block ID.
  • Implement support for the "laser_tag_fire" packet type.
  • Follow the rate limits appropriate to your transmit power level.

Other use cases:

  • Describe your project and request a Block ID or Device ID as appropriate.
  • Implement support for whichever packet types are relevant.
  • Modes 11-31 are reserved for future expansion; suggestions are welcome.

Code Examples

MicroPython

Encoding and Decoding Packets

The micropython/openlasir_utils.py module handles packet encoding and decoding, separate from the actual reception and tranmission (it can convert between address+command and packet type/meaning/device identification/data)

Encoding a laser tag fire packet:

from openlasir_utils import encode_laser_tag_fire_packet

address, command = encode_laser_tag_fire_packet(block_id=0, device_id=42, color="Red")
print(f"Address: {address} (0x{address:02X})")
print(f"Command: {command} (0x{command:04X})")
# Address: 0 (0x00)
# Command: 32810 (0x802A)
#   Device ID 42 = 0x2A in bits 0-7
#   Mode 0 (laser_tag_fire) in bits 8-12
#   Color 4 (Red) = 0b100 in bits 13-15

Decoding a laser tag fire packet:

from openlasir_utils import decode_packet_laser_tag_fire

block_id, device_id, color_name, color_rgb = decode_packet_laser_tag_fire(address, command)
print(f"Block ID: {block_id}, Device ID: {device_id}")
print(f"Color: {color_name} {color_rgb}")
# Block ID: 0, Device ID: 42
# Color: Red (255, 0, 0)

Encoding and decoding a general packet (any mode):

from openlasir_utils import encode_general_packet, decode_packet_general

# Encode a user presence announcement with data=2 (Yellow)
address, command = encode_general_packet(
    block_id=34, device_id=7,
    mode="user_presence_announcement", data="Yellow"
)

# Decode
block_id, device_id, mode_name, data = decode_packet_general(address, command)
print(f"Block: {block_id}, Device: {device_id}, Mode: {mode_name}, Data: {data}")
# Block: 34, Device: 7, Mode: user_presence_announcement, Data: 2

Handshake initiation and response:

from openlasir_utils import encode_general_packet, decode_packet_general

# Device A sends a handshake initiation
address, command = encode_general_packet(
    block_id=6, device_id=100,
    mode="user_to_user_handshake_initiation", data=0
)

# Device B receives and decodes it
block_id, device_id, mode_name, data = decode_packet_general(address, command)
print(f"Received {mode_name} from block {block_id}, device {device_id}")
# Received user_to_user_handshake_initiation from block 6, device 100

# Device B sends the corresponding response
response_addr, response_cmd = encode_general_packet(
    block_id=34, device_id=5,
    mode="user_to_user_handshake_response", data=0
)

Color lookup utilities:

from openlasir_utils import COLOR_NUM_TO_NAME, COLOR_NUM_TO_RGB, COLOR_NAME_TO_NUM

COLOR_NAME_TO_NUM  # {'Cyan': 0, 'Magenta': 1, 'Yellow': 2, 'Green': 3, 'Red': 4, 'Blue': 5, 'Orange': 6, 'White': 7}
COLOR_NUM_TO_RGB[4]   # (255, 0, 0)
COLOR_NUM_TO_NAME[6]  # 'Orange'

Using the IR Transmitter and Receiver (MicroPython)

On a MicroPython board (ESP32, RP2040, Pyboard, etc.), the ir_tx and ir_rx modules handle physical IR transmission and reception. These are based on micropython_ir by Peter Hinch, with new classes created for OpenLASIR.

Transmitter setup and usage:

from machine import Pin
from ir_tx.openlasir import OpenLASIR as OpenLASIR_TX
from openlasir_utils import encode_laser_tag_fire_packet

ir_transmitter = OpenLASIR_TX(Pin(27, Pin.OUT), 38000)

address, command = encode_laser_tag_fire_packet(block_id=0, device_id=1, color="Red")
ir_transmitter.transmit(address, command)

while ir_transmitter.busy():
    pass # Or sleep, or skip this loop if you want to execute other logic while transmitting is in progress via PIO

Receiver setup and usage:

from machine import Pin
from ir_rx.openlasir import OpenLASIR as OpenLASIR_RX
from openlasir_utils import decode_packet_general, decode_packet_laser_tag_fire

def on_receive(data, addr, ctrl):
    block_id, device_id, mode_name, extra = decode_packet_general(addr, data)
    print(f"Received: block={block_id}, device={device_id}, mode={mode_name}, data={extra}")

    if mode_name == "laser_tag_fire":
        _, _, color_name, color_rgb = decode_packet_laser_tag_fire(addr, data)
        print(f"  Tagged with color: {color_name} {color_rgb}")

# The receiver uses pin interrupts; no polling required
ir_listener = OpenLASIR_RX(Pin(12, Pin.IN), on_receive)

Transmit/receive on the same device: When both the transmitter and receiver are on the same device, close the receiver before transmitting to prevent it from picking up the outgoing signal:

import utime

ir_listener.close()

ir_transmitter.transmit(address, command)
while ir_transmitter.busy():
    utime.sleep(0.01)

# Re-initialize after transmission
ir_listener = OpenLASIR_RX(Pin(12, Pin.IN), on_receive)

Full Laser Tag Game Example (MicroPython)

The examples/micropython/lasertag/main.py file contains a complete minimal laser tag implementation, including:

  • Button-triggered firing with rate limiting (1 shot/second)
  • Hit detection with attacker color display
  • 2-second disable period after being tagged
  • Receiver close/reopen around transmissions

Hardware initialization:

from ir_rx.openlasir import OpenLASIR as OpenLASIR_RX
from ir_tx.openlasir import OpenLASIR as OpenLASIR_TX
from machine import Pin

ir_listener = OpenLASIR_RX(Pin(12, Pin.IN), callback_upon_being_tagged)
ir_transmitter = OpenLASIR_TX(Pin(27, Pin.OUT), 38000)
fire_button = Pin(0, Pin.IN, Pin.PULL_UP)

To run the example:

  1. Copy openlasir_utils.py, ir_rx/, and ir_tx/ to your MicroPython board.
  2. Set the pin numbers in main.py to match your hardware.
  3. Set MY_BLOCK_ID, MY_DEVICE_ID, and MY_COLOR.
  4. Run on two or more boards.

Arduino / C++

Arduino support uses Arduino-IRremote with OpenLASIR protocol support, plus a header-only utility library (arduino/OpenLASIR_Utils.h) that mirrors the MicroPython openlasir_utils.py module.

Setup

  1. Install the Arduino-IRremote library (see NOTE above).
  2. Include OpenLASIR_Utils.h from the arduino/ folder in this repository (copy it into your project or Arduino libraries folder).

Encoding and Decoding Packets

The OpenLASIR_Utils.h header provides encoding, decoding, and lookup functions.

Encoding a laser tag fire packet:

#include "OpenLASIR_Utils.h"

uint8_t  address;
uint16_t command;
OpenLASIR_encodeLaserTagFire(0, 42, OPENLASIR_COLOR_RED, address, command);
// address = 0x00 (Block ID 0)
// command = 0x802A
//   Device ID 42 = 0x2A in bits 0-7
//   Mode 0 (laser_tag_fire) in bits 8-12
//   Color 4 (Red) = 0b100 in bits 13-15

Decoding a laser tag fire packet:

OpenLASIR_Packet pkt;
if (OpenLASIR_decodeLaserTagFire(address, command, pkt)) {
    Serial.print("Block ID: ");  Serial.println(pkt.blockId);
    Serial.print("Device ID: "); Serial.println(pkt.deviceId);
    Serial.print("Color: ");     Serial.println(OpenLASIR_getColorName(pkt.data));
    // Block ID: 0, Device ID: 42, Color: Red
}

Encoding and decoding a general packet (any mode):

uint8_t  address;
uint16_t command;

// Encode a user presence announcement with data=2 (Yellow)
OpenLASIR_encodeGeneralPacket(34, 7,
    OPENLASIR_MODE_USER_PRESENCE_ANNOUNCEMENT, OPENLASIR_COLOR_YELLOW,
    address, command);

// Decode
OpenLASIR_Packet pkt = OpenLASIR_decodeGeneralPacket(address, command);
Serial.print("Block: ");   Serial.print(pkt.blockId);
Serial.print(" Device: ");  Serial.print(pkt.deviceId);
Serial.print(" Mode: ");    Serial.print(OpenLASIR_getModeName(pkt.mode));
Serial.print(" Data: ");    Serial.println(pkt.data);
// Block: 34, Device: 7, Mode: user_presence_announcement, Data: 2

Color lookup utilities:

OpenLASIR_getColorName(4);          // "Red"
OpenLASIR_getModeName(0);           // "laser_tag_fire"

uint8_t r, g, b;
OpenLASIR_getColorRGB(4, r, g, b);  // r=255, g=0, b=0

Using the IR Transmitter and Receiver (Arduino)

IR transmission and reception uses the Arduino-IRremote library. Enable the OpenLASIR decoder by defining DECODE_OPENLASIR before including the library.

Sending a laser tag fire packet:

#define DECODE_OPENLASIR
#include <IRremote.hpp>
#include "OpenLASIR_Utils.h"

uint8_t  address;
uint16_t command;
OpenLASIR_encodeLaserTagFire(0, 1, OPENLASIR_COLOR_RED, address, command);

IrSender.sendOpenLASIR(address, command, 0);  // 0 repeats

Receiving and decoding:

#define DECODE_OPENLASIR
#include <IRremote.hpp>
#include "OpenLASIR_Utils.h"

void setup() {
    Serial.begin(115200);
    IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK);
}

void loop() {
    if (IrReceiver.decode()) {
        if (IrReceiver.decodedIRData.protocol == OPENLASIR) {
            OpenLASIR_Packet pkt = OpenLASIR_decodeGeneralPacket(
                IrReceiver.decodedIRData.address,
                IrReceiver.decodedIRData.command);

            Serial.print("Block: ");  Serial.print(pkt.blockId);
            Serial.print(" Device: "); Serial.print(pkt.deviceId);
            Serial.print(" Mode: ");   Serial.println(OpenLASIR_getModeName(pkt.mode));

            if (pkt.mode == OPENLASIR_MODE_LASER_TAG_FIRE) {
                Serial.print("  Tagged with color: ");
                Serial.println(OpenLASIR_getColorName(pkt.data));
            }
        }
        IrReceiver.resume();
    }
}

Transmit/receive on the same device: Stop the receiver before transmitting to prevent it from picking up the outgoing signal, then restart it:

IrReceiver.stop();
IrSender.sendOpenLASIR(address, command, 0);
IrReceiver.start();  // Must use start(), not restartAfterSend()

Full Laser Tag Game Example (Arduino)

The examples/arduino/lasertag/lasertag.ino file contains a complete minimal laser tag implementation for Arduino, including:

  • Button-triggered firing with rate limiting (1 shot/second)
  • Hit detection with attacker color display
  • 2-second disable period after being tagged
  • Receiver stop/start around transmissions
  • Platform-aware pin defaults for ESP32, RP2040, and AVR

To run the example:

  1. Install the Arduino-IRremote library.
  2. Open examples/arduino/lasertag/lasertag.ino in the Arduino IDE.
  3. Set the pin numbers to match your hardware.
  4. Set MY_BLOCK_ID, MY_DEVICE_ID, and MY_COLOR.
  5. Upload to two or more boards and play!

Repository Structure

OpenLASIR/
├── README.md                       # This document
├── micropython/
│   ├── openlasir_utils.py          # Packet encoding/decoding (MicroPython)
│   ├── ir_tx/
│   │   ├── __init__.py             # IR transmitter base class (from micropython_ir)
│   │   └── openlasir.py            # OpenLASIR transmitter implementation
│   └── ir_rx/
│       ├── __init__.py             # IR receiver base class (from micropython_ir)
│       └── openlasir.py            # OpenLASIR receiver/decoder implementation
├── arduino/
│   └── OpenLASIR_Utils.h           # Packet encoding/decoding (Arduino/C++)
└── examples/
    ├── micropython/
    │   └── lasertag/
    │       └── main.py             # Minimal laser tag game (MicroPython)
    └── arduino/
        └── lasertag/
            └── lasertag.ino        # Minimal laser tag game (Arduino)

Credits and License

Protocol design and openlasir_utils.py: Dani Weidman (github.com/danielweidman, dani.pink)

MicoPython IR transmitter and receiver modules (ir_tx/, ir_rx/): Based on micropython_ir by Peter Hinch, modified to implement the OpenLASIR protocol. The original library supports NEC, Sony, Philips RC-5/RC-6, and other IR protocols for MicroPython.

Arduino IR transmitter and receiver modules: Uses Arduino-IRremote library (which contains native OpenLASIR support).


To get involved, interact on GitHub or join the Laser* Tag Badge Discord.