Add Moonlight Drive-In integration example

This commit is contained in:
2026-01-24 10:36:29 +11:00
parent 56d758d7fc
commit c517d78ce6

220
moonlight_integration.py Normal file
View File

@@ -0,0 +1,220 @@
#!/usr/bin/env python3
"""
LEGO Dimensions + Moonlight Drive-In Integration Example
This example shows how to integrate the LEGO Dimensions portal reader
with the Moonlight Drive-In video player system.
When a LEGO Dimensions disc is placed on the portal, it triggers
video playback. When removed, playback stops.
"""
import sys
import time
import json
import requests
from typing import Dict, Optional
from lego_dimensions_reader import (
LegoDimensionsReader,
TagInfo,
Pad,
COLORS
)
class MoonlightDimensionsClient:
"""
Integration client connecting LEGO Dimensions portal to
Moonlight Drive-In video player.
"""
def __init__(self, api_url: str, mapping_file: Optional[str] = None):
"""
Initialize the integration client.
Args:
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
"""
self.api_url = api_url.rstrip('/')
self.reader = LegoDimensionsReader()
# Load UID to video mapping
self.video_mapping: Dict[str, str] = {}
if mapping_file:
self._load_mapping(mapping_file)
# Set up callbacks
self.reader.on_tag_insert = self._on_tag_insert
self.reader.on_tag_remove = self._on_tag_remove
self.reader.on_connect = self._on_connect
self.reader.on_disconnect = self._on_disconnect
self.reader.on_error = self._on_error
# Track currently playing
self._current_video: Optional[str] = None
def _load_mapping(self, filepath: str):
"""Load UID to video mapping from JSON file."""
try:
with open(filepath, 'r') as f:
data = json.load(f)
self.video_mapping = data.get('mappings', data)
print(f"Loaded {len(self.video_mapping)} video mappings")
except FileNotFoundError:
print(f"Warning: Mapping file not found: {filepath}")
print("Tags will be logged but no videos will play.")
except json.JSONDecodeError as e:
print(f"Warning: Invalid JSON in mapping file: {e}")
def _on_connect(self):
"""Handle portal connection."""
print("\n✓ LEGO Dimensions Portal Connected")
print(" Ready to detect discs...")
# Flash all pads blue to indicate ready
for pad in [Pad.CENTER, Pad.LEFT, Pad.RIGHT]:
self.reader.flash_pad(pad, COLORS['BLUE'], count=2)
def _on_disconnect(self):
"""Handle portal disconnection."""
print("\n✗ Portal Disconnected")
def _on_error(self, error: Exception):
"""Handle errors."""
print(f"\n⚠ Error: {error}")
def _on_tag_insert(self, tag: TagInfo):
"""Handle tag placement - trigger video playback."""
print(f"\n{'='*50}")
print(f"✓ TAG DETECTED on {tag.pad.name} pad")
print(f" UID: {tag.uid_hex}")
# Check if we have a video mapped for this UID
video_path = self.video_mapping.get(tag.uid_hex)
if video_path:
print(f" Video: {video_path}")
self._play_video(video_path)
# Set pad to green to indicate playing
self.reader.set_pad_color(tag.pad, COLORS['GREEN'])
else:
print(f" No video mapped for this tag")
print(f" Add mapping: \"{tag.uid_hex}\": \"path/to/video.mp4\"")
# Flash yellow to indicate unmapped tag
self.reader.flash_pad(tag.pad, COLORS['YELLOW'], count=3)
print(f"{'='*50}")
def _on_tag_remove(self, tag: TagInfo):
"""Handle tag removal - stop playback."""
print(f"\n✗ TAG REMOVED from {tag.pad.name} pad")
# Stop video if one is playing
if self._current_video:
self._stop_video()
# Turn off pad LED
self.reader.set_pad_color(tag.pad, COLORS['OFF'])
def _play_video(self, video_path: str):
"""Send play command to Moonlight API."""
try:
response = requests.post(
f"{self.api_url}/api/play",
json={"path": video_path},
timeout=5
)
if response.ok:
self._current_video = video_path
print(f" ▶ Playing: {video_path}")
else:
print(f" ⚠ API Error: {response.status_code}")
except requests.exceptions.ConnectionError:
print(f" ⚠ Cannot connect to Moonlight API at {self.api_url}")
except requests.exceptions.Timeout:
print(f" ⚠ API request timed out")
def _stop_video(self):
"""Send stop command to Moonlight API."""
try:
response = requests.post(
f"{self.api_url}/api/stop",
timeout=5
)
if response.ok:
print(f" ⏹ Stopped playback")
self._current_video = None
except requests.exceptions.RequestException:
pass # Ignore stop errors
def add_mapping(self, uid: str, video_path: str):
"""Add a UID to video mapping at runtime."""
self.video_mapping[uid.upper()] = video_path
def start(self):
"""Start the integration client."""
print("\n" + "="*50)
print(" LEGO Dimensions + Moonlight Drive-In")
print("="*50)
print(f"\nMoonlight API: {self.api_url}")
print(f"Video mappings: {len(self.video_mapping)}")
print("\nStarting portal connection...")
self.reader.start()
def stop(self):
"""Stop the integration client."""
self.reader.disconnect()
print("\nClient stopped.")
def main():
"""Main entry point."""
# Configuration
MOONLIGHT_API = "http://drive-in:8547" # Change to your server
MAPPING_FILE = "video_mappings.json" # Optional mapping file
# Example mapping file format (video_mappings.json):
# {
# "04A1B2C3D4E5F6": "videos/batman_intro.mp4",
# "04D5E6F7A8B9C0": "videos/gandalf_intro.mp4"
# }
client = MoonlightDimensionsClient(
api_url=MOONLIGHT_API,
mapping_file=MAPPING_FILE
)
# You can also add mappings programmatically:
# client.add_mapping("04AABBCCDDEE00", "videos/custom.mp4")
try:
client.start()
print("\nPlace LEGO Dimensions discs on the portal...")
print("Press Ctrl+C to exit\n")
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\n\nShutting down...")
except ConnectionError as e:
print(f"\nFailed to connect: {e}")
return 1
finally:
client.stop()
return 0
if __name__ == "__main__":
sys.exit(main())