☰
COMTool 插件开发指南:内置插件、外部插件与连接插件的完整实现路径
2026/10/14 17:27:26 网站建设 项目流程
  • 开发工具
  • 嵌入式
  • 串口调试

【免费下载链接】COMTool

Cross platform communicate assistant(Serial/network/terminal tool)( 跨平台 串口调试助手 网络调试助手 终端工具 linux windows mac Raspberry Pi )支持插件和二次开发

项目地址:https://gitcode.com/gh_mirrors/co/COMTool
点击查看免费下载

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 的步骤操作:

  1. 进入COMTool/plugins目录,创建插件文件,如myplugin.py;
  2. 在文件中编写继承自Plugin_Base、类名为Plugin的类:
from .base import Plugin_Base class Plugin(Plugin_Base): pass
  1. 实现继承自Plugin_Base的变量与方法,界面文案可统一用_()函数做国际化,如_("hello");
  2. 在 COMTool/plugins/init.py 中注册启用:
from . import myplugin plugins = [..., myplugin.Plugin]
  1. 运行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 源码也能加载插件,步骤如下:

  1. 创建项目目录和插件文件myplugin.py(文件名可任意);
  2. 编写与上节相同的Plugin类(类名必须是Plugin),示例参照 COMTool/plugins/myplugin.py;
  3. 启动 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 展示了包形态插件的三个关键实践:

  1. 构建时自动执行i18n.main("finish", path=packageDir),把翻译二进制文件打进包内(第 12 行);
  2. 通过package_data声明翻译资源,示例为package_data={'COMTool': ["locales/*/*/*.?o"]}(第 96-98 行),对应文档要求“setup.py必须把*.mo文件纳入包数据,用户才能使用翻译”;
  3. 包内插件代码同时兼容两种导入方式:from plugins.base import Plugin_Base/from COMTool.plugins.base import Plugin_Base(见 myplugin2.py),保证在源码树内直接运行或以安装包方式运行时都能 import 成功。

六、插件国际化(i18n)

docs/plugins.md 给出五步流程,参照示例 COMTool/plugins/myplugin2:

  1. 创建plugin_i18n.py定义_函数,插件内统一用_("Hello")引用文案;
  2. 执行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一样)只需两步:

  1. 在COMTool/conn目录新建文件(如conn_xxx.py),实现COMM的方法;
  2. 在 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 )支持插件和二次开发

项目地址:https://gitcode.com/gh_mirrors/co/COMTool
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询