feat: UART Monitor standalone window

Replace 'Read Serial' button with 'Open UART' that opens a new
UART Monitor window (CTkToplevel) with:

- Title bar with status indicator (● connecting/ready/monitoring/error)
- Port info display + Start/Stop/Clear buttons
- Large scrollable log area (CTkTextbox) with monospace font
- Auto-scroll to bottom, line counter in status bar
- Boot info parsing (IP, version) shown as [BOOT] prefix
- Background serial monitoring via UartMonitor class

UART unavailable handling:
- On init, tries to open serial port in background thread
- If port can't open → shows 'UART 不可用' state with error message
- If port opens → shows 'Ready', user clicks Start to begin monitoring

Window features:
- Modal (grab_set) — focus stays on monitor window
- Clean up UartMonitor on close (WM_DELETE_WINDOW handler)
- Thread-safe UI updates via after(0, ...)
- Reuses existing UartMonitor from serial_monitor.py
This commit is contained in:
Jeremy Shen
2026-06-10 14:51:13 +08:00
parent 93ab224d6d
commit d5fd816ff9
3 changed files with 280 additions and 33 deletions
+8 -30
View File
@@ -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."""