feat: 集成 DLD154Tool — PySide6 桌面串口调试工具
7 文件 1331 行, 支持 DLD154Pro 双协议: - 700BS: 状态/频率/拨码/复位查询 + 自动轮询 - Modbus RTU: FC 0x02/0x03/0x04/0x06/0x10/0x11 + 全数据块(0x0100)美化解析 + 主动上报控制 - PySide6 GUI, 4 Tab, 后台串口线程 从独立仓库 wangfq/DLD154Tool 合并入 DLD154Pro monorepo
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user