DATCOM 项目成果与核心 API 说明
学习的一个新的技术进行记录分享。
界面展示
-
布局结构
- 顶部双工具栏:操作栏与主工具栏,分别承载新增、删除、映射、清理虚拟串口、日志查看、退出、搜索等操作。
- 中心区域:连接列表表格,展示各连接的串口/网络状态、目标地址、实时计数与设备信息。
- 底部状态栏:版本信息与简要提示,便于发布与定位。
-
交互与状态
- 新增连接:弹出参数收集对话框(串口号、波特率、协议、IP/端口、设备信息与描述),确认后在表格新增一行。
- 状态展示:串口与网络各自有状态字段(已就绪/已连接/未连接/失败),计数(接收/发送字节)实时累加。
- 搜索过滤:在主工具栏输入关键字,表格实时过滤匹配行。
- 日志查看:内置日志查看器,支持滚动与周期刷新。
- 端口监控:定时轮询虚拟/物理端口占用情况,便于现场排查与清理。


项目概述
- 桌面应用,面向串口数据采集与按 TCP/UDP 的实时转发
- 支持虚拟串口配对与物理端口映射,内置日志查看与端口占用监控
- 可一键打包为可执行文件,便于现场部署
成果亮点
- 多连接管理:在一个界面下新增、删除与编辑多个串口到网络的桥接
- 可靠转发:UDP 即发、TCP自动重连,统一队列保证发送顺序与统计准确
- 可观测性:实时状态(串口/网络)与计数(接收/发送),滚动日志随时查看
- 虚拟串口能力:创建/删除端口对、接线仿真与严格波特率、总线状态查询
- 现场友好:端口占用监控、清理虚拟串口、搜索过滤、版本信息展示
典型使用场景
- 将设备串口数据桥接到测试服务器,支持内网/外网联调
- 在无真实双串口设备时,使用虚拟串口配对进行数据链路验证
- 批量接入多个设备,统一转发到不同 IP 与端口
架构与模块
- 桥接控制:将"串口采集线程"与"网络发送线程"进行桥接,并维护计数与状态分发
- UI 装配:双层工具栏 + 连接表格 + 状态栏,提供新增/删除/映射/清理/搜索等操作
- 驱动封装:对虚拟串口 SDK 的统一封装,兼容不同系统架构(x86/x64)
- 日志与监控:滚动日志文件 + 内置查看器;定时器监控虚拟/物理端口占用
技术介绍
-
技术选型
- 桌面框架:采用
PyQt5提供成熟窗口与控件体系,利于快速构建稳定桌面 UI。 - 串口通信:使用
pyserial进行跨平台串口访问,支持常见参数与非阻塞读取。 - 网络传输:基于标准
socket实现TCP/UDP,便于控制连接、超时与重试策略。 - 打包交付:使用
PyInstaller将项目打包为单文件可执行,整合图标与资源。
- 桌面框架:采用
-
并发与线程模型
- 采用
QThread + 信号/槽:串口读取与网络发送各在线程中运行,通过信号上报状态与数据,避免 GUI 阻塞与竞态。 - 放弃
asyncio的原因:在串口与 GUI 的结合场景下,QThread更易与事件系统融合,且便于与第三方 DLL 协作。 - 发送队列与背压:统一以队列承载待发送数据,TCP 建连失败时退避重试,UDP 即发确保低延迟。
- 采用
-
稳定性与错误处理
- 自动重连:TCP 连接失败进入退避重试;串口异常短暂休眠后重试打开。
- 统一日志:未处理异常通过
excepthook写入滚动日志文件;按大小切分并保留备份。 - 资源管理:停止时关闭串口与网络句柄,状态统一回传为"未连接",避免悬挂与句柄泄漏。
-
虚拟串口(VSPD)集成
- DLL 封装:对
vspdctl.dll的导出函数进行封装,提供创建/删除端口对、严格波特率仿真、接线映射、总线查询与访问控制。 - 架构适配:当运行环境与 DLL 架构不匹配时,自动启动 32 位代理进程,通过简单 JSON-RPC 实现进程间调用,确保在 x86/x64 上都可工作。
- 安装与权限:提供安装程序调用支持(管理员权限),便于现场快速部署虚拟串口驱动。
- DLL 封装:对
-
性能与可观测性
- 非阻塞 IO 与固定缓冲:串口读取固定缓冲大小,减少频繁分配;网络发送按批量进行。
- UI 最小更新:计数与状态的 UI 更新节制触发,确保在高频数据下界面流畅。
- 内置日志查看器:每秒刷新并自动滚动,支持现场快速定位问题。
核心 API 能力
- 串口采集
- 功能:打开串口,非阻塞读取,持续产出数据流;状态变更事件(就绪/未连接/失败)
- 关键参数:串口号、波特率、校验位、数据位、停止位、读取缓冲大小
- 行为特性:自动重连、异常退避、资源关闭与状态回传
- 网络转发
- 功能:支持 UDP 即发与 TCP 建连重试,内部队列化发送;连接与发送状态事件
- 关键参数:协议类型(TCP/UDP)、目标 IP、目标端口、连接超时与重试策略
- 行为特性:TCP 失败自动重试、UDP 无连接即发、发送成功计数上报
- 数据桥接
- 功能:串口数据进入桥接层后转发到网络(与可选物理串口写出),并维护接收/发送累计计数
- 关键能力:是否可发送判断(网络就绪)、多目标输出(网络 + 物理)、按行索引更新 UI 统计
- 行为特性:线程安全的事件分发、统一异常处理、轻量耦合 UI 更新
- 虚拟串口驱动
- 功能:创建/删除虚拟端口对、查询总线状态、设置波特率仿真与接线映射、访问白名单
- 关键参数:端口名、严格仿真开关、接线映射(DTR/RTS 等)、查询缓冲大小
- 行为特性:封装 DLL 接口并自动选择代理进程(在架构不匹配时),统一错误处理与返回值
- 日志与端口监控
- 功能:滚动日志文件(自动切分与保留)、内置日志查看器(定时刷新与跟随滚动)
- 关键参数:日志目录、文件大小与备份数、刷新周期
- 行为特性:未处理异常统一写入日志,查看器只读与轻量刷新;端口占用周期性轮询
- UI 交互
- 功能:新增/删除连接、映射物理端口、清理虚拟串口、查看日志、退出与搜索过滤
- 行为特性:所有操作产生统一事件,桥接层按行索引更新表格状态与计数
用户操作流程
- 打开应用后,通过工具栏点击"新增",填写串口与网络参数(协议、IP、端口等)
- 确认后,列表新增一行,显示串口与网络的状态、接收/发送字节数
- 如需模拟设备或桥接到本机测试工具,可通过"映射物理端口/清理虚拟串口"进行管理
- 遇到异常时,打开"运行日志",定位连接失败、重试与发送统计等信息
性能与稳定性策略
- 串口非阻塞读取 + 队列化发送,避免界面阻塞
- TCP 建连失败退避重试,UDP 即发确保低延迟
- 统一异常捕获与日志落地,便于回溯与现场排查
- 资源生命周期清晰:停止时关闭串口与网络对象,状态统一回传
部署与交付
- 依赖简单:Python 环境与基础库
- 支持一键打包为单文件应用,便于在测试产线或客户现场快速落地
- 可选捆绑虚拟串口驱动安装包(含多平台与架构变体)
可扩展性
- 配置持久化(JSON/INI)、批量操作与多选、图标与版本资源、错误提示与重试策略可调
- 未来可增加带宽与丢包统计、断点续传策略、TLS 加密与访问控制
总结
- 核心定位是"多连接串口采集 + 可靠网络转发 + 虚拟串口能力"的桌面工具
- 通过清晰的桥接与事件模型,实现可观测、可维护、易扩展的工程落地
- 面向分享与展示,我们仅呈现成果与 API 能力,保留源码细节以便后续商业或内部使用

核心 API
以下仅展示少量关键接口片段,帮助理解能力边界与调用方式。
- 串口采集线程
python
class SerialWorker(QThread):
bytes_received = pyqtSignal(bytes)
status_changed = pyqtSignal(str)
def run(self):
while self._running:
if self._ser is None or not self._ser.is_open:
self._ser = serial.Serial(self.port, self.baudrate, timeout=0.1)
self.status_changed.emit("已就绪")
data = self._ser.read(4096)
if data:
self.bytes_received.emit(data)
- 网络发送线程
python
class SocketClient(QThread):
status_changed = pyqtSignal(str)
bytes_sent = pyqtSignal(int)
def run(self):
while self._running:
if self._sock is None:
if self.protocol == "UDP":
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.status_changed.emit("已就绪")
else:
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._sock.settimeout(3)
self.status_changed.emit("连接中...")
self._sock.connect((self.host, self.port_remote))
self.status_changed.emit("已连接")
data = self._q.get(timeout=0.2)
if self.protocol == "UDP":
self._sock.sendto(data, (self.host, self.port_remote))
self.bytes_sent.emit(len(data))
else:
self._sock.sendall(data)
self.bytes_sent.emit(len(data))
- 数据桥接与计数
python
def on_serial_data(self, data: bytes):
self.serial_rx += len(data)
self.signals.counters_changed.emit(self._row_index(), self.serial_rx, self.net_tx)
if self.net.can_send():
self.net.send(data)
if self.phys_out and self.phys_out.can_send():
self.phys_out.send(data)
- 虚拟串口驱动接口
python
driver = VSPDDriver()
driver.create_pair("COM5", "COM6")
driver.set_strict_baudrate("COM5", True)
driver.delete_pair("COM5")