Files
lego-dimensions-reader/README.md

247 lines
6.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](https://gitea.hideawaygaming.com.au/jessikitty/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
```bash
pip install pyusb
```
### 2. Install Windows USB Driver (Required)
The portal's default Windows HID driver must be replaced with WinUSB:
1. Download **Zadig** from https://zadig.akeo.ie/
2. Connect the LEGO Dimensions portal via USB
3. Run Zadig **as Administrator**
4. Select **Options → List All Devices**
5. Choose **"LEGO READER V2.10"** from the dropdown
6. Select **WinUSB** as the target driver
7. Click **"Replace Driver"**
8. Unplug and reconnect the portal
## Quick Start
```python
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
```python
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
```python
from lego_dimensions_reader import Pad
Pad.ALL # All pads
Pad.CENTER # Center pad
Pad.LEFT # Left pad
Pad.RIGHT # Right pad
```
### Colors
```python
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:
```python
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:
```python
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