Add Moonlight Drive-In integration example
This commit is contained in:
220
moonlight_integration.py
Normal file
220
moonlight_integration.py
Normal 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())
|
||||
Reference in New Issue
Block a user