- 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
264 lines
8.5 KiB
Python
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",
|
|
)
|