feat: UART boot wait, serial validation, xsct logging, layout fix

Config:
- Add boot_wait_delay (default 10s) for post-reboot wait time

Layout:
- Restore row weights for proportional scaling when maximized
- CTkScrollableFrame handles overflow when window is small

Step 3:
- Log xsct/vivado tool output (last 20 lines per result)
  for detailed diagnostics

Step 4:
- UART boot wait: monitor for IP/version via UART after reboot
- If UART unavailable, fall back to config.boot_wait_delay
- Reduces unnecessary wait time when boot is detected early

Serial:
- Real-time port validation on selection change (CTkComboBox command)
- Persistent UART unavailable warning until valid port selected
- Auto-restart UART monitor on port switch
- Graceful cross-platform handling
This commit is contained in:
2026-06-11 14:44:58 +08:00
parent 9b07a4d5ca
commit f6cf5de96f
6 changed files with 406 additions and 108 deletions
+74
View File
@@ -268,6 +268,7 @@ class MainWindow(ctk.CTk):
# Scrollable main container — scrollbar appears when content overflows
main_frame = ctk.CTkScrollableFrame(self)
main_frame.pack(fill="both", expand=True, padx=PADDING_LARGE, pady=PADDING_LARGE)
main_frame.grid_rowconfigure(1, weight=1)
main_frame.grid_columnconfigure(0, weight=1)
# ── Header ──
@@ -285,6 +286,7 @@ class MainWindow(ctk.CTk):
# ── Right Panel: Configuration ──
right_frame = ctk.CTkFrame(main_frame)
right_frame.grid(row=1, column=1, sticky="nsew", padx=(PADDING, 0))
right_frame.grid_rowconfigure(3, weight=1)
right_frame.grid_columnconfigure(0, weight=1)
self._build_config_panel(right_frame)
@@ -622,6 +624,7 @@ class MainWindow(ctk.CTk):
port_frame,
values=[""] + [p.device for p in detect_serial_ports()],
variable=self._port_var,
command=self._on_port_changed,
)
self._port_menu.grid(row=0, column=1, sticky="nsew", padx=PADDING_SMALL)
@@ -852,6 +855,49 @@ class MainWindow(ctk.CTk):
self._uart_warning_label.configure(text=msg)
self._uart_warning_label.pack(fill="x", padx=PADDING, pady=(0, PADDING_SMALL))
def _hide_uart_warning(self) -> None:
"""Hide the persistent UART warning label."""
self._uart_warning_label.pack_forget()
def _on_port_changed(self, new_port: str) -> None:
"""Handle serial port selection change — validate immediately.
Args:
new_port: The newly selected serial port path.
"""
if not new_port:
self._uart_available = False
self._show_uart_notification("No port selected")
if self._uart_monitor:
self._stop_uart_monitor()
return
# Attempt to open port to validate
try:
port = new_port
baudrate = self._config.serial_baudrate if self._config else 115200
available, reason = check_uart_available(port, baudrate)
self._uart_available = available
if available:
self._uart_message = reason
self._hide_uart_warning()
self._log_message(f" ✓ UART {port}: {reason}")
# If UART monitor was running, restart on new port
if self._uart_monitor and self._uart_monitor.is_running():
self._stop_uart_monitor()
self._start_uart_monitor()
else:
self._uart_available = False
self._uart_message = reason
self._show_uart_notification(reason)
if self._uart_monitor:
self._stop_uart_monitor()
except Exception as e:
self._uart_available = False
self._show_uart_notification(str(e))
if self._uart_monitor:
self._stop_uart_monitor()
def _run_step_2_program_flash(self) -> bool:
"""Step 2: Program and verify Flash using program_flash."""
if not self._config:
@@ -937,6 +983,11 @@ class MainWindow(ctk.CTk):
for result in results:
status = "" if result.success else ""
self._log_message(f" {status} {result.step}: {result.message}")
if result.output:
# Log tool output for diagnostics (truncated)
output_lines = result.output.strip().splitlines()
for line in output_lines[-20:]: # last 20 lines
self._log_message(f" [xsct] {line}")
if "DAP" in result.message and not result.success:
dap_fail = True
@@ -1077,6 +1128,29 @@ class MainWindow(ctk.CTk):
self._log_message(" Skipping boot verify (reboot failed)")
return False
# Wait for board to boot — UART if available, else config delay
boot_delay = self._config.boot_wait_delay
uart_alive = (self._uart_monitor is not None
and self._uart_monitor.is_running())
if uart_alive:
self._log_message(f" ⏳ Waiting for UART boot signal (max {boot_delay}s)...")
elapsed = 0.0
while elapsed < boot_delay:
info = self._uart_monitor.latest_info
if info.ip_address or info.version:
self._log_message(
f" ✓ Boot detected after {elapsed:.1f}s"
f" (IP={info.ip_address}, Ver={info.version})"
)
break
time.sleep(0.5)
elapsed += 0.5
else:
self._log_message(f" ⏱ UART timeout — no boot signal in {boot_delay}s")
else:
self._log_message(f" ⏳ UART unavailable — sleeping {boot_delay}s for boot...")
time.sleep(boot_delay)
# 4e: Verify boot
self._log_message("Step 4e: Verifying boot...")
if hasattr(self, '_tftp_sub_step_frame'):