From 5ed195e2d68c81fad69ed2bb1dae6e03a97df7ff Mon Sep 17 00:00:00 2001 From: Jeremy Shen Date: Fri, 12 Jun 2026 16:10:28 +0800 Subject: [PATCH] fix: skip marking, fixed panel ratio, dialog hints, 30s boot, ping+UDP fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/config_manager.py | 2 +- src/gui/main_window.py | 141 ++++++++++++++++++++++++----------------- src/gui/widgets.py | 14 +++- 3 files changed, 98 insertions(+), 59 deletions(-) diff --git a/src/config_manager.py b/src/config_manager.py index 0b5201d..a0bb389 100644 --- a/src/config_manager.py +++ b/src/config_manager.py @@ -75,7 +75,7 @@ class Config: ping_count: int = 3 uart_delay: int = 3 inter_step_delay: int = 2 - boot_wait_delay: int = 10 + boot_wait_delay: int = 30 step_timeouts: dict[str, int] = field(default_factory=lambda: {}) # Internal: path to the config file on disk diff --git a/src/gui/main_window.py b/src/gui/main_window.py index af08884..52a3cc7 100644 --- a/src/gui/main_window.py +++ b/src/gui/main_window.py @@ -222,8 +222,15 @@ class MainWindow(ctk.CTk): try: fresh = Config.from_file(user_path) self._config = fresh - # Update UI selectors with resolved (absolute) paths — selectors - # display only the filename internally. + # Update UI selectors — suppress callbacks to avoid + # 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 [ ("bootloader_bit_path", self._bit_selector), ("bootloader_elf_path", self._elf_selector), @@ -236,6 +243,8 @@ class MainWindow(ctk.CTk): resolved = fresh.resolve_path(val) selector.set_relative_path(val) selector.set_path(str(resolved)) + for s in _sels: + s._suppress_callback = False # Update IP if self._ip_string_var: self._ip_string_var.set(fresh.zynq_ip) @@ -245,8 +254,6 @@ class MainWindow(ctk.CTk): except Exception: pass # Keep existing config on error - self._log_all_file_info() - def _sync_config_from_ui(self) -> None: """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}" ) - 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: - """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: return - self._status.clear_info() paths = [ ("FSBL ELF", self._config.fsbl_elf_path), ("Bitstream", self._config.bootloader_bit_path), @@ -416,6 +411,7 @@ class MainWindow(ctk.CTk): ] for label, path in paths: if not path: + self._status.set_info(label, "(not set)") continue resolved = self._config.resolve_path(path) info = compute_file_info(resolved) @@ -435,8 +431,8 @@ class MainWindow(ctk.CTk): self.main_frame = ctk.CTkScrollableFrame(self) 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_columnconfigure(0, weight=7) - self.main_frame.grid_columnconfigure(1, weight=3) + self.main_frame.grid_columnconfigure(0, weight=7, uniform="main_col") + self.main_frame.grid_columnconfigure(1, weight=3, uniform="main_col") # ── Header ── 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_cb = ctk.CTkCheckBox( flash_frame, - text="下载校验 (Step 4.2 Download Verify)", + text="下载校验 + CRC (Step 4.2)", variable=self._download_verify_var, font=FONT_SMALL, command=self._on_download_verify_toggle, @@ -1417,11 +1413,16 @@ class MainWindow(ctk.CTk): self._log_message(" ⏳ Waiting 2s before download...") 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: - self._log_message("Step 4b: Download verify — skipped (disabled in config)") - all_results.append(True) + self._log_message("Step 4b+4c: Download verify & CRC — skipped (disabled in config)") + 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: + # 4b: Download Verify self._log_message("Step 4b: TFTP download verify...") self._tftp_phase = "Download Verify" if hasattr(self, '_tftp_sub_step_frame'): @@ -1443,32 +1444,31 @@ class MainWindow(ctk.CTk): self._tftp_sub_step_frame.set_step_error("Download Verify") all_results.append(False) - if not all_results[-1]: - self._log_message(" Skipping CRC check (download failed)") - return False - - # 4c: CRC Check - self._log_message("Step 4c: CRC check...") - if hasattr(self, '_tftp_sub_step_frame'): - self._tftp_sub_step_frame.set_phase("CRC Check") - original_crc = _compute_crc32(bin_path) - downloaded_crc = download_result.crc_local - crc_match = original_crc == downloaded_crc - self._log_message(f" Original CRC: {original_crc:#010x}") - self._log_message(f" Downloaded CRC: {downloaded_crc:#010x}") - if crc_match: - self._log_message(" ✓ CRC match") - if hasattr(self, '_tftp_sub_step_frame'): - self._tftp_sub_step_frame.set_step_complete("CRC Check") - all_results.append(True) - else: - self._log_message(" ✗ CRC mismatch") - if hasattr(self, '_tftp_sub_step_frame'): - self._tftp_sub_step_frame.set_step_error("CRC Check") - all_results.append(False) + if not all_results[-1]: + self._log_message(" Skipping CRC check (download failed)") + else: + # 4c: CRC Check + self._log_message("Step 4c: CRC check...") + if hasattr(self, '_tftp_sub_step_frame'): + self._tftp_sub_step_frame.set_phase("CRC Check") + original_crc = _compute_crc32(bin_path) + downloaded_crc = download_result.crc_local + crc_match = original_crc == downloaded_crc + self._log_message(f" Original CRC: {original_crc:#010x}") + self._log_message(f" Downloaded CRC: {downloaded_crc:#010x}") + if crc_match: + self._log_message(" ✓ CRC match") + if hasattr(self, '_tftp_sub_step_frame'): + self._tftp_sub_step_frame.set_step_complete("CRC Check") + all_results.append(True) + else: + self._log_message(" ✗ CRC mismatch") + if hasattr(self, '_tftp_sub_step_frame'): + self._tftp_sub_step_frame.set_step_error("CRC Check") + all_results.append(False) if not all_results[-1]: - self._log_message(" Skipping reboot (CRC mismatch)") + self._log_message(" Skipping reboot (download/CRC failed)") return False # 4d: Reboot @@ -1565,16 +1565,38 @@ class MainWindow(ctk.CTk): discovered_ip = parsed.ip_address 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: self._log_message(" ⏳ Network stack — waiting 3s...") time.sleep(3) - self._log_message(" Scanning for Zynq IP (192.168.100.11–30)...") - discovered_ip = self._discover_zynq_ip() - if discovered_ip: - self._log_message(f" ✓ Found Zynq at {discovered_ip}") + + # 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: - self._log_message(" ⚠ Zynq not found on any scanned IP") + # Fallback: UDP broadcast scan + self._log_message(" Scanning for Zynq IP (192.168.100.11–30)...") + 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() + if discovered_ip: + self._log_message(f" ✓ Found Zynq at {discovered_ip}") + else: + self._log_message(" ⚠ Zynq not found on any scanned IP") if discovered_ip and discovered_ip != self._config.zynq_ip: self._log_message( @@ -1799,6 +1821,11 @@ class MainWindow(ctk.CTk): results.append(False) self._set_step_status(i, "error") 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) diff --git a/src/gui/widgets.py b/src/gui/widgets.py index 7e6b060..a664996 100644 --- a/src/gui/widgets.py +++ b/src/gui/widgets.py @@ -544,6 +544,7 @@ class FileSelector(ctk.CTkFrame): self._callback = callback 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._relative_path: str = "" # relative path (synced to Config) self._display_text = ctk.StringVar(value="") @@ -593,7 +594,7 @@ class FileSelector(ctk.CTkFrame): from tkinter import filedialog file_path = filedialog.askopenfilename( - title="Select file", + title=f"Select {self._hint_text}", filetypes=self._file_types, ) if file_path: @@ -974,6 +975,17 @@ class TftpSubStepFrame(ctk.CTkFrame): item["label"].configure(text_color=_C_ERROR) 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: """Start pulsing the running sub-step.""" _LIGHT = RUNNING_FADE_LIGHT