Update to use nfc_card_id (decimal) for API calls and mappings
- Mapping file now uses decimal card IDs: "983187584": "video.mp4" - API calls include card_id as decimal integer - Displays NFC Card ID in logs for easy mapping creation - Version bump to 1.1.0
This commit is contained in:
@@ -1,12 +1,17 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
LEGO Dimensions + Moonlight Drive-In Integration Example
|
LEGO Dimensions + Moonlight Drive-In Integration
|
||||||
|
|
||||||
This example shows how to integrate the LEGO Dimensions portal reader
|
Integrates the LEGO Dimensions portal reader with the Moonlight Drive-In
|
||||||
with the Moonlight Drive-In video player system.
|
video player system using NFC card IDs (decimal format).
|
||||||
|
|
||||||
When a LEGO Dimensions disc is placed on the portal, it triggers
|
When a LEGO Dimensions disc is placed on the portal, it triggers
|
||||||
video playback. When removed, playback stops.
|
video playback. When removed, playback stops.
|
||||||
|
|
||||||
|
v1.1.0 Changes:
|
||||||
|
- Now uses nfc_card_id (decimal) for lookups and API calls
|
||||||
|
- Mapping file keys can be decimal strings: "983187584": "path/to/video.mp4"
|
||||||
|
- API sends card_id as decimal integer for compatibility
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@@ -22,11 +27,16 @@ from lego_dimensions_reader import (
|
|||||||
COLORS
|
COLORS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
__version__ = "1.1.0"
|
||||||
|
|
||||||
|
|
||||||
class MoonlightDimensionsClient:
|
class MoonlightDimensionsClient:
|
||||||
"""
|
"""
|
||||||
Integration client connecting LEGO Dimensions portal to
|
Integration client connecting LEGO Dimensions portal to
|
||||||
Moonlight Drive-In video player.
|
Moonlight Drive-In video player.
|
||||||
|
|
||||||
|
Uses NFC card ID (decimal) format for compatibility with
|
||||||
|
standard NFC readers and existing mapping systems.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, api_url: str, mapping_file: Optional[str] = None):
|
def __init__(self, api_url: str, mapping_file: Optional[str] = None):
|
||||||
@@ -35,12 +45,13 @@ class MoonlightDimensionsClient:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
api_url: Base URL for Moonlight Drive-In API (e.g., "http://drive-in:8547")
|
api_url: Base URL for Moonlight Drive-In API (e.g., "http://drive-in:8547")
|
||||||
mapping_file: Optional JSON file mapping UIDs to video paths
|
mapping_file: Optional JSON file mapping card IDs to video paths
|
||||||
"""
|
"""
|
||||||
self.api_url = api_url.rstrip('/')
|
self.api_url = api_url.rstrip('/')
|
||||||
self.reader = LegoDimensionsReader()
|
self.reader = LegoDimensionsReader()
|
||||||
|
|
||||||
# Load UID to video mapping
|
# Load card ID to video mapping
|
||||||
|
# Keys can be decimal strings like "983187584"
|
||||||
self.video_mapping: Dict[str, str] = {}
|
self.video_mapping: Dict[str, str] = {}
|
||||||
if mapping_file:
|
if mapping_file:
|
||||||
self._load_mapping(mapping_file)
|
self._load_mapping(mapping_file)
|
||||||
@@ -54,13 +65,21 @@ class MoonlightDimensionsClient:
|
|||||||
|
|
||||||
# Track currently playing
|
# Track currently playing
|
||||||
self._current_video: Optional[str] = None
|
self._current_video: Optional[str] = None
|
||||||
|
self._current_card_id: Optional[int] = None
|
||||||
|
|
||||||
def _load_mapping(self, filepath: str):
|
def _load_mapping(self, filepath: str):
|
||||||
"""Load UID to video mapping from JSON file."""
|
"""Load card ID to video mapping from JSON file."""
|
||||||
try:
|
try:
|
||||||
with open(filepath, 'r') as f:
|
with open(filepath, 'r') as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
self.video_mapping = data.get('mappings', data)
|
# Support both "mappings" wrapper and direct dict
|
||||||
|
raw_mappings = data.get('mappings', data)
|
||||||
|
|
||||||
|
# Normalize keys to strings
|
||||||
|
for key, value in raw_mappings.items():
|
||||||
|
# Accept both string and numeric keys
|
||||||
|
self.video_mapping[str(key)] = value
|
||||||
|
|
||||||
print(f"Loaded {len(self.video_mapping)} video mappings")
|
print(f"Loaded {len(self.video_mapping)} video mappings")
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print(f"Warning: Mapping file not found: {filepath}")
|
print(f"Warning: Mapping file not found: {filepath}")
|
||||||
@@ -90,19 +109,25 @@ class MoonlightDimensionsClient:
|
|||||||
print(f"\n{'='*50}")
|
print(f"\n{'='*50}")
|
||||||
print(f"✓ TAG DETECTED on {tag.pad.name} pad")
|
print(f"✓ TAG DETECTED on {tag.pad.name} pad")
|
||||||
print(f" UID: {tag.uid_hex}")
|
print(f" UID: {tag.uid_hex}")
|
||||||
|
print(f" NFC Card ID: {tag.nfc_card_id}")
|
||||||
|
|
||||||
# Check if we have a video mapped for this UID
|
# Look up video by decimal card ID
|
||||||
video_path = self.video_mapping.get(tag.uid_hex)
|
card_id_str = str(tag.nfc_card_id)
|
||||||
|
video_path = self.video_mapping.get(card_id_str)
|
||||||
|
|
||||||
|
# Also try zero-padded format
|
||||||
|
if not video_path:
|
||||||
|
video_path = self.video_mapping.get(tag.nfc_card_id_str)
|
||||||
|
|
||||||
if video_path:
|
if video_path:
|
||||||
print(f" Video: {video_path}")
|
print(f" Video: {video_path}")
|
||||||
self._play_video(video_path)
|
self._play_video(video_path, tag.nfc_card_id)
|
||||||
|
|
||||||
# Set pad to green to indicate playing
|
# Set pad to green to indicate playing
|
||||||
self.reader.set_pad_color(tag.pad, COLORS['GREEN'])
|
self.reader.set_pad_color(tag.pad, COLORS['GREEN'])
|
||||||
else:
|
else:
|
||||||
print(f" No video mapped for this tag")
|
print(f" No video mapped for this tag")
|
||||||
print(f" Add mapping: \"{tag.uid_hex}\": \"path/to/video.mp4\"")
|
print(f" Add mapping: \"{tag.nfc_card_id}\": \"path/to/video.mp4\"")
|
||||||
|
|
||||||
# Flash yellow to indicate unmapped tag
|
# Flash yellow to indicate unmapped tag
|
||||||
self.reader.flash_pad(tag.pad, COLORS['YELLOW'], count=3)
|
self.reader.flash_pad(tag.pad, COLORS['YELLOW'], count=3)
|
||||||
@@ -112,6 +137,7 @@ class MoonlightDimensionsClient:
|
|||||||
def _on_tag_remove(self, tag: TagInfo):
|
def _on_tag_remove(self, tag: TagInfo):
|
||||||
"""Handle tag removal - stop playback."""
|
"""Handle tag removal - stop playback."""
|
||||||
print(f"\n✗ TAG REMOVED from {tag.pad.name} pad")
|
print(f"\n✗ TAG REMOVED from {tag.pad.name} pad")
|
||||||
|
print(f" NFC Card ID: {tag.nfc_card_id}")
|
||||||
|
|
||||||
# Stop video if one is playing
|
# Stop video if one is playing
|
||||||
if self._current_video:
|
if self._current_video:
|
||||||
@@ -120,17 +146,21 @@ class MoonlightDimensionsClient:
|
|||||||
# Turn off pad LED
|
# Turn off pad LED
|
||||||
self.reader.set_pad_color(tag.pad, COLORS['OFF'])
|
self.reader.set_pad_color(tag.pad, COLORS['OFF'])
|
||||||
|
|
||||||
def _play_video(self, video_path: str):
|
def _play_video(self, video_path: str, card_id: int):
|
||||||
"""Send play command to Moonlight API."""
|
"""Send play command to Moonlight API with card ID."""
|
||||||
try:
|
try:
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{self.api_url}/api/play",
|
f"{self.api_url}/api/play",
|
||||||
json={"path": video_path},
|
json={
|
||||||
|
"path": video_path,
|
||||||
|
"card_id": card_id # Send decimal card ID
|
||||||
|
},
|
||||||
timeout=5
|
timeout=5
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.ok:
|
if response.ok:
|
||||||
self._current_video = video_path
|
self._current_video = video_path
|
||||||
|
self._current_card_id = card_id
|
||||||
print(f" ▶ Playing: {video_path}")
|
print(f" ▶ Playing: {video_path}")
|
||||||
else:
|
else:
|
||||||
print(f" ⚠ API Error: {response.status_code}")
|
print(f" ⚠ API Error: {response.status_code}")
|
||||||
@@ -145,27 +175,33 @@ class MoonlightDimensionsClient:
|
|||||||
try:
|
try:
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{self.api_url}/api/stop",
|
f"{self.api_url}/api/stop",
|
||||||
|
json={
|
||||||
|
"card_id": self._current_card_id
|
||||||
|
} if self._current_card_id else {},
|
||||||
timeout=5
|
timeout=5
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.ok:
|
if response.ok:
|
||||||
print(f" ⏹ Stopped playback")
|
print(f" ⏹ Stopped playback")
|
||||||
self._current_video = None
|
self._current_video = None
|
||||||
|
self._current_card_id = None
|
||||||
|
|
||||||
except requests.exceptions.RequestException:
|
except requests.exceptions.RequestException:
|
||||||
pass # Ignore stop errors
|
pass # Ignore stop errors
|
||||||
|
|
||||||
def add_mapping(self, uid: str, video_path: str):
|
def add_mapping(self, card_id: int, video_path: str):
|
||||||
"""Add a UID to video mapping at runtime."""
|
"""Add a card ID to video mapping at runtime."""
|
||||||
self.video_mapping[uid.upper()] = video_path
|
self.video_mapping[str(card_id)] = video_path
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Start the integration client."""
|
"""Start the integration client."""
|
||||||
print("\n" + "="*50)
|
print("\n" + "="*50)
|
||||||
print(" LEGO Dimensions + Moonlight Drive-In")
|
print(" LEGO Dimensions + Moonlight Drive-In")
|
||||||
|
print(f" Integration v{__version__}")
|
||||||
print("="*50)
|
print("="*50)
|
||||||
print(f"\nMoonlight API: {self.api_url}")
|
print(f"\nMoonlight API: {self.api_url}")
|
||||||
print(f"Video mappings: {len(self.video_mapping)}")
|
print(f"Video mappings: {len(self.video_mapping)}")
|
||||||
|
print("Using NFC Card ID format (decimal)")
|
||||||
print("\nStarting portal connection...")
|
print("\nStarting portal connection...")
|
||||||
|
|
||||||
self.reader.start()
|
self.reader.start()
|
||||||
@@ -184,9 +220,11 @@ def main():
|
|||||||
|
|
||||||
# Example mapping file format (video_mappings.json):
|
# Example mapping file format (video_mappings.json):
|
||||||
# {
|
# {
|
||||||
# "04A1B2C3D4E5F6": "videos/batman_intro.mp4",
|
# "983187584": "videos/batman_intro.mp4",
|
||||||
# "04D5E6F7A8B9C0": "videos/gandalf_intro.mp4"
|
# "1234567890": "videos/gandalf_intro.mp4"
|
||||||
# }
|
# }
|
||||||
|
#
|
||||||
|
# Keys are decimal NFC card IDs (what standard NFC readers show)
|
||||||
|
|
||||||
client = MoonlightDimensionsClient(
|
client = MoonlightDimensionsClient(
|
||||||
api_url=MOONLIGHT_API,
|
api_url=MOONLIGHT_API,
|
||||||
@@ -194,7 +232,7 @@ def main():
|
|||||||
)
|
)
|
||||||
|
|
||||||
# You can also add mappings programmatically:
|
# You can also add mappings programmatically:
|
||||||
# client.add_mapping("04AABBCCDDEE00", "videos/custom.mp4")
|
# client.add_mapping(983187584, "videos/custom.mp4")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client.start()
|
client.start()
|
||||||
|
|||||||
Reference in New Issue
Block a user