6.1 KiB
6.1 KiB
LEGO Dimensions NFC Portal Reader
Python module for reading LEGO Dimensions character and vehicle discs using the USB portal (PS3/PS4/Wii U versions) on Windows.
Designed to integrate with the Moonlight Drive-In video player system.
Features
- Event-driven tag detection - Callbacks for tag insert/remove events
- LED control - Set colors, flash, and fade effects on portal pads
- Thread-safe - Background polling thread with proper locking
- Character database - Built-in lookup for 80+ characters and vehicles
- TEA encryption - Full encryption/decryption support for character IDs
Hardware Requirements
| Portal Version | Compatible |
|---|---|
| PS3 | ✅ Yes |
| PS4 | ✅ Yes |
| Wii U | ✅ Yes |
| Xbox 360 | ❌ No |
| Xbox One | ❌ No |
USB Identifiers:
- Vendor ID:
0x0e6f - Product ID:
0x0241
Installation
1. Install Python Package
pip install pyusb
2. Install Windows USB Driver (Required)
The portal's default Windows HID driver must be replaced with WinUSB:
- Download Zadig from https://zadig.akeo.ie/
- Connect the LEGO Dimensions portal via USB
- Run Zadig as Administrator
- Select Options → List All Devices
- Choose "LEGO READER V2.10" from the dropdown
- Select WinUSB as the target driver
- Click "Replace Driver"
- Unplug and reconnect the portal
Quick Start
from lego_dimensions_reader import LegoDimensionsReader, COLORS
def on_tag_placed(tag):
print(f"Tag detected: {tag.uid_hex} on {tag.pad.name}")
def on_tag_removed(tag):
print(f"Tag removed from {tag.pad.name}")
reader = LegoDimensionsReader()
reader.on_tag_insert = on_tag_placed
reader.on_tag_remove = on_tag_removed
reader.start()
# Keep running...
import time
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
reader.disconnect()
API Reference
LegoDimensionsReader
Main class for portal communication.
Callbacks
| Callback | Signature | Description |
|---|---|---|
on_tag_insert |
(TagInfo) -> None |
Called when tag is placed |
on_tag_remove |
(TagInfo) -> None |
Called when tag is removed |
on_connect |
() -> None |
Called on successful connection |
on_disconnect |
() -> None |
Called on disconnection |
on_error |
(Exception) -> None |
Called on errors |
Methods
reader.connect() # Connect to portal (called automatically by start())
reader.disconnect() # Disconnect and cleanup
reader.start() # Start event polling thread
reader.stop() # Stop polling thread
# LED Control
reader.set_pad_color(Pad.CENTER, COLORS['RED'])
reader.flash_pad(Pad.LEFT, COLORS['GREEN'], on_time=10, off_time=10, count=5)
reader.fade_pad(Pad.RIGHT, COLORS['BLUE'], speed=10, count=1)
# State
reader.get_active_tags() # Dict of tags currently on pads
reader.is_connected # Boolean
reader.is_running # Boolean
TagInfo
Data class returned in callbacks.
| Property | Type | Description |
|---|---|---|
uid |
bytes |
7-byte tag UID |
uid_hex |
str |
UID as hex string |
pad |
Pad |
Which pad (CENTER, LEFT, RIGHT) |
event |
TagEvent |
INSERTED or REMOVED |
Pad Enum
from lego_dimensions_reader import Pad
Pad.ALL # All pads
Pad.CENTER # Center pad
Pad.LEFT # Left pad
Pad.RIGHT # Right pad
Colors
from lego_dimensions_reader import COLORS
COLORS['OFF'] # [0, 0, 0]
COLORS['RED'] # [255, 0, 0]
COLORS['GREEN'] # [0, 255, 0]
COLORS['BLUE'] # [0, 0, 255]
COLORS['WHITE'] # [255, 255, 255]
COLORS['YELLOW'] # [255, 255, 0]
COLORS['CYAN'] # [0, 255, 255]
COLORS['MAGENTA'] # [255, 0, 255]
COLORS['ORANGE'] # [255, 128, 0]
COLORS['PURPLE'] # [128, 0, 255]
Integration with Moonlight Drive-In
Example integration for triggering video playback:
import requests
from lego_dimensions_reader import LegoDimensionsReader
# Map tag UIDs to video files
VIDEO_MAPPING = {
"04A1B2C3D4E5F6": "videos/batman_intro.mp4",
"04D5E6F7A8B9C0": "videos/gandalf_intro.mp4",
}
MOONLIGHT_API = "http://drive-in:8547"
def on_tag_insert(tag):
video = VIDEO_MAPPING.get(tag.uid_hex)
if video:
requests.post(f"{MOONLIGHT_API}/api/play", json={"path": video})
def on_tag_remove(tag):
requests.post(f"{MOONLIGHT_API}/api/stop")
reader = LegoDimensionsReader()
reader.on_tag_insert = on_tag_insert
reader.on_tag_remove = on_tag_remove
reader.start()
TEA Encryption Functions
For advanced tag reading/writing:
from lego_dimensions_reader import (
generate_password,
generate_tea_key,
decrypt_character_id,
encrypt_character_id
)
uid = bytes.fromhex("04A1B2C3D4E5F6")
# Generate tag password
password = generate_password(uid)
# Decrypt character ID from tag data
encrypted_data = bytes.fromhex("...") # 8 bytes from pages 0x24-0x25
character_id = decrypt_character_id(uid, encrypted_data)
# Encrypt character ID for writing
encrypted = encrypt_character_id(uid, character_id=1) # Batman
Troubleshooting
"LEGO Dimensions Portal not found"
- Ensure portal is connected via USB
- Check you're using PS3/PS4/Wii U portal (NOT Xbox)
- Verify Zadig driver installation
"Access denied" error
- Run your script as Administrator
- Re-run Zadig driver installation
Portal detected but no tag events
- Try unplugging and reconnecting portal
- Check USB cable connection
- Ensure tag is placed flat on the pad
Technical Details
NFC Tags
- Tag type: NTAG213 (Mifare Ultralight C)
- Memory: 144 bytes (45 pages × 4 bytes)
- Character data: Pages 0x24-0x25 (TEA encrypted)
- Vehicle data: Page 0x24 (unencrypted)
- Password protection: Pages 0x24+
USB Protocol
- Packet size: 32 bytes (fixed)
- Endpoint OUT: 0x01
- Endpoint IN: 0x81
- Tag events start with byte 0x56
Credits
Based on reverse-engineering work by:
- ags131 (node-ld)
- bettse
- socram8888
- Ellerbach (LegoDimensions .NET library)
License
MIT License