diff --git a/src/gui/widgets.py b/src/gui/widgets.py index c3144b4..5224818 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_port + ser = _open_port(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..b905de6 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_port - with serial.Serial( + with _open_port( 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..e56b5a9 100644 --- a/src/serial_monitor.py +++ b/src/serial_monitor.py @@ -7,6 +7,7 @@ Provides UartMonitor for continuous background monitoring. from __future__ import annotations +import sys import re import threading import time @@ -17,6 +18,23 @@ import serial import serial.tools.list_ports +def _open_port(port: str, baudrate: int, timeout: float = 1.0) -> serial.Serial: + """Open a serial port with Windows-friendly settings. + + On Windows, tries exclusive=False first (shared access) to avoid + PermissionError(13) when the port appears free but CreateFile + with dwShareMode=0 returns ERROR_ACCESS_DENIED. + + Falls back to default (exclusive) if pyserial < 3.3. + """ + if sys.platform == "win32": + try: + return serial.Serial(port, baudrate, timeout=timeout, exclusive=False) + except TypeError: + pass # pyserial < 3.3, doesn't support exclusive= + return serial.Serial(port, baudrate, timeout=timeout) + + @dataclass class BootInfo: """Parsed information extracted from Zynq boot output.""" @@ -169,7 +187,7 @@ def check_uart_available( # 3. Try to open and read try: - with serial.Serial(port, baudrate, timeout=timeout) as ser: + with _open_port(port, baudrate, timeout) as ser: ser.reset_input_buffer() lines: list[str] = [] while ser.in_waiting: @@ -243,7 +261,7 @@ def test_serial_version( import serial try: - with serial.Serial(port, baudrate, timeout=timeout) as ser: + with _open_port(port, baudrate, timeout) as ser: ser.reset_input_buffer() ser.reset_output_buffer() @@ -384,7 +402,7 @@ class UartMonitor: # Quick port check before starting thread try: - test_ser = serial.Serial(self._port, self._baudrate, timeout=1) + test_ser = _open_port(self._port, self._baudrate, 1) test_ser.close() except serial.SerialException as e: if self.on_error: @@ -410,7 +428,7 @@ class UartMonitor: def _read_loop(self) -> None: """Main read loop running in background thread.""" try: - ser = serial.Serial( + ser = _open_port( self._port, self._baudrate, timeout=self._timeout,