Files
Zynq_Flasher/src/boot_verifier.py
T
Jeremy Shen 3a9bc5d9ed feat(uart): add UART availability check, inter-step delays, and post-download Zynq status verification
- Add check_uart_available() to serial_monitor.py — detects port availability
- Add verify_zynq_status() to boot_verifier.py — lightweight post-download check
- Add uart_delay / inter_step_delay config fields (defaults: 3s / 2s)
- Step 1: Check UART availability, show non-blocking 'UART 不可用' notification
- Step 4: Add post-download Zynq status check (ping + serial) after TFTP
- _run_steps_worker: Add configurable inter-step delays between all steps
- Update step labels (4a→4b→4c→4d) for clarity
2026-06-09 17:53:48 +08:00

264 lines
8.5 KiB
Python

"""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",
)