#!/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())