fix: use shared (non-exclusive) COM port access on Windows
pyserial opens COM ports with dwShareMode=0 (exclusive) by default. On Windows 10/11 this can cause PermissionError(13) even when the port is not visibly occupied — the OS may have an internal handle. Added _open_port() helper that tries exclusive=False (FILE_SHARE_READ| FILE_SHARE_WRITE) first on Windows, falling back to exclusive if pyserial < 3.3. Applied to all 7 serial.Serial() call sites across serial_monitor, reboot_manager, widgets.
This commit is contained in:
+2
-1
@@ -1077,7 +1077,8 @@ class UartMonitorWindow(ctk.CTkToplevel):
|
|||||||
def _bg_open() -> None:
|
def _bg_open() -> None:
|
||||||
try:
|
try:
|
||||||
import serial
|
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()
|
ser.close()
|
||||||
self.after(0, self._on_open_success)
|
self.after(0, self._on_open_success)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -125,11 +125,12 @@ def reboot_via_serial(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import serial
|
import serial
|
||||||
|
from serial_monitor import _open_port
|
||||||
|
|
||||||
with serial.Serial(
|
with _open_port(
|
||||||
config.serial_port,
|
config.serial_port,
|
||||||
config.serial_baudrate,
|
config.serial_baudrate,
|
||||||
timeout=5,
|
timeout=5.0,
|
||||||
) as ser:
|
) as ser:
|
||||||
ser.reset_input_buffer()
|
ser.reset_input_buffer()
|
||||||
# Send Ctrl+C to break out of any running process
|
# Send Ctrl+C to break out of any running process
|
||||||
|
|||||||
+22
-4
@@ -7,6 +7,7 @@ Provides UartMonitor for continuous background monitoring.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
@@ -17,6 +18,23 @@ import serial
|
|||||||
import serial.tools.list_ports
|
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
|
@dataclass
|
||||||
class BootInfo:
|
class BootInfo:
|
||||||
"""Parsed information extracted from Zynq boot output."""
|
"""Parsed information extracted from Zynq boot output."""
|
||||||
@@ -169,7 +187,7 @@ def check_uart_available(
|
|||||||
|
|
||||||
# 3. Try to open and read
|
# 3. Try to open and read
|
||||||
try:
|
try:
|
||||||
with serial.Serial(port, baudrate, timeout=timeout) as ser:
|
with _open_port(port, baudrate, timeout) as ser:
|
||||||
ser.reset_input_buffer()
|
ser.reset_input_buffer()
|
||||||
lines: list[str] = []
|
lines: list[str] = []
|
||||||
while ser.in_waiting:
|
while ser.in_waiting:
|
||||||
@@ -243,7 +261,7 @@ def test_serial_version(
|
|||||||
import serial
|
import serial
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with serial.Serial(port, baudrate, timeout=timeout) as ser:
|
with _open_port(port, baudrate, timeout) as ser:
|
||||||
ser.reset_input_buffer()
|
ser.reset_input_buffer()
|
||||||
ser.reset_output_buffer()
|
ser.reset_output_buffer()
|
||||||
|
|
||||||
@@ -384,7 +402,7 @@ class UartMonitor:
|
|||||||
|
|
||||||
# Quick port check before starting thread
|
# Quick port check before starting thread
|
||||||
try:
|
try:
|
||||||
test_ser = serial.Serial(self._port, self._baudrate, timeout=1)
|
test_ser = _open_port(self._port, self._baudrate, 1)
|
||||||
test_ser.close()
|
test_ser.close()
|
||||||
except serial.SerialException as e:
|
except serial.SerialException as e:
|
||||||
if self.on_error:
|
if self.on_error:
|
||||||
@@ -410,7 +428,7 @@ class UartMonitor:
|
|||||||
def _read_loop(self) -> None:
|
def _read_loop(self) -> None:
|
||||||
"""Main read loop running in background thread."""
|
"""Main read loop running in background thread."""
|
||||||
try:
|
try:
|
||||||
ser = serial.Serial(
|
ser = _open_port(
|
||||||
self._port,
|
self._port,
|
||||||
self._baudrate,
|
self._baudrate,
|
||||||
timeout=self._timeout,
|
timeout=self._timeout,
|
||||||
|
|||||||
Reference in New Issue
Block a user