""" serial_client.py — RS485/串口抽象层 封装 pyserial: 端口扫描/打开/关闭/收发, 线程安全。 """ import threading import time from dataclasses import dataclass from typing import Optional, Callable import serial import serial.tools.list_ports @dataclass class SerialConfig: port: str = "" baudrate: int = 9600 bytesize: int = serial.EIGHTBITS parity: str = serial.PARITY_NONE stopbits: int = serial.STOPBITS_ONE timeout: float = 0.05 # 读超时 class SerialClient: """串口客户端 — 后台读线程 + 回调通知""" def __init__(self): self._ser: Optional[serial.Serial] = None self._thread: Optional[threading.Thread] = None self._running = False self._lock = threading.Lock() self._on_data: Optional[Callable[[bytes], None]] = None # ── 静态工具 ────────────────────────────────── @staticmethod def list_ports() -> list[str]: return [p.device for p in serial.tools.list_ports.comports()] # ── 连接管理 ────────────────────────────────── def connect(self, cfg: SerialConfig) -> None: self.disconnect() self._ser = serial.Serial( port=cfg.port, baudrate=cfg.baudrate, bytesize=cfg.bytesize, parity=cfg.parity, stopbits=cfg.stopbits, timeout=cfg.timeout, ) self._running = True self._thread = threading.Thread(target=self._read_loop, daemon=True) self._thread.start() def disconnect(self) -> None: self._running = False if self._thread and self._thread.is_alive(): self._thread.join(timeout=1.0) with self._lock: if self._ser and self._ser.is_open: self._ser.close() self._ser = None @property def is_connected(self) -> bool: with self._lock: return self._ser is not None and self._ser.is_open # ── 发送 ────────────────────────────────────── def send(self, data: bytes) -> None: with self._lock: if self._ser and self._ser.is_open: self._ser.write(data) # ── 接收回调 ────────────────────────────────── def set_data_callback(self, cb: Optional[Callable[[bytes], None]]) -> None: self._on_data = cb # ── 内部读线程 ──────────────────────────────── def _read_loop(self) -> None: while self._running: try: with self._lock: if not self._ser or not self._ser.is_open: break waiting = self._ser.in_waiting if waiting > 0: with self._lock: data = self._ser.read(waiting) if data and self._on_data: self._on_data(data) else: time.sleep(0.01) except (serial.SerialException, OSError): time.sleep(0.1)