- 开发工具
- 嵌入式
- 串口调试
【免费下载链接】COMTool
Cross platform communicate assistant(Serial/network/terminal tool)( 跨平台 串口调试助手 网络调试助手 终端工具 linux windows mac Raspberry Pi )支持插件和二次开发
COMTool 是一款基于 PyQt5 的跨平台通信调试助手(串口/网络/终端),其扩展性核心在于插件机制:一个插件本质上是一个继承自Plugin_Base且类名固定为Plugin的 Python 类,可通过内置注册、单文件外挂或 pip 包三种方式被加载;连接方式(串口、TCP/UDP、SSH 等)同样是可插拔的COMM子类。本文基于 docs/plugins.md 的插件开发文档,结合 COMTool/plugins/base.py、COMTool/main2.py、COMTool/pluginItems.py 等源码,完整讲解三种插件形态的创建步骤、加载原理、线程模型与国际化方案,读完即可动手写出一个可收发数据、可持久化配置、支持多语言的 COMTool 插件。
一、开发前准备:先让源码跑起来
docs/plugins.md 明确指出:开发插件前需要先掌握如何运行源码,详见 README.MD 的 Development 章节。以源码方式启动 COMTool 的命令为:
python COMTool/Main.py入口 COMTool/Main.py 会调用COMTool.main2.main,并在返回None时自动重启进程(用于换肤、换语言等需要重启的场景)。从源码看,COMTool/main2.py 要求 Python 版本不低于 3.7,否则直接退出;setup.py 中声明的运行时依赖包括pyqt5、pyserial、requests、Babel、qtawesome、paramiko、pyte、pyperclip、coloredlogs、pyqtgraph,开发前需保证这些包可用(在 ARM Linux 如树莓派上,setup.py 会建议改用sudo apt install python3-pyqt5 python3-numpy等方式安装)。
二、插件与主程序的协作模型
理解插件编写规则之前,先看主程序如何使用插件,这决定了插件类必须暴露哪些成员:
- 插件注册表:COMTool/plugins/init.py 维护
pluginClasses列表,并构建以插件id为键的builtinPlugins字典。当前内置插件为dbg、protocol、terminal、graph四个,示例插件myplugin在源码中以注释形式保留(# from . import myplugin)。 - 插件实例化与接线:主窗口在 COMTool/main2.py 的
loadPluginsInfoList中依次加载三类插件(内置、配置文件记录的外部插件、Python 包扫描到的插件),随后 COMTool/pluginItems.py 中的PluginItem实例化插件类,并把宿主能力注入插件对象:plugin.send:绑定到内部sendData,支持send(data_bytes=None, file_path=None, callback=...);plugin.ctrlConn:调用当前连接对象的ctrl控制方法;plugin.hintSignal:弹出提示框,签名emit(type(error, warning, info), title, msg);plugin.reloadWindowSignal:重载窗口以应用新配置;plugin.onInit(config=itemConfig):传入该窗口的配置字典,且 COMTool/pluginItems.py 会强制要求配置中包含version键,否则抛异常。
因此插件类必须设置唯一的id,可选设置name(显示名)、help(帮助页内容,可为str或QWidget,见 COMTool/plugins/base.py)。
Plugin_Base 生命周期与线程模型
COMTool/plugins/base.py 在类 docstring 中给出了完整调用序列:
set vars (hintSignal, send, connCallbak ...) → onInit → onWidget* → onUiInitDone → onActive → onConnChanged(UI 线程)/ send(可在 UI 线程直接调用)/ onReceived(接收线程) → onDel关键约束(来自 base.py 源码注释):
| 回调 | 线程 | 说明 |
|---|---|---|
onInit(config) | 主线程 | 初始化参数,不要耗时过长;config在程序退出时自动保存 |
onWidgetMain(parent) | 主线程 | 必须实现,返回主界面QWidget |
onWidgetSettings(parent) | 主线程 | 返回设置区QWidget或None |
onWidgetFunctional(parent) | 主线程 | 返回功能区QWidget或None |
onUiInitDone() | 主线程 | UI 初始化完成,可刷新界面 |
onConnChanged(status, msg) | UI 线程 | 连接状态变化,基类默认已更新状态栏 |
onReceived(data: bytes) | 接收线程 | 严禁直接操作 UI,需通过信号转到 UI 线程 |
onDel() | 主线程 | 清理资源 |
基类还提供了两个数据编解码工具,可直接复用:
parseSendData(data, encoding, usrCRLF, isHexStr, escape):把用户输入转成字节,支持 CRLF 替换、十六进制字符串(格式应为00 01 02 03,否则报错)、转义序列(如\n、\x00)解析,见 base.py;decodeReceivedData(data, encoding, isHexStr, escape):接收数据的反向解码。
另有bindVar辅助方法,支持把QCheckBox/QLineEdit/ComboBox/QRadioButton的值双向绑定到配置字典,并带类型转换与错误提示(base.py),是编写设置面板时的实用工具。
数据发送链路
插件调用self.send(dataBytes)并不会立刻发送:PluginItem只是把数据压入dataToSend队列(pluginItems.py),由后台sendDataProcess线程(pluginItems.py)在连接可用时取出并调用具体连接的send,发送完成后回调callback(ok, msg, length, path)。因此插件侧无需自行管理发送线程,send可在 UI 线程中直接调用。
三、形态一:内置插件(随 COMTool 一起加载)
按 docs/plugins.md 的步骤操作:
- 进入
COMTool/plugins目录,创建插件文件,如myplugin.py; - 在文件中编写继承自
Plugin_Base、类名为Plugin的类:
from .base import Plugin_Base class Plugin(Plugin_Base): pass- 实现继承自
Plugin_Base的变量与方法,界面文案可统一用_()函数做国际化,如_("hello"); - 在 COMTool/plugins/init.py 中注册启用:
from . import myplugin plugins = [..., myplugin.Plugin]- 运行
python COMTool/Main.py,插件即出现在插件选择器中。
仓库自带的完整示例是 COMTool/plugins/myplugin.py,它实现了最典型的“收发数据”插件,核心要点:
class Plugin(Plugin_Base): id = "myplugin" name = _("my plugin") updateSignal = pyqtSignal(str, str) # 接收线程 -> UI 线程的桥梁 def onWidgetMain(self, parent): # 构建 receiveArea(QTextEdit) + input(QTextEdit) + 发送按钮 ... self.button.clicked.connect(self.buttonSend) self.updateSignal.connect(self.updateUI) return self.widget def buttonSend(self): # UI 线程 data = self.input.toPlainText() if not data: self.hintSignal.emit("error", _("Error"), _("Input data first please")) return dataBytes = data.encode(self.configGlobal["encoding"]) self.send(dataBytes) def onReceived(self, data: bytes): # 接收线程 dataStr = data.decode(self.configGlobal["encoding"]) # 禁止在此直接改 UI,用信号切回 UI 线程 self.updateSignal.emit("receive", dataStr) def updateUI(self, dataType, data): # UI 线程 if dataType == "receive": self.receiveArea.moveCursor(QTextCursor.End) self.receiveArea.insertPlainText(data)其中self.configGlobal["encoding"]来自全局配置(主窗口编码下拉框),提示框通过hintSignal弹出,这正是基类注入的三个宿主能力的典型用法。
四、形态二:外部单文件插件(运行时动态加载)
不修改 COMTool 源码也能加载插件,步骤如下:
- 创建项目目录和插件文件
myplugin.py(文件名可任意); - 编写与上节相同的
Plugin类(类名必须是Plugin),示例参照 COMTool/plugins/myplugin.py; - 启动 COMTool,点击插件列表(标签栏右上角的插件选择器)选择“Load plugin from file”,选择该
.py文件即可加载。
从源码看,加载过程是 COMTool/main2.py 的_importPlugin:它把插件所在目录sys.path.insert(0, dir)插到最前面,然后执行__import__(name).Plugin取类;加载成功后loadExternalPlugin会把{id: {"path": path}}记入配置pluginsInfo.external,下次启动时loadPluginsInfoList会自动重新导入(main2.py),并恢复该插件历史保存的窗口配置。
docs/plugins.md 强调的两个注意事项及源码依据:
- 文件命名风险:由于插件目录会被插到
sys.path头部,目录内文件名可能与标准库或 COMTool 自身模块冲突,文档建议将文件统一命名为comtool_plugin_xxx.py以避免遮蔽; - PyInstaller 二进制的依赖问题:打包后的可执行文件只包含 COMTool 用到的依赖,若插件
import了特殊包,有三种解决方式:保持插件零额外依赖(推荐)、把依赖包拷贝到可执行文件所在目录、或通过pip安装 COMTool 程序包后再用。
五、形态三:Python 包插件(pip 安装,自动发现)
这是分发插件的正式途径,仓库内完整示例位于 COMTool/plugins/myplugin2(其 README.md 说明在示例目录内直接pip install .即可体验)。要点:
- 包名必须以
comtool_plugin_xxx开头; - 构建:
python setup.py sdist bdist_wheel; - 发布到 pypi.org:
twine upload ./dist/*; - 用户端
pip install comtool-plugin-xxx后,COMTool 启动时自动加载。
自动发现的实现在 COMTool/main2.py:遍历sys.path中每个目录,凡发现以comtool_plugin_开头且非dist-info的目录,就执行__import__(name).Plugin并加入插件列表。也就是说,只要包安装在 COMTool 可解释到的sys.path里,就会“即装即用”。
打包脚本 COMTool/plugins/myplugin2/setup.py 展示了包形态插件的三个关键实践:
- 构建时自动执行
i18n.main("finish", path=packageDir),把翻译二进制文件打进包内(第 12 行); - 通过
package_data声明翻译资源,示例为package_data={'COMTool': ["locales/*/*/*.?o"]}(第 96-98 行),对应文档要求“setup.py必须把*.mo文件纳入包数据,用户才能使用翻译”; - 包内插件代码同时兼容两种导入方式:
from plugins.base import Plugin_Base/from COMTool.plugins.base import Plugin_Base(见 myplugin2.py),保证在源码树内直接运行或以安装包方式运行时都能 import 成功。
六、插件国际化(i18n)
docs/plugins.md 给出五步流程,参照示例 COMTool/plugins/myplugin2:
- 创建
plugin_i18n.py定义_函数,插件内统一用_("Hello")引用文案; - 执行
comtool-i18n -p <package path> prepare,例如:
comtool-i18n -p COMTool/plugins/myplugin2/comtool_plugin_myplugin2 prepare自动扫描代码中待翻译字符串,在locales目录生成messages.pot与各语言messages.po; 3. 人工翻译各po文件; 4. 执行comtool-i18n -p <package name> finish生成locales下的*.mo二进制文件; 5. 在setup.py的package_data中包含*.mo文件。
comtool-i18n命令由 setup.py 的console_scripts入口comtool-i18n=COMTool.i18n:cli_main提供,其 CLI 定义在 COMTool/i18n.py:-p/--path指定插件包根目录,子命令仅prepare与finish。开发时也可以不装包直接跑源码:python COMTool/i18n.py -p COMTool/plugins/myplugin2/comtool_plugin_myplugin2 prepare(见 myplugin2.py 头注释)。底层实现基于 Babel:extract/init/update抽取并更新 po,compile编译为 mo(i18n.py),扫描规则由 COMTool/babel.cfg 的[python: **.py]指定,支持语言为en、zh_CN、zh_TW、ja(i18n.py)。
插件侧的plugin_i18n.py实现(COMTool/plugins/myplugin2/comtool_plugin_myplugin2/plugin_i18n.py)值得细看:
- 用
gettext.translation('messages', localedir=localesDir, languages=locales)加载翻译并lang.install(),导出_ = lang.gettext; - 带自愈逻辑:若
locales/en/LC_MESSAGES/messages.mo不存在,先自动调用main("finish", path=currDir)现场编译; - 同样采用
try/except双路径导入,兼容源码树与安装包两种部署形态。
成品翻译文件可见 COMTool/plugins/myplugin2/comtool_plugin_myplugin2/locales/zh_CN/LC_MESSAGES/messages.po,其中"Send"译为“发送”、"Input data first please"译为“请先输入数据”,与插件代码中的_("Send")一一对应。
七、连接插件:扩展通信方式
docs/plugins.md 指出,连接插件与普通插件“just the same”,基类是 COMTool/conn/init.py 引入的COMM,实际定义在 COMTool/conn/base.py。新建连接方式(如现有conn_serial.py、conn_ssh.py、conn_tcp_udp.py一样)只需两步:
- 在
COMTool/conn目录新建文件(如conn_xxx.py),实现COMM的方法; - 在 COMTool/conn/init.py 中把新类加入
conns列表:
from .base import ConnectionStatus from .conn_serial import Serial from .conn_tcp_udp import TCP_UDP from .conn_ssh import SSH conns = [Serial, TCP_UDP, SSH]COMM必须/可选实现的成员(见 conn/base.py):
| 成员 | 要求 | 说明 |
|---|---|---|
id、name | 必填 | 空则构造时抛ValueError(如id="serial") |
onInit(config) | 可选 | 初始化参数,勿耗时过长,参考 conn_serial.py 中的默认值写法 |
onWidget() | 必须 | 返回连接参数设置QWidget |
onUiInitDone() | 可选 | UI 初始化完成回调 |
send(data: bytes) | 必须 | 发送数据 |
isConnected()/getConnStatus() | 必须 | 返回ConnectionStatus枚举 |
disconnect() | 必须 | 断开连接 |
getConfig() | 可选 | 返回配置 dict |
ctrl(k, v) | 可选 | 插件侧ctrlConn最终调用它 |
onDel() | 可选 | 清理并等待线程退出 |
信号方面:onConnectionStatus = pyqtSignal(ConnectionStatus, str)向宿主汇报状态,ConnectionStatus枚举为CLOSED / CONNECTED / LOSE / CONNECTING。PluginItem会把当前选中连接的isConnected/getConnStatus绑定到插件对象,并转发状态信号(pluginItems.py),所以连接插件写完后无需改插件代码,新连接方式会自动出现在每个插件窗口左侧的“Connection”设置区。完成后同样运行python COMTool/Main.py验证。
八、开发检查清单
- 插件类名为
Plugin,id全局唯一,name用_()包裹以便翻译; onWidgetMain返回主界面,其他onWidget*按需返回QWidget或None;onReceived中不做任何 UI 操作,用pyqtSignal切回 UI 线程;- 发送统一走
self.send(dataBytes),依赖内部发送队列与回调; - 外部单文件插件的目录内文件命名避免与标准库冲突(建议
comtool_plugin_xxx.py); - 包插件名以
comtool_plugin_开头,setup.py把*.mo纳入package_data,构建时执行i18n.main("finish", path=...); - 连接插件继承
COMM,实现onWidget/send/isConnected/getConnStatus/disconnect,并注册进conns; - 本地验证命令:
python COMTool/Main.py。
以上流程全部与当前仓库 docs/plugins.md 的文档骨架一致,并在 COMTool/plugins/base.py、COMTool/main2.py、COMTool/pluginItems.py、COMTool/conn/base.py、COMTool/i18n.py 中均有对应实现可查证;官方文档 README.MD 中“Write your own plugins in minutes”一节也指向本指南,可作为插件开发的第一手参考资料。
- 开发工具
- 嵌入式
- 串口调试
【免费下载链接】COMTool
Cross platform communicate assistant(Serial/network/terminal tool)( 跨平台 串口调试助手 网络调试助手 终端工具 linux windows mac Raspberry Pi )支持插件和二次开发
相关推荐
Claude Code 插件开发:MCP 连接器发现与连接的完整指南(Cowork 插件定制篇)
Claude Code 插件开发:MCP 连接器发现与连接的完整指南(Cowork 插件定制篇) 导读 本文是 Claude Code 系统提示词体系中用于指导
文档提示工程人工智能Apache APISIX ext-plugin-pre-req 插件完全指南:在内置 Lua 插件之前运行外部插件
Apache APISIX ext plugin pre req 插件完全指南:在内置 Lua 插件之前运行外部插件 ext plugin pre req 是
API网关后端云原生微服务Kubebuilder 插件扩展完全指南:自定义 Scaffolding 插件与外部插件实战
Kubebuilder 插件扩展完全指南:自定义 Scaffolding 插件与外部插件实战 Kubebuilder 是一个基于 CRD(Custom Reso
开发者工具代码生成CLI云原生后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考