Fix compatibility with NFCNetworkClient v1.1.0 - properly pass root parameter

This commit is contained in:
2025-12-24 10:57:47 +11:00
parent 551eb76036
commit e6f15d5c26

View File

@@ -1 +1,290 @@
$(cat /home/claude/nfc_autoconnect.py)
#!/usr/bin/env python3
"""
Smart Auto-Connect NFC Network Client
Moonlight Drive-In Theater System
Enhanced client with smart DNS resolution and automatic connection.
Compatible with NFCNetworkClient v1.1.0 (global input capture).
"""
import tkinter as tk
from tkinter import ttk
import sys
import os
import socket
import subprocess
import time
from typing import Optional, List, Tuple
# Import the base client
try:
from nfc_network_client import NFCNetworkClient, CLIENT_VERSION as BASE_VERSION
except ImportError:
print("ERROR: Cannot find nfc_network_client.py")
print("Make sure nfc_network_client.py is in the same directory")
sys.exit(1)
SMART_CLIENT_VERSION = "2.0.0"
class DNSResolver:
"""Smart DNS resolution with multiple fallback methods"""
def __init__(self, device_name: str, fallback_ip: str, port: int = 8547):
self.device_name = device_name
self.fallback_ip = fallback_ip
self.port = port
self.last_known_ip: Optional[str] = None
def resolve(self) -> Tuple[Optional[str], str]:
"""
Try multiple DNS resolution methods
Returns: (ip_address, method_name)
"""
print(f"\nAttempting to locate device: {self.device_name}")
# Method 1: Standard DNS
result = self._try_standard_dns()
if result:
return result, "STANDARD_DNS"
# Method 2: mDNS (.local)
result = self._try_mdns()
if result:
return result, "MDNS"
# Method 3: Tailscale DNS
result = self._try_tailscale_dns()
if result:
return result, "TAILSCALE_DNS"
# Method 4: Fallback to known IP
if self.fallback_ip:
print(f"\nMethod: Fallback to known IP")
if self._check_host_reachable(self.fallback_ip):
print(f"SUCCESS: Fallback IP is reachable: {self.fallback_ip}")
return self.fallback_ip, "FALLBACK_IP"
print("\nERROR: Could not locate device using any method")
return None, "FAILED"
def _try_standard_dns(self) -> Optional[str]:
"""Try standard DNS resolution"""
print(f"\nMethod: Standard DNS")
try:
ip = socket.gethostbyname(self.device_name)
print(f" [DNS] ✓ Resolved: {self.device_name} -> {ip}")
if self._check_host_reachable(ip):
print(f"SUCCESS: Found {self.device_name} at {ip} via Standard DNS")
return ip
except socket.gaierror:
print(f" [DNS] ✗ Failed to resolve: {self.device_name}")
return None
def _try_mdns(self) -> Optional[str]:
"""Try mDNS resolution (.local)"""
mdns_name = f"{self.device_name}.local"
print(f"\nMethod: mDNS (Bonjour)")
try:
ip = socket.gethostbyname(mdns_name)
print(f" [mDNS] ✓ Resolved: {mdns_name} -> {ip}")
if self._check_host_reachable(ip):
print(f"SUCCESS: Found {self.device_name} at {ip} via mDNS")
return ip
except socket.gaierror:
print(f" [mDNS] ✗ Failed to resolve: {mdns_name}")
return None
def _try_tailscale_dns(self) -> Optional[str]:
"""Try Tailscale DNS resolution"""
ts_name = f"{self.device_name}.tail-scale.ts.net"
print(f"\nMethod: Tailscale VPN DNS")
try:
ip = socket.gethostbyname(ts_name)
print(f" [Tailscale] ✓ Resolved: {ts_name} -> {ip}")
if self._check_host_reachable(ip):
print(f"SUCCESS: Found {self.device_name} at {ip} via Tailscale")
return ip
except socket.gaierror:
print(f" [Tailscale] ✗ Failed to resolve: {ts_name}")
return None
def _check_host_reachable(self, ip: str) -> bool:
"""Check if host is reachable via ping"""
try:
# Try quick ping (1 packet, 1 second timeout)
if sys.platform == "win32":
result = subprocess.run(
["ping", "-n", "1", "-w", "1000", ip],
capture_output=True,
timeout=2
)
else:
result = subprocess.run(
["ping", "-c", "1", "-W", "1", ip],
capture_output=True,
timeout=2
)
if result.returncode == 0:
print(f" [Ping] ✓ Host reachable: {ip}")
return True
else:
print(f" [Ping] ✗ Host unreachable: {ip}")
return False
except (subprocess.TimeoutExpired, Exception) as e:
print(f" [Ping] ✗ Ping failed: {e}")
return False
class SmartAutoConnectClient(NFCNetworkClient):
"""Enhanced NFC client with smart auto-connect"""
def __init__(self, root: tk.Tk, device_name: str, fallback_ip: str, port: int = 8547):
# Store smart connect settings
self.device_name = device_name
self.fallback_ip = fallback_ip
self.target_port = port
self.resolver = DNSResolver(device_name, fallback_ip, port)
# Initialize parent with root
super().__init__(root)
# Update title to show smart connect
self.root.title(f"NFC Network Client - Smart Connect v{SMART_CLIENT_VERSION}")
# Customize UI for smart connect
self._customize_ui()
def _customize_ui(self):
"""Add smart connect specific UI elements"""
# Update server entry to show device name
self.server_entry.delete(0, tk.END)
self.server_entry.insert(0, f"{self.device_name} (auto-resolved)")
self.server_entry.config(state="disabled")
def connect_to_server(self):
"""Override connect to use smart DNS resolution"""
self.log(f"Smart connecting to: {self.device_name}")
self.update_status("Resolving...", "orange")
self.connect_btn.config(state="disabled")
# Resolve in background thread
def resolve_and_connect():
ip, method = self.resolver.resolve()
if ip:
print(f"\n{'='*70}")
print(f"WILL CONNECT TO: {ip}:{self.target_port}")
print(f"Resolution method: {method}")
print(f"{'='*70}\n")
# Try to connect
url = f"http://{ip}:{self.target_port}"
# Test the connection
import requests
try:
response = requests.get(
f"{url}/api/status",
timeout=2
)
if response.status_code == 200:
data = response.json()
if data.get("server_type") == "moonlight_drivein":
# Success!
self.root.after(0, lambda: self.connection_success(url))
return
except:
pass
# Failed
self.root.after(0, self.connection_failed)
import threading
threading.Thread(target=resolve_and_connect, daemon=True).start()
def connection_failed(self):
"""Handle connection failure with smart reconnect"""
super().connection_failed()
self.log("Will retry in 5 seconds...", "INFO")
# Auto-retry after 5 seconds
self.root.after(5000, self.connect_to_server)
def print_banner():
"""Print startup banner"""
print("\n" + "="*70)
print(" " * 20 + "NFC Network Client - Smart Auto-Connect")
print("="*70)
print(f"\nTarget device: {CONFIG['device_name']}")
print(f"Fallback IP: {CONFIG['fallback_ip']}")
print(f"Port: {CONFIG['port']}")
print("\n" + "="*70)
def load_config() -> dict:
"""Load configuration from environment or defaults"""
return {
'device_name': os.environ.get('DRIVEIN_DEVICE', 'drive-in'),
'fallback_ip': os.environ.get('DRIVEIN_IP', '100.94.163.117'),
'port': int(os.environ.get('DRIVEIN_PORT', '8547'))
}
# Global config
CONFIG = load_config()
def main():
"""Main entry point"""
print(f"\nNFC Network Client - Smart Auto-Connect v{SMART_CLIENT_VERSION}")
print(f"Based on NFC Network Client v{BASE_VERSION}")
print("="*70)
print_banner()
# Create Tkinter root
root = tk.Tk()
try:
# Create and run smart client
app = SmartAutoConnectClient(
root,
device_name=CONFIG['device_name'],
fallback_ip=CONFIG['fallback_ip'],
port=CONFIG['port']
)
print("Client started successfully!")
print("Window should appear shortly...")
print("="*70 + "\n")
root.mainloop()
except Exception as e:
print(f"\nERROR: Client failed to start")
print(f"Error: {e}")
import traceback
traceback.print_exc()
return 1
finally:
if hasattr(app, 'keyboard_listener') and app.keyboard_listener:
app.keyboard_listener.stop()
return 0
if __name__ == "__main__":
sys.exit(main())