"""Boot verification module for Zynq Flasher GUI. Verifies that the Zynq device has booted successfully after a reboot operation, by checking network connectivity and serial output. """ from __future__ import annotations import time from dataclasses import dataclass from typing import Callable from config_manager import Config from ip_verifier import ping_ip, ping_ip_verbose from serial_monitor import detect_serial_ports, parse_boot_output, read_serial_stream @dataclass class BootVerificationResult: """Result of a boot verification check.""" success: bool message: str ip_reachable: bool = False serial_found: bool = False boot_info: dict | None = None ProgressCallback = Callable[[str, str], None] | None def verify_boot( config: Config, timeout: int | None = None, max_retries: int = 5, callback: ProgressCallback = None, ) -> BootVerificationResult: """Verify the Zynq device has booted successfully. Performs multiple checks: 1. Ping the device IP to verify network connectivity 2. Check serial port for boot messages 3. Parse boot output for IP, version, and status Args: config: Application configuration. timeout: Seconds to wait per check. max_retries: Maximum number of retry attempts. callback: Optional progress callback. Returns: BootVerificationResult with verification status. """ timeout = timeout or config.ping_timeout ip = config.zynq_ip if callback: callback("start", f"Verifying boot on {ip}...") # Check 1: Network connectivity ip_reachable = False for attempt in range(1, max_retries + 1): if callback: callback("progress", f"Ping attempt {attempt}/{max_retries}...") if ping_ip(ip, 1, timeout): ip_reachable = True if callback: callback("complete", f"Device {ip} is reachable") break time.sleep(2) # Check 2: Serial port boot messages serial_found = False boot_info: dict = {} ports = detect_serial_ports() if ports and config.serial_port: for port_info in ports: if port_info.device == config.serial_port: try: lines = read_serial_stream( config.serial_port, config.serial_baudrate, timeout=5, ) parsed = parse_boot_output(lines) if parsed.ip_address or parsed.boot_message or parsed.version: serial_found = True boot_info = { "ip_address": parsed.ip_address, "version": parsed.version, "boot_message": parsed.boot_message, "raw_lines": lines[-20:], # Last 20 lines } if callback: callback("complete", "Boot messages detected on serial") break except Exception as e: if callback: callback("progress", f"Serial read error on {port_info.device}: {e}") # Overall result success = ip_reachable or serial_found message_parts = [] if ip_reachable: message_parts.append(f"IP {ip} reachable") if serial_found: message_parts.append("serial boot info found") if not success: message_parts.append("no boot indicators detected") message = "; ".join(message_parts) if message_parts else "Verification inconclusive" if callback: callback("complete" if success else "error", message) return BootVerificationResult( success=success, message=message, ip_reachable=ip_reachable, serial_found=serial_found, boot_info=boot_info if boot_info else {}, ) def verify_zynq_status( config: Config, timeout: int | None = None, max_retries: int = 3, uart_available: bool = True, callback: ProgressCallback = None, ) -> BootVerificationResult: """Quickly verify Zynq is responding after a download operation. Performs a fast ping check and optionally a serial port read. Unlike verify_boot, this is a lightweight check meant to confirm the device is alive after flash/TFTP operations (before reboot). Args: config: Application configuration. timeout: Seconds per ping attempt. max_retries: Number of ping retry attempts. uart_available: Whether UART serial port is available. callback: Optional progress callback. Returns: BootVerificationResult (ip_reachable and serial_found reflect the quick check results). """ timeout = timeout or config.ping_timeout ip = config.zynq_ip if callback: callback("start", f"Checking Zynq status on {ip}...") # Check 1: Ping ip_reachable = False for attempt in range(1, max_retries + 1): if callback: callback("progress", f"Ping attempt {attempt}/{max_retries}...") if ping_ip(ip, 1, timeout): ip_reachable = True if callback: callback("complete", f"Device {ip} is reachable") break time.sleep(1) # Check 2: Serial (only if UART is available) serial_found = False boot_info: dict = {} if uart_available and config.serial_port: ports = detect_serial_ports() if ports: for port_info in ports: if port_info.device == config.serial_port: try: lines = read_serial_stream( config.serial_port, config.serial_baudrate, timeout=3, ) parsed = parse_boot_output(lines) if parsed.ip_address or parsed.boot_message or parsed.version: serial_found = True boot_info = { "ip_address": parsed.ip_address, "version": parsed.version, "boot_message": parsed.boot_message, "raw_lines": lines[-20:], } if callback: callback("complete", "Boot messages detected on serial") break except Exception: pass # Non-fatal; fall through success = ip_reachable or serial_found message_parts = [] if ip_reachable: message_parts.append(f"IP {ip} reachable") if serial_found: message_parts.append("serial boot info found") if not success: message_parts.append("Zynq not responding") message = "; ".join(message_parts) if message_parts else "Check inconclusive" if callback: callback("complete" if success else "error", message) return BootVerificationResult( success=success, message=message, ip_reachable=ip_reachable, serial_found=serial_found, boot_info=boot_info if boot_info else {}, ) def wait_for_boot( config: Config, timeout: int | None = None, poll_interval: float = 2.0, callback: ProgressCallback = None, ) -> BootVerificationResult: """Wait for the Zynq device to complete boot sequence. Continuously checks for boot indicators until timeout or success. Args: config: Application configuration. timeout: Maximum seconds to wait. poll_interval: Seconds between checks. callback: Optional progress callback. Returns: BootVerificationResult with final verification status. """ timeout = timeout or (config.reboot_timeout + 30) start_time = time.time() if callback: callback("start", f"Waiting for boot (timeout: {timeout}s)...") while time.time() - start_time < timeout: remaining = int(timeout - (time.time() - start_time)) if callback: callback("progress", f"Waiting... {remaining}s remaining") result = verify_boot(config, callback=callback) if result.success: return result time.sleep(poll_interval) if callback: callback("error", f"Boot verification timed out after {timeout}s") return BootVerificationResult( success=False, message=f"Boot verification timed out after {timeout}s", )