From 36fbb6a662dc8087198b1267c8e1bb41c148c29b Mon Sep 17 00:00:00 2001 From: Jeremy Shen Date: Fri, 12 Jun 2026 09:46:31 +0800 Subject: [PATCH] fix: try both COM11 and \\.\COM11 formats when opening serial port pyserial should auto-add \\.\ prefix for COM>=10, but some versions don't. Added _open_serial() helper that tries the raw port name first, then falls back to explicit NT namespace prefix on Windows. Replaces all serial.Serial() calls across serial_monitor, reboot_manager, and widgets. --- src/gui/widgets.py | 3 ++- src/reboot_manager.py | 5 +++-- src/serial_monitor.py | 36 ++++++++++++++++++++++++++++++++---- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/gui/widgets.py b/src/gui/widgets.py index c3144b4..fd50041 100644 --- a/src/gui/widgets.py +++ b/src/gui/widgets.py @@ -1077,7 +1077,8 @@ class UartMonitorWindow(ctk.CTkToplevel): def _bg_open() -> None: try: import serial - ser = serial.Serial(self._port, self._baudrate, timeout=1) + from serial_monitor import _open_serial + ser = _open_serial(self._port, self._baudrate, 1) ser.close() self.after(0, self._on_open_success) except Exception as e: diff --git a/src/reboot_manager.py b/src/reboot_manager.py index d30e3a2..4b014cc 100644 --- a/src/reboot_manager.py +++ b/src/reboot_manager.py @@ -125,11 +125,12 @@ def reboot_via_serial( try: import serial + from serial_monitor import _open_serial - with serial.Serial( + with _open_serial( config.serial_port, config.serial_baudrate, - timeout=5, + timeout=5.0, ) as ser: ser.reset_input_buffer() # Send Ctrl+C to break out of any running process diff --git a/src/serial_monitor.py b/src/serial_monitor.py index 9e9acd5..c77071f 100644 --- a/src/serial_monitor.py +++ b/src/serial_monitor.py @@ -8,6 +8,7 @@ Provides UartMonitor for continuous background monitoring. from __future__ import annotations import re +import sys import threading import time from dataclasses import dataclass @@ -17,6 +18,33 @@ import serial import serial.tools.list_ports +def _open_serial(port: str, baudrate: int, timeout: float = 1.0) -> serial.Serial: + """Open a serial port, trying both name formats on Windows. + + pyserial normally adds \\\\.\\COM prefix for COM>=10, but some + versions don't. Try raw name first, then explicit NT namespace. + """ + port = port.strip() + last_err = None + + # Try 1: port name as-is + try: + return serial.Serial(port, baudrate, timeout=timeout) + except serial.SerialException as e: + last_err = e + + # Try 2: Windows NT namespace prefix + if sys.platform == "win32": + m = re.match(r"^(COM\d+)$", port, re.IGNORECASE) + if m: + try: + return serial.Serial(r"\\.\%s" % port, baudrate, timeout=timeout) + except serial.SerialException as e: + last_err = e + + raise last_err or OSError(f"Cannot open serial port: {port}") + + @dataclass class BootInfo: """Parsed information extracted from Zynq boot output.""" @@ -169,7 +197,7 @@ def check_uart_available( # 3. Try to open and read try: - with serial.Serial(port, baudrate, timeout=timeout) as ser: + with _open_serial(port, baudrate, timeout) as ser: ser.reset_input_buffer() lines: list[str] = [] while ser.in_waiting: @@ -243,7 +271,7 @@ def test_serial_version( import serial try: - with serial.Serial(port, baudrate, timeout=timeout) as ser: + with _open_serial(port, baudrate, timeout) as ser: ser.reset_input_buffer() ser.reset_output_buffer() @@ -384,7 +412,7 @@ class UartMonitor: # Quick port check before starting thread try: - test_ser = serial.Serial(self._port, self._baudrate, timeout=1) + test_ser = _open_serial(self._port, self._baudrate, 1) test_ser.close() except serial.SerialException as e: if self.on_error: @@ -410,7 +438,7 @@ class UartMonitor: def _read_loop(self) -> None: """Main read loop running in background thread.""" try: - ser = serial.Serial( + ser = _open_serial( self._port, self._baudrate, timeout=self._timeout,