diff --git a/moonlight_integration.py b/moonlight_integration.py index bc69cce..219ef9e 100644 --- a/moonlight_integration.py +++ b/moonlight_integration.py @@ -3,15 +3,15 @@ LEGO Dimensions + Moonlight Drive-In Integration Integrates the LEGO Dimensions portal reader with the Moonlight Drive-In -video player system using NFC card IDs (decimal format). +video player system using NFC card IDs (10-digit decimal format). -When a LEGO Dimensions disc is placed on the portal, it triggers -video playback. When removed, playback stops. +When a LEGO Dimensions disc is placed on the portal, it sends the +10-digit card ID as plain text to the player (like keyboard input). -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 +v1.2.0 Changes: +- Sends 10-digit card ID as plain text (like keyboard entry) +- No JSON, just the raw card ID string +- Updated default API URL """ import sys @@ -27,7 +27,7 @@ from lego_dimensions_reader import ( COLORS ) -__version__ = "1.1.0" +__version__ = "1.2.0" class MoonlightDimensionsClient: @@ -35,8 +35,8 @@ class MoonlightDimensionsClient: Integration client connecting LEGO Dimensions portal to Moonlight Drive-In video player. - Uses NFC card ID (decimal) format for compatibility with - standard NFC readers and existing mapping systems. + Sends 10-digit NFC card ID as plain text to the player, + simulating keyboard input. """ def __init__(self, api_url: str, mapping_file: Optional[str] = None): @@ -44,14 +44,13 @@ class MoonlightDimensionsClient: 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 card IDs to video paths + api_url: Base URL for Moonlight Drive-In API (e.g., "http://100.94.163.117:8547") + mapping_file: Optional JSON file mapping card IDs to video paths (for local display only) """ self.api_url = api_url.rstrip('/') self.reader = LegoDimensionsReader() - # Load card ID to video mapping - # Keys can be decimal strings like "983187584" + # Optional local mapping for display purposes self.video_mapping: Dict[str, str] = {} if mapping_file: self._load_mapping(mapping_file) @@ -64,26 +63,19 @@ class MoonlightDimensionsClient: self.reader.on_error = self._on_error # Track currently playing - self._current_video: Optional[str] = None - self._current_card_id: Optional[int] = None + self._current_card_id: Optional[str] = None def _load_mapping(self, filepath: str): - """Load card ID to video mapping from JSON file.""" + """Load card ID to video mapping from JSON file (optional, for display).""" try: with open(filepath, 'r') as f: data = json.load(f) - # 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") except FileNotFoundError: - print(f"Warning: Mapping file not found: {filepath}") - print("Tags will be logged but no videos will play.") + pass # Mapping file is optional except json.JSONDecodeError as e: print(f"Warning: Invalid JSON in mapping file: {e}") @@ -105,103 +97,95 @@ class MoonlightDimensionsClient: print(f"\n⚠ Error: {error}") def _on_tag_insert(self, tag: TagInfo): - """Handle tag placement - trigger video playback.""" + """Handle tag placement - send card ID to player.""" + # Get 10-digit zero-padded card ID + card_id = tag.nfc_card_id_str # "0983187584" + print(f"\n{'='*50}") print(f"✓ TAG DETECTED on {tag.pad.name} pad") print(f" UID: {tag.uid_hex}") - print(f" NFC Card ID: {tag.nfc_card_id}") + print(f" Card ID: {card_id}") - # Look up video by decimal card ID - card_id_str = str(tag.nfc_card_id) - video_path = self.video_mapping.get(card_id_str) + # Send card ID to player + self._send_card_id(card_id) - # Also try zero-padded format - if not video_path: - video_path = self.video_mapping.get(tag.nfc_card_id_str) + # Set pad to green to indicate sent + self.reader.set_pad_color(tag.pad, COLORS['GREEN']) + # Show mapped video if we have local mapping + video_path = self.video_mapping.get(str(tag.nfc_card_id)) or \ + self.video_mapping.get(card_id) if video_path: - print(f" Video: {video_path}") - self._play_video(video_path, tag.nfc_card_id) - - # 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.nfc_card_id}\": \"path/to/video.mp4\"") - - # Flash yellow to indicate unmapped tag - self.reader.flash_pad(tag.pad, COLORS['YELLOW'], count=3) + print(f" Mapped to: {video_path}") 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") - print(f" NFC Card ID: {tag.nfc_card_id}") + """Handle tag removal.""" + card_id = tag.nfc_card_id_str - # Stop video if one is playing - if self._current_video: - self._stop_video() + print(f"\n✗ TAG REMOVED from {tag.pad.name} pad") + print(f" Card ID: {card_id}") + + # Optionally send stop command + self._send_stop() # Turn off pad LED self.reader.set_pad_color(tag.pad, COLORS['OFF']) + self._current_card_id = None - def _play_video(self, video_path: str, card_id: int): - """Send play command to Moonlight API with card ID.""" + def _send_card_id(self, card_id: str): + """Send the 10-digit card ID as plain text to the player.""" try: + # Send as plain text - like keyboard input response = requests.post( - f"{self.api_url}/api/play", - json={ - "path": video_path, - "card_id": card_id # Send decimal card ID - }, + f"{self.api_url}/api/input", + data=card_id, + headers={'Content-Type': 'text/plain'}, timeout=5 ) if response.ok: - self._current_video = video_path self._current_card_id = card_id - print(f" ▶ Playing: {video_path}") + print(f" ▶ Sent: {card_id}") else: - print(f" ⚠ API Error: {response.status_code}") + # Try alternate endpoint + response = requests.post( + f"{self.api_url}/api/play", + data=card_id, + headers={'Content-Type': 'text/plain'}, + timeout=5 + ) + if response.ok: + self._current_card_id = card_id + print(f" ▶ Sent: {card_id}") + else: + print(f" ⚠ API Error: {response.status_code}") except requests.exceptions.ConnectionError: - print(f" ⚠ Cannot connect to Moonlight API at {self.api_url}") + print(f" ⚠ Cannot connect to player at {self.api_url}") except requests.exceptions.Timeout: - print(f" ⚠ API request timed out") + print(f" ⚠ Request timed out") - def _stop_video(self): - """Send stop command to Moonlight API.""" + def _send_stop(self): + """Send stop command to player.""" try: - response = requests.post( + requests.post( f"{self.api_url}/api/stop", - json={ - "card_id": self._current_card_id - } if self._current_card_id else {}, timeout=5 ) - - if response.ok: - print(f" ⏹ Stopped playback") - self._current_video = None - self._current_card_id = None - + print(f" ⏹ Stop sent") except requests.exceptions.RequestException: pass # Ignore stop errors - def add_mapping(self, card_id: int, video_path: str): - """Add a card ID to video mapping at runtime.""" - self.video_mapping[str(card_id)] = video_path - def start(self): """Start the integration client.""" print("\n" + "="*50) print(" LEGO Dimensions + Moonlight Drive-In") print(f" Integration v{__version__}") print("="*50) - print(f"\nMoonlight API: {self.api_url}") - print(f"Video mappings: {len(self.video_mapping)}") - print("Using NFC Card ID format (decimal)") + print(f"\nPlayer URL: {self.api_url}") + print("Sending 10-digit card IDs as text") print("\nStarting portal connection...") self.reader.start() @@ -214,26 +198,15 @@ class MoonlightDimensionsClient: 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): - # { - # "983187584": "videos/batman_intro.mp4", - # "1234567890": "videos/gandalf_intro.mp4" - # } - # - # Keys are decimal NFC card IDs (what standard NFC readers show) + # Configuration - UPDATE THIS TO YOUR PLAYER IP + MOONLIGHT_API = "http://100.94.163.117:8547" + MAPPING_FILE = "video_mappings.json" # Optional client = MoonlightDimensionsClient( api_url=MOONLIGHT_API, mapping_file=MAPPING_FILE ) - # You can also add mappings programmatically: - # client.add_mapping(983187584, "videos/custom.mp4") - try: client.start()