Fix compatibility with NFCNetworkClient v1.1.0 - properly pass root parameter
This commit is contained in:
@@ -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())
|
||||||
|
|||||||
Reference in New Issue
Block a user