✨ feat: UART monitor, step timeouts, and non-blocking subprocess output
Based on real hardware timing data from XC7Z100 testing: UART Monitoring: - New UartMonitor class in serial_monitor.py: background thread reads serial output continuously, parses for IP/version/boot messages - GUI starts monitor when workflow runs, stops on completion/close - Serial lines flow into log display in real time Step Timing: - New step_timeouts config (experience × 2): check_env:30s flash_erase:120s flash_program:600s bitstream:60s elf_download:60s tftp_reboot:120s - GUI shows estimated timeout before each step - Shows actual elapsed time after each step completes - All subprocess calls now use config-driven timeouts Non-blocking Output: - New subprocess_utils.py: run_streaming() uses Popen + threaded stdout/stderr readers, parses WARNING/ERROR/progress% in real time - flash_programmer and bitstream_programmer use it - Callbacks receive categorized events (out/err/warn/error/progress/done)
This commit is contained in:
+50
-1
@@ -24,7 +24,7 @@ from ip_verifier import ping_ip
|
||||
from tftp_manager import tftp_upload_verify, TftpResult
|
||||
from reboot_manager import reboot_zynq, RebootResult
|
||||
from boot_verifier import verify_boot, verify_zynq_status, BootVerificationResult
|
||||
from serial_monitor import detect_serial_ports, parse_boot_output, check_uart_available
|
||||
from serial_monitor import detect_serial_ports, parse_boot_output, check_uart_available, UartMonitor
|
||||
from gui.styles import (
|
||||
WINDOW_WIDTH,
|
||||
WINDOW_HEIGHT,
|
||||
@@ -79,6 +79,7 @@ class MainWindow(ctk.CTk):
|
||||
self._uart_message: str = ""
|
||||
self._zynq_jtag_present: bool = False
|
||||
self._zynq_jtag_info = None
|
||||
self._uart_monitor: UartMonitor | None = None
|
||||
|
||||
# StringVar references for config sync
|
||||
self._ip_string_var: ctk.StringVar | None = None
|
||||
@@ -961,6 +962,15 @@ class MainWindow(ctk.CTk):
|
||||
self._run_step_3_load_bootloader,
|
||||
self._run_step_4_load_main_program,
|
||||
]
|
||||
step_timeout_keys = [
|
||||
("check_env", 30),
|
||||
("flash_program", 600),
|
||||
("bitstream", 60),
|
||||
("tftp_reboot", 120),
|
||||
]
|
||||
|
||||
# Start UART monitor if available
|
||||
self._start_uart_monitor()
|
||||
|
||||
# Determine end index based on single_step flag
|
||||
end_index = start_index + 1 if single_step else len(step_funcs)
|
||||
@@ -970,12 +980,21 @@ class MainWindow(ctk.CTk):
|
||||
for i in range(start_index, end_index):
|
||||
step_idx = i - start_index
|
||||
self._set_step_status(i, "running")
|
||||
|
||||
# Log step timeout estimate
|
||||
timeout_key, default_timeout = step_timeout_keys[i] if i < len(step_timeout_keys) else ("", 30)
|
||||
step_timeout = self._config.step_timeouts.get(timeout_key, default_timeout) if self._config else default_timeout
|
||||
self._log_message(f" ⏱ Step {i+1} timeout: {step_timeout}s")
|
||||
|
||||
self._progress.set_value(step_idx / total)
|
||||
t_step_start = time.time()
|
||||
|
||||
try:
|
||||
success = step_funcs[i]()
|
||||
results.append(success)
|
||||
self._set_step_status(i, "complete" if success else "error")
|
||||
elapsed = time.time() - t_step_start
|
||||
self._log_message(f" ⏱ Step {i+1} completed in {elapsed:.0f}s")
|
||||
except Exception as e:
|
||||
results.append(False)
|
||||
self._set_step_status(i, "error")
|
||||
@@ -1020,6 +1039,35 @@ class MainWindow(ctk.CTk):
|
||||
# Save config after running
|
||||
self._save_config()
|
||||
|
||||
# Stop UART monitor
|
||||
self._stop_uart_monitor()
|
||||
|
||||
# ── UART Monitor ──────────────────────────────────────────
|
||||
|
||||
def _start_uart_monitor(self) -> None:
|
||||
"""Start background serial monitoring if UART is available."""
|
||||
if not self._uart_available or not self._config:
|
||||
return
|
||||
port = self._config.serial_port
|
||||
if not port:
|
||||
return
|
||||
|
||||
self._uart_monitor = UartMonitor(port, self._config.serial_baudrate)
|
||||
self._uart_monitor.on_line = lambda line: self._log_message(f" [UART] {line}")
|
||||
self._uart_monitor.on_boot_info = lambda info: (
|
||||
self._log_message(f" [UART] Boot detected: IP={info.ip_address}, Ver={info.version}")
|
||||
if info.ip_address or info.version else None
|
||||
)
|
||||
self._uart_monitor.on_error = lambda e: self._log_message(f" [UART] Error: {e}")
|
||||
if self._uart_monitor.start():
|
||||
self._log_message(" [UART] Serial monitor started")
|
||||
|
||||
def _stop_uart_monitor(self) -> None:
|
||||
"""Stop background serial monitoring."""
|
||||
if self._uart_monitor:
|
||||
self._uart_monitor.stop()
|
||||
self._uart_monitor = None
|
||||
|
||||
# ── Serial ─────────────────────────────────────────────────
|
||||
|
||||
def _refresh_ports(self) -> None:
|
||||
@@ -1040,6 +1088,7 @@ class MainWindow(ctk.CTk):
|
||||
|
||||
def _on_close(self) -> None:
|
||||
"""Handle window close event: save config and cleanup."""
|
||||
self._stop_uart_monitor()
|
||||
if self._config:
|
||||
self._sync_config_from_ui() # Sync UI values first
|
||||
# Save to user config file (config.yaml)
|
||||
|
||||
Reference in New Issue
Block a user