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:
wangfq
2026-07-30 16:06:58 +08:00
parent df6e38f118
commit e97388500b
7 changed files with 1331 additions and 0 deletions
+100
View File
@@ -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)