diff --git a/config.yaml b/config.yaml index 2bff602..44aedee 100644 --- a/config.yaml +++ b/config.yaml @@ -1,7 +1,7 @@ bootloader_bin_path: /home/ly0kos/work/Verify/FW/tftp_app/bootloader/bootloader_z100_800M.bin bootloader_bit_path: /home/ly0kos/work/Verify/FW/tftp_app/bootloader/pl_arm_wrapper_hw_platform_0/pl_arm_wrapper.bit bootloader_elf_path: /home/ly0kos/work/Verify/FW/tftp_app/bootloader/app.elf -erase_all: false +erase_all: true firmware_bin_path: /home/ly0kos/work/Verify/FW/V1.3.1.3.3_06_01_pmu_cpld_check/V1.3.1.3.3_06_01_pmu_cpld_check.bin flash_model: s25fl256s1 flash_type: qspi-x4-single @@ -21,6 +21,5 @@ step_timeouts: tftp_reboot: 120 tftp_upload_name: z7bin uart_delay: 3 -xilinx_path: "" -vitis_path: '' +xilinx_path: /data/xilinx zynq_ip: 192.168.100.11 diff --git a/src/gui/main_window.py b/src/gui/main_window.py index d5470a4..7df3855 100644 --- a/src/gui/main_window.py +++ b/src/gui/main_window.py @@ -53,6 +53,7 @@ from gui.widgets import ( FileSelector, LogDisplay, SubStepFrame, + UartMonitorWindow, ) @@ -591,14 +592,14 @@ class MainWindow(ctk.CTk): ) refresh_btn.grid(row=0, column=2, padx=PADDING_SMALL) - # Read button - read_btn = ctk.CTkButton( + # Open UART button + uart_btn = ctk.CTkButton( panel, - text="Read Serial", + text="Open UART", font=FONT_BODY, command=self._read_serial, ) - read_btn.grid(row=2, column=0, padx=PADDING, pady=PADDING_SMALL) + uart_btn.grid(row=2, column=0, padx=PADDING, pady=PADDING_SMALL) # Test version button self._test_version_btn = ctk.CTkButton( @@ -1202,33 +1203,10 @@ class MainWindow(ctk.CTk): self.destroy() def _read_serial(self) -> None: - """Read and display serial output.""" + """Open the UART Monitor window.""" port = self._port_var.get() - if not port: - self._log_message("No serial port selected") - return - - self._log_message(f"Reading from {port}...") - try: - import serial - - with serial.Serial(port, self._config.serial_baudrate if self._config else 115200, timeout=5) as ser: - ser.reset_input_buffer() - lines = [] - while ser.in_waiting: - line = ser.readline().decode("utf-8", errors="replace").strip() - if line: - lines.append(line) - - if lines: - info = parse_boot_output(lines) - self._log_message(f" Parsed: IP={info.ip_address}, Ver={info.version}") - for line in lines[-10:]: - self._log_message(f" > {line}") - else: - self._log_message(" No data received") - except Exception as e: - self._log_message(f" Error: {e}") + baudrate = self._config.serial_baudrate if self._config else 115200 + UartMonitorWindow(self, port=port, baudrate=baudrate) def _test_serial_version(self) -> None: """Test serial port by sending 'ver()' command and parsing response.""" diff --git a/src/gui/widgets.py b/src/gui/widgets.py index 649e51a..9706ff1 100644 --- a/src/gui/widgets.py +++ b/src/gui/widgets.py @@ -6,9 +6,13 @@ consistent styling from gui/styles.py. from __future__ import annotations +import threading + import customtkinter as ctk from typing import Callable +from serial_monitor import UartMonitor + from gui.styles import ( FONT_BODY, FONT_MONO, @@ -761,3 +765,269 @@ class SubStepFrame(ctk.CTkFrame): self._toggle() self._toggle() # collapse then expand to reset self._toggle() + + +# ════════════════════════════════════════════════════════════════════ +# UartMonitorWindow — standalone UART monitoring window +# ════════════════════════════════════════════════════════════════════ + + +class UartMonitorWindow(ctk.CTkToplevel): + """Standalone UART monitor window with real-time serial output. + + Opens a Toplevel window that connects to the selected serial port, + displays live output in a scrollable log, and provides Start/Stop/Clear + controls. Reuses UartMonitor from serial_monitor. + """ + + def __init__( + self, + master: ctk.CTk | ctk.CTkToplevel, + port: str, + baudrate: int = 115200, + ) -> None: + """Initialize the UART monitor window. + + Args: + master: Parent window (MainWindow). + port: Serial port device path (e.g., '/dev/ttyUSB0'). + baudrate: Baud rate for serial communication. + """ + super().__init__(master) + self.title("UART Monitor") + self.geometry("680x540") + self.minsize(480, 360) + self.grab_set() + self.protocol("WM_DELETE_WINDOW", self._on_close) + + self._port = port + self._baudrate = baudrate + self._monitor: UartMonitor | None = None + self._line_count = 0 + self._is_opening = False + + self._build_title_bar() + self._build_controls() + self._build_log_area() + self._build_status_bar() + + # Try to open serial port after window is drawn + self.after(100, self._try_open) + + # ── Title Bar ────────────────────────────────────────────── + + def _build_title_bar(self) -> None: + bar = ctk.CTkFrame(self, fg_color=DARK_CARD) + bar.pack(fill="x", padx=PADDING, pady=(PADDING, PADDING_SMALL)) + + self._title_label = ctk.CTkLabel( + bar, text="UART Monitor", font=_font(14, "bold"), + ) + self._title_label.pack(side="left", padx=(PADDING_SMALL, 0), pady=PADDING_SMALL) + + self._status_indicator = ctk.CTkLabel( + bar, text="●", font=_font(12), + text_color=ACCENT_PENDING, + ) + self._status_indicator.pack(side="right", padx=(PADDING_SMALL, PADDING_SMALL)) + + self._status_text = ctk.CTkLabel( + bar, text="Connecting...", font=FONT_SMALL, + text_color=DESC_TEXT_COLOR, + ) + self._status_text.pack(side="right") + + # ── Controls ─────────────────────────────────────────────── + + def _build_controls(self) -> None: + frame = ctk.CTkFrame(self, fg_color="transparent") + frame.pack(fill="x", padx=PADDING, pady=PADDING_SMALL) + + self._port_info = ctk.CTkLabel( + frame, text=f"Port: {self._port} @ {self._baudrate} baud", + font=FONT_SMALL, + ) + self._port_info.pack(side="left") + + btn_frame = ctk.CTkFrame(frame, fg_color="transparent") + btn_frame.pack(side="right") + + self._start_btn = ctk.CTkButton( + btn_frame, text="Start", font=FONT_SMALL, width=64, + fg_color=PRIMARY_COLOR, hover_color=STEP_RUN_BG_HOVER, + command=self._on_start, + ) + self._start_btn.pack(side="left", padx=(0, PADDING_SMALL)) + + self._stop_btn = ctk.CTkButton( + btn_frame, text="Stop", font=FONT_SMALL, width=64, + fg_color=DANGER_COLOR, hover_color="#B92B2F", + command=self._on_stop, state="disabled", + ) + self._stop_btn.pack(side="left", padx=(0, PADDING_SMALL)) + + self._clear_btn = ctk.CTkButton( + btn_frame, text="Clear", font=FONT_SMALL, width=64, + fg_color=STEP_RUNNER_BG, hover_color=STEP_RUNNER_BG_HOVER, + command=self._on_clear, state="disabled", + ) + self._clear_btn.pack(side="left") + + # ── Log Area ─────────────────────────────────────────────── + + def _build_log_area(self) -> None: + self._log_frame = ctk.CTkFrame(self) + self._log_frame.pack(fill="both", expand=True, padx=PADDING, pady=PADDING_SMALL) + + self._log_text = ctk.CTkTextbox( + self._log_frame, + font=FONT_MONO, + corner_radius=CORNER_RADIUS, + state="disabled", + ) + self._log_text.pack(fill="both", expand=True, padx=PADDING, pady=PADDING) + + # ── Status Bar ───────────────────────────────────────────── + + def _build_status_bar(self) -> None: + bar = ctk.CTkFrame(self, fg_color=DARK_CARD) + bar.pack(fill="x", padx=PADDING, pady=(0, PADDING)) + + self._line_label = ctk.CTkLabel( + bar, text="Lines: 0", font=FONT_SMALL, + text_color=DESC_TEXT_COLOR, + ) + self._line_label.pack(side="left", padx=PADDING_SMALL) + + self._error_label = ctk.CTkLabel( + bar, text="", font=FONT_SMALL, + text_color=DANGER_COLOR, + ) + self._error_label.pack(side="right", padx=PADDING_SMALL) + + # ── Serial Port Open / Close ─────────────────────────────── + + def _try_open(self) -> None: + """Attempt to open the serial port on init.""" + if not self._port: + self._show_unavailable("未选择串口") + return + + self._is_opening = True + self._update_status(ACCENT_PENDING, "Connecting...") + + def _bg_open() -> None: + try: + import serial + ser = serial.Serial(self._port, self._baudrate, timeout=1) + ser.close() + self.after(0, self._on_open_success) + except Exception as e: + self.after(0, lambda: self._show_unavailable(str(e))) + + threading.Thread(target=_bg_open, daemon=True).start() + + def _on_open_success(self) -> None: + """Serial port opened — enable Start button.""" + self._is_opening = False + self._start_btn.configure(state="normal") + self._update_status(ACCENT_PENDING, "Ready") + self._log(f"[{self._port}] Port ready. Click Start to begin monitoring.\n") + + def _show_unavailable(self, reason: str) -> None: + """Show UART unavailable state.""" + self._is_opening = False + self._status_indicator.configure(text_color=DANGER_COLOR) + self._status_text.configure(text="UART 不可用") + self._port_info.configure(text=f"Port: {self._port} (Unavailable)") + self._start_btn.configure(state="disabled") + self._log(f"[UART] 不可用: {reason}\n") + + # ── Start / Stop / Clear ─────────────────────────────────── + + def _on_start(self) -> None: + if self._monitor and self._monitor.is_running(): + return + + self._start_btn.configure(state="disabled") + self._stop_btn.configure(state="normal") + self._clear_btn.configure(state="normal") + self._update_status(ACCENT_RUNNING, "Monitoring...") + self._log(f"[*] Starting monitor on {self._port} @ {self._baudrate} baud\n") + + self._monitor = UartMonitor(self._port, self._baudrate) + self._monitor.on_line = self._on_line + self._monitor.on_boot_info = self._on_boot_info + self._monitor.on_error = self._on_error + + if not self._monitor.start(): + self._log("[!] Failed to start monitor\n") + self._update_status(ACCENT_ERROR, "Start failed") + self._start_btn.configure(state="normal") + self._stop_btn.configure(state="disabled") + self._clear_btn.configure(state="disabled") + + def _on_stop(self) -> None: + if self._monitor: + self._monitor.stop() + self._monitor = None + self._start_btn.configure(state="normal") + self._stop_btn.configure(state="disabled") + self._clear_btn.configure(state="disabled") + self._update_status(ACCENT_PENDING, "Stopped") + self._log("[*] Monitor stopped\n") + + def _on_clear(self) -> None: + self._log_text.configure(state="normal") + self._log_text.delete("1.0", "end") + self._log_text.configure(state="disabled") + self._line_count = 0 + self._line_label.configure(text="Lines: 0") + + # ── Callbacks (called from UartMonitor background thread) ── + + def _on_line(self, line: str) -> None: + self.after(0, lambda: self._log_line(line)) + + def _on_boot_info(self, info) -> None: + parts = [] + if info.ip_address: + parts.append(f"IP={info.ip_address}") + if info.version: + parts.append(f"Ver={info.version}") + if parts: + self.after(0, lambda: self._log(f"[BOOT] {' '.join(parts)}\n")) + + def _on_error(self, error: str) -> None: + self.after(0, lambda: self._log_error(error)) + + # ── UI Update Helpers ────────────────────────────────────── + + def _log(self, text: str) -> None: + """Append text to the log (thread-safe via after).""" + self._log_text.configure(state="normal") + self._log_text.insert("end", text) + self._log_text.see("end") + self._log_text.configure(state="disabled") + + def _log_line(self, line: str) -> None: + self._line_count += 1 + self._log(f"{line}\n") + self._line_label.configure(text=f"Lines: {self._line_count}") + + def _log_error(self, error: str) -> None: + self._error_label.configure(text=error) + + def _update_status(self, color: str, text: str) -> None: + self._status_indicator.configure(text_color=color) + self._status_text.configure(text=text) + + # ── Window Close ─────────────────────────────────────────── + + def _on_close(self) -> None: + """Cleanup UartMonitor and destroy window.""" + if self._monitor: + self._monitor.stop() + self._monitor = None + self.grab_release() + self.destroy()