fix: skip marking, fixed panel ratio, dialog hints, 30s boot, ping+UDP fallback

- TftpSubStepFrame.set_step_skipped(): ◌ Skip in WARNING color
- _reload_config(): suppress callbacks to avoid duplicate log/status wipe
- _log_all_file_info(): removed clear_info(), updates in-place per-key
- Removed duplicate _log_file_info method
- main_frame grid: uniform col group for strict 7:3 ratio
- FileSelector browse title shows expected file type hint
- boot_wait_delay: default 10→30s
- Step 4 IP fallback: ping first, then UDP scan, extra boot_delay retry
This commit is contained in:
2026-06-12 16:10:28 +08:00
parent 639f2e3ce6
commit 5ed195e2d6
3 changed files with 98 additions and 59 deletions
+1 -1
View File
@@ -75,7 +75,7 @@ class Config:
ping_count: int = 3 ping_count: int = 3
uart_delay: int = 3 uart_delay: int = 3
inter_step_delay: int = 2 inter_step_delay: int = 2
boot_wait_delay: int = 10 boot_wait_delay: int = 30
step_timeouts: dict[str, int] = field(default_factory=lambda: {}) step_timeouts: dict[str, int] = field(default_factory=lambda: {})
# Internal: path to the config file on disk # Internal: path to the config file on disk
+58 -31
View File
@@ -222,8 +222,15 @@ class MainWindow(ctk.CTk):
try: try:
fresh = Config.from_file(user_path) fresh = Config.from_file(user_path)
self._config = fresh self._config = fresh
# Update UI selectors with resolved (absolute) paths — selectors # Update UI selectors — suppress callbacks to avoid
# display only the filename internally. # redundant file-info logging and status-panel rewrites.
_sels = [
self._bit_selector, self._elf_selector,
self._bootloader_bin_selector, self._fsbl_selector,
self._firmware_bin_selector,
]
for s in _sels:
s._suppress_callback = True
for attr, selector in [ for attr, selector in [
("bootloader_bit_path", self._bit_selector), ("bootloader_bit_path", self._bit_selector),
("bootloader_elf_path", self._elf_selector), ("bootloader_elf_path", self._elf_selector),
@@ -236,6 +243,8 @@ class MainWindow(ctk.CTk):
resolved = fresh.resolve_path(val) resolved = fresh.resolve_path(val)
selector.set_relative_path(val) selector.set_relative_path(val)
selector.set_path(str(resolved)) selector.set_path(str(resolved))
for s in _sels:
s._suppress_callback = False
# Update IP # Update IP
if self._ip_string_var: if self._ip_string_var:
self._ip_string_var.set(fresh.zynq_ip) self._ip_string_var.set(fresh.zynq_ip)
@@ -245,8 +254,6 @@ class MainWindow(ctk.CTk):
except Exception: except Exception:
pass # Keep existing config on error pass # Keep existing config on error
self._log_all_file_info()
def _sync_config_from_ui(self) -> None: def _sync_config_from_ui(self) -> None:
"""Read current UI values and update the Config object. """Read current UI values and update the Config object.
@@ -387,26 +394,14 @@ class MainWindow(ctk.CTk):
f"{info.size_human} | CRC {info.crc32_hex} | {info.mtime_iso}" f"{info.size_human} | CRC {info.crc32_hex} | {info.mtime_iso}"
) )
def _log_file_info(self, info: FileInfo) -> None:
"""Log file metadata in a human-readable format.
Args:
info: FileInfo dataclass with computed metadata.
"""
filename = os.path.basename(info.path) if info.path else "(unknown)"
if not info.exists:
self._log_message(f"{filename}: file not found")
return
self._log_message(
f"{filename}: "
f"{info.size_human} | CRC {info.crc32_hex} | {info.mtime_iso}"
)
def _log_all_file_info(self) -> None: def _log_all_file_info(self) -> None:
"""Display file metadata in the status info panel.""" """Update file metadata in the status info panel (preserves other info).
Each file line is updated in-place via ``set_info()`` — existing
Zynq info, tool versions, etc. are left untouched.
"""
if not self._config: if not self._config:
return return
self._status.clear_info()
paths = [ paths = [
("FSBL ELF", self._config.fsbl_elf_path), ("FSBL ELF", self._config.fsbl_elf_path),
("Bitstream", self._config.bootloader_bit_path), ("Bitstream", self._config.bootloader_bit_path),
@@ -416,6 +411,7 @@ class MainWindow(ctk.CTk):
] ]
for label, path in paths: for label, path in paths:
if not path: if not path:
self._status.set_info(label, "(not set)")
continue continue
resolved = self._config.resolve_path(path) resolved = self._config.resolve_path(path)
info = compute_file_info(resolved) info = compute_file_info(resolved)
@@ -435,8 +431,8 @@ class MainWindow(ctk.CTk):
self.main_frame = ctk.CTkScrollableFrame(self) self.main_frame = ctk.CTkScrollableFrame(self)
self.main_frame.pack(fill="both", expand=True, padx=PADDING_LARGE, pady=PADDING_LARGE) self.main_frame.pack(fill="both", expand=True, padx=PADDING_LARGE, pady=PADDING_LARGE)
self.main_frame.grid_rowconfigure(1, weight=1) self.main_frame.grid_rowconfigure(1, weight=1)
self.main_frame.grid_columnconfigure(0, weight=7) self.main_frame.grid_columnconfigure(0, weight=7, uniform="main_col")
self.main_frame.grid_columnconfigure(1, weight=3) self.main_frame.grid_columnconfigure(1, weight=3, uniform="main_col")
# ── Header ── # ── Header ──
self._build_header(self.main_frame) self._build_header(self.main_frame)
@@ -771,7 +767,7 @@ class MainWindow(ctk.CTk):
self._download_verify_var = ctk.BooleanVar(value=self._config.download_verify) self._download_verify_var = ctk.BooleanVar(value=self._config.download_verify)
self._download_verify_cb = ctk.CTkCheckBox( self._download_verify_cb = ctk.CTkCheckBox(
flash_frame, flash_frame,
text="下载校验 (Step 4.2 Download Verify)", text="下载校验 + CRC (Step 4.2)",
variable=self._download_verify_var, variable=self._download_verify_var,
font=FONT_SMALL, font=FONT_SMALL,
command=self._on_download_verify_toggle, command=self._on_download_verify_toggle,
@@ -1417,11 +1413,16 @@ class MainWindow(ctk.CTk):
self._log_message(" ⏳ Waiting 2s before download...") self._log_message(" ⏳ Waiting 2s before download...")
time.sleep(2) time.sleep(2)
# 4b: TFTP Download Verify (skipped if disabled in config) # 4b+4c: TFTP Download Verify + CRC Check
if not self._config.download_verify: if not self._config.download_verify:
self._log_message("Step 4b: Download verify — skipped (disabled in config)") self._log_message("Step 4b+4c: Download verify & CRC — skipped (disabled in config)")
all_results.append(True) all_results.append(True) # 4b
all_results.append(True) # 4c
if hasattr(self, '_tftp_sub_step_frame'):
self._tftp_sub_step_frame.set_step_skipped("Download Verify")
self._tftp_sub_step_frame.set_step_skipped("CRC Check")
else: else:
# 4b: Download Verify
self._log_message("Step 4b: TFTP download verify...") self._log_message("Step 4b: TFTP download verify...")
self._tftp_phase = "Download Verify" self._tftp_phase = "Download Verify"
if hasattr(self, '_tftp_sub_step_frame'): if hasattr(self, '_tftp_sub_step_frame'):
@@ -1445,8 +1446,7 @@ class MainWindow(ctk.CTk):
if not all_results[-1]: if not all_results[-1]:
self._log_message(" Skipping CRC check (download failed)") self._log_message(" Skipping CRC check (download failed)")
return False else:
# 4c: CRC Check # 4c: CRC Check
self._log_message("Step 4c: CRC check...") self._log_message("Step 4c: CRC check...")
if hasattr(self, '_tftp_sub_step_frame'): if hasattr(self, '_tftp_sub_step_frame'):
@@ -1468,7 +1468,7 @@ class MainWindow(ctk.CTk):
all_results.append(False) all_results.append(False)
if not all_results[-1]: if not all_results[-1]:
self._log_message(" Skipping reboot (CRC mismatch)") self._log_message(" Skipping reboot (download/CRC failed)")
return False return False
# 4d: Reboot # 4d: Reboot
@@ -1565,11 +1565,33 @@ class MainWindow(ctk.CTk):
discovered_ip = parsed.ip_address discovered_ip = parsed.ip_address
self._log_message(f" IP from UART buffer: {discovered_ip}") self._log_message(f" IP from UART buffer: {discovered_ip}")
# Discover actual IP after reboot (may have changed via DHCP) # Fallback: if no IP from UART, try ping → UDP scan, then extra delay
if not discovered_ip: if not discovered_ip:
self._log_message(" ⏳ Network stack — waiting 3s...") self._log_message(" ⏳ Network stack — waiting 3s...")
time.sleep(3) time.sleep(3)
# Try ping configured IP first (fast, reliable)
self._log_message(f" Pinging {self._config.zynq_ip}...")
ok, _ = ping_ip(self._config.zynq_ip, timeout=2)
if ok:
discovered_ip = self._config.zynq_ip
self._log_message(f" ✓ Zynq responds to ping at {discovered_ip}")
else:
# Fallback: UDP broadcast scan
self._log_message(" Scanning for Zynq IP (192.168.100.1130)...") self._log_message(" Scanning for Zynq IP (192.168.100.1130)...")
discovered_ip = self._discover_zynq_ip()
if discovered_ip:
self._log_message(f" ✓ Found Zynq at {discovered_ip}")
else:
self._log_message(" ⚠ Zynq not found — extra boot delay...")
self._log_message(f" ⏳ Waiting {boot_delay}s for late boot...")
time.sleep(boot_delay)
# Retry ping + scan after extra delay
ok2, _ = ping_ip(self._config.zynq_ip, timeout=2)
if ok2:
discovered_ip = self._config.zynq_ip
self._log_message(f" ✓ Zynq responds to ping at {discovered_ip}")
else:
discovered_ip = self._discover_zynq_ip() discovered_ip = self._discover_zynq_ip()
if discovered_ip: if discovered_ip:
self._log_message(f" ✓ Found Zynq at {discovered_ip}") self._log_message(f" ✓ Found Zynq at {discovered_ip}")
@@ -1799,6 +1821,11 @@ class MainWindow(ctk.CTk):
results.append(False) results.append(False)
self._set_step_status(i, "error") self._set_step_status(i, "error")
self._log_message(f" Exception in step {i+1}: {e}") self._log_message(f" Exception in step {i+1}: {e}")
# Mark sub-step frames as error too
if i == 1 and hasattr(self, '_sub_step_frame'):
self._sub_step_frame.set_phase("error")
if i == 3 and hasattr(self, '_tftp_sub_step_frame'):
self._tftp_sub_step_frame.set_step_error("Boot Verify")
self._progress.set_value((step_idx + 1) / total) self._progress.set_value((step_idx + 1) / total)
+13 -1
View File
@@ -544,6 +544,7 @@ class FileSelector(ctk.CTkFrame):
self._callback = callback self._callback = callback
self._file_types = file_types or [("All files", "*")] self._file_types = file_types or [("All files", "*")]
self._hint_text = label_text.rstrip(":") # e.g. "FSBL ELF (.elf)"
self._full_path: str = "" # absolute path (internal) self._full_path: str = "" # absolute path (internal)
self._relative_path: str = "" # relative path (synced to Config) self._relative_path: str = "" # relative path (synced to Config)
self._display_text = ctk.StringVar(value="") self._display_text = ctk.StringVar(value="")
@@ -593,7 +594,7 @@ class FileSelector(ctk.CTkFrame):
from tkinter import filedialog from tkinter import filedialog
file_path = filedialog.askopenfilename( file_path = filedialog.askopenfilename(
title="Select file", title=f"Select {self._hint_text}",
filetypes=self._file_types, filetypes=self._file_types,
) )
if file_path: if file_path:
@@ -974,6 +975,17 @@ class TftpSubStepFrame(ctk.CTkFrame):
item["label"].configure(text_color=_C_ERROR) item["label"].configure(text_color=_C_ERROR)
self._stop_pulse() self._stop_pulse()
def set_step_skipped(self, phase: str) -> None:
"""Mark a specific step as skipped (warning color, Skip text)."""
idx = self._substep_names.index(phase) if phase in self._substep_names else -1
if idx < 0:
return
item = self._sub_items[idx]
item["dot"].configure(text="", text_color=WARNING_COLOR)
item["label"].configure(text_color=WARNING_COLOR)
item["pct"].configure(text="Skip", text_color=WARNING_COLOR)
self._stop_pulse()
def _start_pulse(self, idx: int) -> None: def _start_pulse(self, idx: int) -> None:
"""Start pulsing the running sub-step.""" """Start pulsing the running sub-step."""
_LIGHT = RUNNING_FADE_LIGHT _LIGHT = RUNNING_FADE_LIGHT