"""Shared serial-port helpers: 8N1 open and scored port enumeration. Qt-free — GUI code adapts the (device, label) list into its own widgets. """ from __future__ import annotations import serial from serial.tools import list_ports # Substrings that suggest a USB-serial adapter we actually talk to # (ESP32-based T3R, CP210x/CH340 dongles, CDC-ACM devices); matching ports # sort first in pickers. DEVICE_HINTS = ("esp32", "jtag", "espressif", "usb serial", "cp210", "ch340", "cdc") def open_8n1(port: str, baudrate: int, timeout: float, write_timeout: float | None = None) -> serial.Serial: """Open a serial port with the 8N1 framing every device here uses.""" return serial.Serial( port=port, baudrate=baudrate, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=timeout, write_timeout=write_timeout, ) def scored_ports() -> list[tuple[str, str]]: """Enumerate serial ports as (device, human label), likeliest-first.""" ports = list(list_ports.comports()) def score(p): text = f"{p.description} {p.manufacturer or ''} {p.product or ''}".lower() return -sum(h in text for h in DEVICE_HINTS) ports.sort(key=score) return [(p.device, f"{p.device} — {p.description or p.device}") for p in ports] def list_port_devices() -> list[str]: """Plain device-path list, likeliest-first.""" return [dev for dev, _ in scored_ports()]