✨ 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:
+15
-16
@@ -41,31 +41,30 @@ def _run_command(
|
||||
timeout: int = 300,
|
||||
callback: ProgressCallback = None,
|
||||
) -> subprocess.CompletedProcess[str]:
|
||||
"""Run a subprocess command with progress reporting.
|
||||
"""Run a subprocess command with real-time streaming output.
|
||||
|
||||
Uses subprocess_utils.run_streaming to stream stdout/stderr in
|
||||
real time, parsing for warnings, errors, and progress.
|
||||
|
||||
Args:
|
||||
cmd: Command and arguments.
|
||||
timeout: Maximum seconds to wait (flash ops can take minutes).
|
||||
timeout: Maximum seconds.
|
||||
callback: Optional callback(status, message).
|
||||
|
||||
Returns:
|
||||
CompletedProcess result.
|
||||
"""
|
||||
from subprocess_utils import run_streaming
|
||||
|
||||
if callback:
|
||||
callback("running", f"Executing: {' '.join(cmd)}")
|
||||
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
)
|
||||
if callback:
|
||||
if result.returncode == 0:
|
||||
callback("complete", f"{cmd[0]} succeeded")
|
||||
else:
|
||||
callback("error", f"{cmd[0]} failed (exit {result.returncode})")
|
||||
return result
|
||||
try:
|
||||
return run_streaming(cmd, timeout=timeout, callback=callback)
|
||||
except subprocess.TimeoutExpired:
|
||||
if callback:
|
||||
callback("error", f"{cmd[0]} timed out after {timeout}s")
|
||||
raise
|
||||
|
||||
|
||||
def _build_program_flash_cmd_base(
|
||||
@@ -135,7 +134,7 @@ def wipe_flash(
|
||||
cmd += ["-erase_only"] # Erase sectors matching the BIN size (safer than -erase_all)
|
||||
|
||||
try:
|
||||
result = _run_command(cmd, timeout=300, callback=callback)
|
||||
result = _run_command(cmd, timeout=config.step_timeouts.get("flash_erase", 120), callback=callback)
|
||||
success = result.returncode == 0
|
||||
return FlashResult(
|
||||
step="wipe",
|
||||
@@ -195,7 +194,7 @@ def program_flash_bin(
|
||||
]
|
||||
|
||||
try:
|
||||
result = _run_command(cmd, timeout=600, callback=callback)
|
||||
result = _run_command(cmd, timeout=config.step_timeouts.get("flash_program", 600), callback=callback)
|
||||
success = result.returncode == 0
|
||||
return FlashResult(
|
||||
step="program",
|
||||
|
||||
Reference in New Issue
Block a user