目录
一个基于 Qt 的桌面客户端,用于与 DG-Lab 服务进行 WebSocket 通信。项目采用 C++20 编写,通过启动独立的 Python 子进程(Bridge.py)来管理与 DG-Lab 服务器的 WebSocket 连接。实现了多级配置管理、模块化日志、异步任务以及灵活的规则引擎等功能。当前版本号: v1.0.0
详细请查看: 更新日志。
DG-LAB-Client 是一个为 DG-Lab(地牢实验室)设备设计的桌面客户端工具,核心目标是从外部程序(如游戏、传感器等)获取数据,经过可配置的规则引擎计算后,向 DG-Lab 的 WebSocket 服务发送强度调节、波形输出等指令。
项目采用 C++/Qt 实现用户界面、配置管理、规则引擎与进程调度,通过启动独立的 Python 子进程(Bridge.py)作为 TCP 中转服务器,封装与 DG-Lab 服务的 WebSocket 通信。整体架构如下:
- C++ 主程序: 负责界面展示、多级配置(main/system/user)、规则文件管理、日志系统、以及向 Python 子进程发送命令。
- Python 子进程: 运行
Bridge.py,启动 TCP 服务器监听本地端口,接收主程序的 JSON 命令,并调用WebSocketCore.py完成与 DG-Lab 服务端的 WebSocket 交互(连接、心跳、绑定、强度/波形控制)。 - 规则引擎: 支持加载带占位符
{}的 JSON 规则文件,动态填入参数(例如从外部数据模块接收的数值),生成最终命令。规则可指定通道(A/B)、模式(0~4)和值计算表达式(支持四则运算、括号)。 - 数据扩展机制(插件化): 通过动态库插件(
module/)从不同外部源获取数据——现已内置 CS2 GSI 插件(从 CS2 游戏读取状态数据),未来可扩展更多插件;主程序仅负责接收数值序列并按规则处理,不关心数据具体含义,实现灵活的社区扩展。
当前状态: 已具备配置管理、规则编辑、Python 子进程通信、WebSocket 指令发送、数值模块与插件化数据接入(如 CS2 GSI)等核心能力,可获取外部数据并驱动规则计算。实时强度/波形反馈等后续功能正在规划中。
-
Python 子进程通信 通过
PythonSubprocessManager启动外部 Python 脚本(Bridge.py),脚本启动后输出监听端口,主程序通过QTcpSocket连接,以 JSON 格式发送命令并接收响应。所有耗时调用均放入全局线程池执行,完成后通过信号槽返回主线程。 -
配置系统 采用
MultiConfigManager管理多个 JSON 配置文件(main/user/system),支持优先级覆盖、热重载、配置变更监听。配置项通过ConfigValue<T>或ConfigObject<T>包装,提供类型安全访问和缓存。规则表格高级编辑: 在“配置”页面的规则表格中,“通道”和“模式”列使用下拉框选择,“值模式”列使用可视化公式构建器。支持通过按钮快速插入 {}、+-*/(),并在保存时自动检查括号平衡合法性,极大提升了复杂计算式(如 {}+{}*2、({}*2)+{})的编辑体验。 -
规则引擎 提供
Rule类(支持{}、{id:xxx(名称)}、{rule:xx}占位符)和单例RuleManager。可从指定目录下扫描 JSON 规则文件(含关键字rule),加载规则集,并支持创建/删除/切换规则文件。规则支持启用状态(enabled)、多父级(通道 A/B 或规则引用)、唯一规则序号,值模式支持空值语义(任一引用为空时忽略该项计算),规则间可通过{rule:xx}引用结果并级联触发,父级为通道的规则结果自动发送给 Python 子进程。规则可用于动态生成发送给 Python 子进程的命令(如强度操作、波形参数),极大提升了操作的灵活性。 -
进程检查 (ProcessChecker) 提供
ProcessChecker静态工具类,跨平台查询指定名称的进程是否在运行(Windows 使用tasklist,macOS/Linux 使用ps),支持大小写敏感开关。用于 CS2 GSI 配置更新时判断游戏是否运行(配置仅游戏启动时加载,运行中更新需提示重启游戏)。 -
CS2 GSI 插件 (CS2GsiPlugin)
CS2GsiPlugin(module/gsi/)将原静态CS2GSIModule改造为符合IPlugin接口的动态库插件:通过 Python 工具PathFinder.py跨平台查找 CS 游戏目录,随机选取监听端口,按模块最小查询周期计算buffer/throttle并生成gamestate_integration_dglab.cfg(路径记录到user.json的app.gsi下,经宿主配置接口读写)。插件经宿主共享DataListener接收 GSI 数据(HTTP POST),注册完整数值列表(个人状态类 17 项 + 团队/地图类 6 项,参照官方 GSI 规范),并实现自身/队友数据归属区分:首次收到有效数据时记录player.steamid为本地玩家基准,之后比较——一致为自身(个人状态类数值正常更新:血量/护甲/金钱/闪光/烟雾/燃烧/回合击杀/爆头/总伤害/装备价值/总击杀/助攻/死亡/MVP 等),不一致为队友(个人数值不更新,仅团队/地图类数值更新:CT/T 得分、连续失利次数、炸弹状态、地图阶段)。周期变化时自动更新配置;若cs2.exe运行中,经宿主notify弹出重启游戏提示。 -
首页通道面板 首页 A/B 通道卡片分为模块区域与规则区域:模块区域显示挂载在该通道上的模块名称与模块内数值的最小查询周期;规则区域显示父级为该通道的规则名称与最近一次计算的数值(规则计算完成时实时刷新)。卡片自适应布局、圆角样式,
x_wave_card波形卡片保持现状。 -
数值模块(Module) 提供
ModuleManager单例与ModuleValue/Module数据模型,管理可查询数值(参照 CS2 官方 GSI 规范,如health、armor、team_num、money等)。每个数值可独立设置查询周期(每秒/每两秒/每四秒/每半秒/四分之一秒),模块页面提供统一设置入口;调度器以所有数值中最短的查询周期为基准进行轮询,数值变化时通过value_changed信号推送,供规则引擎等下游消费。数值支持可选最小/最大值配置(写入自动钳制到范围)。模块页点击模块卡片可弹出数值展示窗口(显示名称、当前值、最小/最大值及底层字段名)。 -
插件系统(IPlugin) 提供插件化模块接口
IPlugin(纯虚基类):生命周期(initialize/uninitialize/cleanup/can_unload)、自描述(名称、版本、API 版本、能力标志、依赖列表)、线程安全声明与错误码返回;统一跨平台导出宏PLUGIN_EXPORT(Windows_WIN32/ GCC/Clang 可见性),约定extern "C"的create_plugin/destroy_plugin/get_plugin_api_version工厂导出,主程序加载时校验 API 版本(PLUGIN_API_VERSION)。插件日志通过回调转发由宿主统一记录(不直接使用LOG_MODULE,类名/方法名自动为插件自身名称与函数名);内存隔离约定插件自行new/delete(宿主仅调用destroy_plugin)。ModuleManager作为插件宿主:启动时扫描<程序目录>/module/(路径由config/main.json的app.module.path配置,app.module.scan_load控制"扫描即加载"),通过QLibrary动态加载、校验 API 版本与依赖、注入日志回调与宿主上下文(IPluginHost:数值注册/写入、共享DataListener),并管理加载状态(暂未加载/已加载/加载失败);卸载时执行can_unload检查 →uninitialize→cleanup→destroy_plugin。示例空壳插件(module/example/)演示完整接口实现与数值注册,构建后自动复制到<程序目录>/module/供扫描加载。 -
波形采样控件(多通道) 提供
SampledWaveformWidget,可同时接收多个独立数据源(监听器)的归一化值(0~1),每个监听器以不同颜色的滚动波形图实时显示。支持动态添加/删除监听器、自定义波形颜色、调整采样间隔和最大振幅比例。适用于同时监控 A/B 通道强度、外部传感器数值等场景。 -
可编辑标签控件 (EditableLabel) 提供
EditableLabel控件,继承自QLabel,支持双击进入编辑模式,内嵌QLineEdit并支持输入验证器。编辑完成后发出text_edited信号,用于需要直接修改文本的场景(如规则名称、设备别名等),提升交互灵活性。 -
IP 选择辅助 (IpSelector) 提供
IpSelector单例类,自动匹配可用 IP 地址(基于黑白名单关键词过滤网卡名称),支持弹出图形化对话框让用户编辑黑白名单并手动选择 IP。简化设备连接前的网络配置流程。 -
样式系统增强 重构 Qt 样式表,使用
type和theme属性实现精细的控件分类(导航按钮、操作按钮、标题、输入框等)。支持 14 种预设主题(亮色、暗色及 12 种彩色主题,如炭黑甜粉、深海奶白、克莱因黄等),通过ThemeSelectorDialog以网格卡片形式可视化切换,代码中通过apply_widget_properties()统一设置控件属性,配合 QSS 实现现代化玻璃拟态界面。 -
日志与调试
DebugLog提供模块化日志等级控制,可输出到控制台、Qt 界面等不同的LogSink;通过Console类可在 Windows 上创建调试控制台。 -
日志导出(自动 + 手动)
LogExporter提供两类日志记录:自动日志在程序启动、配置系统加载完毕后自动开始记录运行日志(默认写入程序目录log/,受导出级别/数量/大小限制,超限分片、退出或导出后自动清理多余日志);手动日志在点击“导出日志”时写入手动目录(默认log/handle/),仅应用级别过滤,不受数量与大小限制。自动与手动各有独立的级别过滤设置(导出级别、仅指定级别、范围、位置),可在“更多设置”弹窗中分别配置,持久化到user.json的app.log.auto/app.log.manual下。 -
WebSocket 通信 Python 脚本
Bridge.py内部使用WebSocketCore.py(工具库)与 DG-Lab 服务进行 WebSocket 交互(连接、心跳、绑定、控制命令),并将结果通过 TCP 返回给 C++ 主程序。 -
跨平台构建 基于 CMake,支持 Windows、Linux、macOS 等平台,并通过 GitHub Actions 自动构建和打包。
- C++ 编译器: 支持 C++20 标准(如 GCC 10+、Clang 12+、MSVC 2022)
- Qt: 5.15 或 6.x(Core、Gui、Widgets、Network)
- Python: 3.9 或更高版本
注意: Python 需要安装在系统环境中,并且 python 命令可用。CMake 配置时会自动查找 Python 解释器路径。
推荐使用 VS2022/2026(MSVC)进行 Windows 开发,Linux/macOS 可使用 GCC 或 Clang。👉 遇到依赖问题?请查看 常见问题 - 编译与运行
- nlohmann/json (版本 3.12.0) 用于 JSON 解析。CMake 会在配置时自动从 GitHub 下载单头文件到构建目录
/include/nlohmann/
Python 子进程(Bridge.py)需要以下库,由 CI 自动安装或手动部署:
websockets(建议版本 10.0 或更高)qrcode[pil](用于生成二维码) 安装命令:
pip install websockets qrcode[pil]git clone https://github.com/yourusername/DG-LAB-Client.git
cd DG-LAB-Clientpip install websockets qrcode[pil]mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/path/to/qt -DPython_ROOT_DIR=/path/to/python- 对于 Qt6,CMake 通常能自动找到;若使用 Qt5,请确保
Qt5包可用。 - 可通过
-DPYTHON_PACKAGES_DIR=path/to/site-packages指定要打包的第三方 Python 包目录(可选,供 CI 使用)。
👉 配置或编译失败?请查看 常见问题 - 编译与运行
cmake --build . --config Release编译完成后,可执行文件位于 build/Release(Windows)或 build(Linux/macOS)目录下。运行时需要确保以下目录与可执行文件同级:
config/: 包含main.json、system.json、user.json以及规则子目录(如config/rules/)python/: 包含Bridge.py和WebSocketCore.pyqcss/: 包含所有主题样式文件(light.qcss,night.qcss,style_*.qcss)assets/: 包含资源文件(如图片)
CMake 的 POST_BUILD 命令会自动复制这些目录到输出目录。
cpack生成的安装包将位于 build/ 目录下(根据平台不同,可能是 .exe、.dmg、.deb 等)。
本项目已配置 GitHub Actions,每次推送到 main 分支或创建以 v 开头的标签时,会自动在 Ubuntu、Windows 和 macOS 上构建并打包,生成对应平台的安装包:
- Windows: NSIS 安装程序 (
.exe) 和 ZIP 压缩包 - macOS: DMG 磁盘映像和 TGZ 压缩包
- Linux: DEB、RPM 包以及 AppImage(自包含可执行文件)
你可以在 GitHub 仓库的 Actions 页面下载最新构建产物,或从 Releases 页面获取正式发布的版本。
系统包含三个配置文件,按优先级从低到高依次为:
main.json: 主配置,优先级 0system.json: 系统配置,优先级 1user.json: 用户配置,优先级 2
高优先级的配置项会覆盖低优先级的同路径配置。 **注意: ** 即使有覆盖逻辑,仍不建议在不同配置文件中定义相同属性配置文件中 __priority 字段用于定义优先级,不可删除。
若配置文件丢失将从默认配置(
DefaultConfigs.cpp)加载,并在程序运行时自动生成缺失的配置文件。👉 配置文件相关问题请查看 常见问题 - 配置问题
示例 `main.json`
{
"__priority": 0,
"app": {
"name": "DG-LAB-Client",
"version": "1.0.0",
"debug": false,
"log": {
"console_level": 0,
"only_type_info": false,
"ui_log_level": 0
},
"ui": {
"theme": "light"
}
},
"python": {
"path": "python",
"bridge_path": "./python/Bridge.py"
},
"rule": {
"path": "./config/rules",
"key": "rule"
},
"version": "1.0",
"DGLABClient": "DG-LAB-Client"
}**注意: ** 其中 "version" 与 "DGLABClient" 为检查字段,内容随意,但 请勿删除或修改 此字段。app.ui.theme 存储当前选中的主题模式。
python/Bridge.py: 主入口脚本,启动 TCP 服务器,等待 C++ 客户端连接,解析命令并调用WebSocketCore.py中的DGLabClient类。python/WebSocketCore.py: WebSocket 客户端核心库,封装了与 DG-Lab 服务器的连接、心跳、绑定、强度控制等逻辑。
规则引擎允许您通过 JSON 文件定义一系列带占位符 {} 的规则,在运行时传入参数动态生成字符串(例如构造 send_strength 命令的 JSON)。使用步骤如下:
- 规则文件存放: 默认规则目录为
config/rules/(可通过rule.path配置修改)。目录下的 JSON 文件若文件名包含rule关键字(可通过rule.key配置),则会显示在 UI 的规则文件列表中。 - 规则格式: 每个规则文件应包含一个
"rules"对象,键为规则名称,值为规则对象。规则对象字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
enabled |
bool | 启用状态(默认 true),关闭后不参与计算 |
parents |
array | 父级列表:"A"/"B" 为通道父级(结果发送给该通道),整数为规则序号(结果推送给对应规则) |
mode |
int | 模式 0-4(递减/递增/设为/连减/连增) |
valuePattern |
string | 值计算表达式,支持 {id:xxx(名称)} 引用模块数值、{rule:xx} 引用规则结果、{} 外部占位符 |
兼容旧格式:未提供
parents时使用channel字段("A"/"B"/空)作为唯一父级。
示例 `rules.json`
{
"rules": {
"wave_short": "{\"cmd\":\"send_pulse\",\"channel\":1,\"pulses\":[\"01020304\"],\"duration\":{}}",
"strength_up": "{\"cmd\":\"send_strength\",\"channel\":{},\"mode\":1}"
}
}-
规则占位符: 模式中的
{}会被按顺序替换为参数(旧式传参);{id:xxx(名称)}计算时通过模块管理器查询对应数值(括号内为名称注释,界面显示为灰色);{rule:xx}引用序号为 xx 的规则计算结果。若任一引用为空值,本次计算被忽略(不推送、不发送)。 -
级联触发: 模块数值变化时自动触发引用该数值的规则计算;规则计算完成后结果推送给所有父级——父级为通道时通过调用函数将结果发送给 Python 端,父级为规则且启用时同样触发计算(带深度保护防循环引用)。通道启用时整条调用链开始运转,关闭时停止,不影响其他分支。
-
UI 操作: 在客户端的“配置”页面中,您可以:
- 从下拉列表切换规则文件。
- 新建/删除/保存规则文件。
- 添加/编辑规则: 规则名称使用普通文本输入,“父级”(原“通道”列)和“模式”使用下拉选择,“值模式”双击后弹出公式构建对话框(支持按钮快速插入符号 + 实时括号合法性检查 + “显示可用数值”按钮插入
{id:xxx(名称)}/{rule:xx}引用)。 - 规则引用范围: “显示可用数值”中的规则列表包含除当前编辑规则外的所有规则(含父级为通道的规则),即一个规则可以有通道与其他规则的混合父级;父级含通道的规则是否启用由独立的通道启用变量决定,引用它不要求对应通道已启用。
- 启用状态: 规则表格首列“启用”为勾选框,点击即可启用/停用规则(停用的规则不参与计算)。
- 父级编辑: 点击“编辑父级”按钮弹出父级编辑对话框——通道父级单选(无/A/B,含通道唯一性去重),规则父级多选(勾选后对应规则的值模式将引用本规则
{rule:xx},取消勾选则移除引用);确认后自动保存规则文件。 - 父级列显示: 通道父级显示 “A”/“B”,规则父级显示 “rule:xx,xx”(超出显示 “...”),混合显示 “A;rule:xx,xx”,无父级显示 “无”。
- 模式列灰显: 父级全部为通道时正常显示模式;父级无通道时文本后加 “(不适用)” 并整列灰色;父级混合时加 “(部分不适用)” 并整列使用更接近主题文本色的灰色(仅影响界面显示,规则文件内容不变)。
- 通道唯一性: 同一通道(A 或 B)仅允许一个规则作为直连父级——从文件加载时保留序号最小的规则,其余置为“无”;手动设置时保留最后设置的规则。
-
在代码中使用:
#include "RuleManager.h" // 初始化(AppConfig 初始化后调用) RuleManager::instance().init(); // 加载规则文件(默认为 rules.json) RuleManager::instance().load_rule_file("rules.json"); // 评估规则 std::string result = RuleManager::instance().evaluate("strength_up", 1); // 将 result 作为命令发送给 Python 子进程
注意: 规则文件可能包含任意命令,不要加载不可信的 JSON 文件!
数值模块负责从本地数据源获取数值(如 CS2 GSI 的 health、armor 等),并传递给规则引擎计算。使用步骤如下:
- 模块页面: 点击左侧导航栏的“模块”按钮进入模块页,页面顶部可统一设置所有数值的查询周期,下方为两列自适应卡片网格(圆角卡片)。卡片分为两类:插件卡片(扫描到的动态库插件,含加载状态与右侧“启用/禁用”按钮——未加载显示“暂未加载”,加载失败显示原因,已加载显示插件名与最小查询周期)与静态模块卡片(内置模块,始终已加载,显示最小查询周期)。未加载的插件卡片不可点击;点击已加载卡片弹出数值展示窗口。
- 查看数值: 点击模块卡片弹出数值展示窗口,每行显示两个数值框,首行为「名称 + 当前值 | 最小值/最大值」(当前值与最值成比例布局,超出省略号;无范围显示
NULL),第二行为底层字段名小字,底部下拉框可单独设置该数值的查询周期。数值可配置可选最小/最大值(插件提供),写入自动钳制到范围——外部超范围数据(如 GSI 血量 150)会被限制为边界值(100)。 - 周期选项: 每秒、每两秒、每四秒、每半秒、四分之一秒。调度器以所有数值中最短的查询周期为基准轮询,例如一号为四分之一秒、二号为半秒、三号为两秒时,每四分之一秒查询一号,每两次查询二号,每八次查询三号。
- 数值变化推送: 模块保留上次查询结果,数值未变化时不推送;数值变化时通过
ModuleManager::value_changed信号推送,供规则引擎等消费。 - 调度机制: 以所有数值中最短的查询周期为基准周期(最小 250ms),每经过一个基准周期查询一次;周期为基准周期整数倍的数值按对应倍率间隔查询(如基准 250ms 时,500ms 的数值每 2 次查询一次,2s 的数值每 8 次查询一次),周期设置变化时自动重建调度器。
- 数据源: 未设置数据源时数值保持“未获取”状态(界面显示
--),不产生模拟数值;通过ModuleManager::instance().set_data_source(callback)接入真实数据(如 CS2 GSI)后开始取值。规则中引用无数据的数值视为空值,忽略该次计算。 - 通用数据接收器 (DataListener): 负责统一接收外部推送的数据。单实例监听单个端口(支持 TCP HTTP POST 与 UDP 数据报两种协议),解析为 JSON 后按数据包携带的来源标识分发:数据包信封格式为
{"source": "<模块名>", "type": "<信息类型>", "data": {...}},无信封字段时回退到监听器配置的默认来源(如 CS2 GSI 数据默认来源CS2 GSI、类型gsi)。各模块通过register_handler(source, type, callback)注册处理器,即可共享同一端口或多端口并行接收。 - 插件加载:
ModuleManager启动时扫描插件目录(默认<程序目录>/module/,可由app.module.path更改),扫描到的插件默认不立即加载,界面显示为"暂未加载";app.module.scan_load设为true时扫描即加载(调试用)。插件加载流程:QLibrary动态加载 →get_plugin_api_version版本校验 → 依赖校验(dependencies()需已加载)→create_plugin创建实例 → 注入日志回调与宿主上下文 →initialize()(插件在此通过IPluginHost注册数值与数据处理器)。加载失败时记录失败原因,界面显示"加载失败"状态。
👉 规则引擎相关问题请查看 常见问题 - 规则引擎问题
首页的 A/B 通道卡片(x_normal_cards)包含三个区域:
- 模块区域: 显示挂载在该通道上的模块名称 + 模块内数值的最小查询周期(如 "CS2 GSI 模块(最小周期 250ms)")。
- 规则区域: 显示父级为该通道的规则名称 + 最近一次计算的数值(未计算显示 "--"),规则计算完成时实时刷新。
- 波形卡片(
x_wave_card): 暂时保留现状。
模块/规则/波形卡片宽度 1:1:1 等分(布局参数与边距在
DGLABClient.h中以常量声明);模块信息与规则信息外层包含子卡片(channel_info_card)凸显内容,模块信息名称与最小周期分行居中显示;新控件样式(channel_card_title/channel_card_item/channel_card_value/channel_card_period/channel_info_card)已加入全部主题 QSS。
日志等级通过配置文件 app.log.console_level、app.log.ui_log_level 控制(数值含义见下表)。您也可以在代码中使用 LOG_MODULE 宏输出日志:
LOG_MODULE("MyModule", "my_function", LOG_INFO, "This is a log message.");日志等级枚举:
- 0: DEBUG
- 1: INFO
- 2: WARN
- 3: ERROR
- 4: NONE
可通过 DebugLog::set_log_sink_level("qt_ui", level) 动态调整 UI 日志显示级别。
👉 日志不显示或等级不生效?请查看 常见问题 - 通用问题
- 自动日志: 程序启动、配置系统加载完毕后自动开始记录运行日志(
LogExporter注册日志输出通道),默认写入程序目录下的log/文件夹;单个日志超过大小上限时分片写入多个文件(视为一份),并自动清理多余日志仅保留最新 N 份(每次导出后与程序退出时)。 - 手动日志: 点击“配置”页面的“导出日志”按钮,将界面日志按手动设置写入手动目录(默认
log/handle/),不受数量与大小限制,不参与清理。 - 导出设置: 点击“更多设置”按钮弹出设置窗口,自动与手动分组配置:
- 自动日志: 导出日志级别、是否只导出指定级别、指定级别及以上/以下、导出位置、保留日志数量(默认 1)、单个日志大小上限(默认 5MB)。
- 手动日志: 导出日志级别、是否只导出指定级别、指定级别及以上/以下、导出位置。
- 设置项持久化到
user.json的app.log.auto/app.log.manual下(兼容旧版平铺键)。
在 Windows 上,如果配置文件中的 app.debug 为 true,程序启动时会自动创建一个调试控制台,用于显示详细的日志输出。 **注意: ** 该控制台使用 #include <windows.h> 仅在 Windows 平台上可用,并且需要在配置文件中启用调试模式。当然,应用中首页也会输出日志到 Qt 界面,您可以根据需要选择查看。
IpSelector 单例提供了便捷的 IP 地址获取方式:
#include "IpSelector.h"
// 自动获取 IP(基于黑白名单匹配)
QString ip = IpSelector::instance()->auto_select_ip();
// 弹出对话框,让用户编辑黑白名单并手动选择 IP
QString selected = IpSelector::instance()->show_selection_dialog(this);
if (!selected.isEmpty()) {
// 使用 selected IP
}黑白名单默认包含常见虚拟网卡关键词(vmware, virtual, docker, vbox)和物理网卡关键词(以太网, wlan, en0, eth),可在对话框中随时修改。
在 UI 中提升一个 QLabel 为 EditableLabel,即可获得双击编辑能力:
EditableLabel* label = new EditableLabel(this);
label->setText("双击我可编辑");
label->set_validator(new QIntValidator(0, 100, this)); // 可选验证器
connect(label, &EditableLabel::text_edited, [](const QString& newText){
// 处理编辑完成事件
});SampledWaveformWidget 是一个支持多通道实时滚动波形的控件,使用方法如下:
- 添加至 UI: 在 Qt Designer 中提升一个 QWidget 为
SampledWaveformWidget,或直接在代码中创建。 - 添加监听器: 调用
add_listener(name, color)为数据源命名并指定波形颜色。 - 输入数据: 调用
input_data(listener_name, value),value范围为 0.0 ~ 1.0,控件会按全局采样间隔采集并更新对应监听器的波形。 - 移除监听器: 调用
remove_listener(name)(不可删除默认的"default"监听器)。 - 调整颜色: 调用
set_listener_color(name, color)动态修改波形颜色。 - 全局配置: 通过
set_sample_interval_ms(ms)和set_max_amplitude(ratio)调整采样间隔和最大振幅比例。 - 支持范围输入: 提供
set_input_range()方法,允许为每个监听器设置输入值的最小值和最大值,控件内部将输入值归一化到 0~1 后进行绘制,适应不同量级的数据输入。
示例代码
SampledWaveformWidget* wave = ui_.wave_show;
// 添加监听器,范围 0~200(例如强度值)
wave->add_listener("strength_A", Qt::red, 0.0, 200.0);
// 或者先添加后设置范围
wave->add_listener("strength_B", Qt::blue);
wave->set_input_range("strength_B", 0, 200);
// 输入原始值 50 -> 归一化 0.25,波形显示在 1/4 高度
wave->input_data("strength_A", 50);
wave->input_data("strength_B", 150); // 归一化 0.75
// 超出范围会被钳位: 输入 250 -> 钳位到 200 -> 归一化 1.0
wave->input_data("strength_A", 250);注意: 默认监听器
"default"始终存在(绿色),若只需显示单条曲线可直接使用旧接口input_data(value)。
程序内置了 14 种预设主题,涵盖亮色、暗色及多种彩色风格(如炭黑甜粉、深海奶白、克莱因黄、中国红黄等)。用户可通过以下方式切换主题:
- 在“设置”页面点击“主题选择”按钮,打开
ThemeSelectorDialog对话框。 - 对话框以卡片形式列出所有主题,每张卡片显示:
- 主题中文名称(如“浅色模式”、“炭黑甜粉”)
- 英文模式名(如
light、charcoal_pink) - 主色预览块及颜色代码(如
#E8F0FE)
- 单击任意卡片,程序将立即加载对应主题的样式表(
qcss/主题英文名.qcss),并保存配置到app.ui.theme字段。 - 切换后主窗口的标题栏、按钮、日志区域等控件会自动应用新主题(通过
load_stylesheet()和apply_inline_styles()实现)。
注意: 若所选主题的 QSS 文件不存在,程序会自动回退到
light.qcss。主题配置文件存储于config/user.json中的app.ui.theme键。
插件化模块系统允许将数值模块(如 CS2 GSI)实现为独立的动态库,由主程序动态扫描/加载/卸载,无需重新编译主程序。本节为插件开发者提供完整指南。
- 源码目录:
module/(每个插件一个子目录,如module/example/、module/gsi/)。 - 运行时扫描目录: 默认
<程序目录>/module/,构建后插件 DLL 自动复制至此;路径可由config/main.json的app.module.path更改。 - 现有插件:
module/example/(示例空壳插件,演示接口与导出)、module/gsi/(CS2 GSI 插件,完整功能示例)。
所有插件必须继承 IPlugin 并实现:
| 分组 | 方法 | 说明 |
|---|---|---|
| 自描述 | name() / version() / api_version() |
插件名称(同时作为模块名与日志类名)、版本号、API 版本 |
| 自描述 | capabilities() / dependencies() |
能力标志(ProvidesValues/ConsumesData/HasSettingsUi)与依赖插件名列表(宿主加载前校验) |
| 线程安全 | thread_safety() |
声明调用线程要求(默认仅主线程) |
| 生命周期 | initialize() |
初始化:注册数值、注册数据处理器、启动服务(返回 PluginError 错误码) |
| 生命周期 | uninitialize() |
反初始化:注销数值与处理器、停止服务(与 initialize 对称) |
| 生命周期 | cleanup() / can_unload() |
资源清理 / 卸载前检查(拒绝时保持已加载) |
| 周期通知 | on_host_period_changed() |
宿主查询周期变化时被调用(可选实现) |
数值通过 ModuleValue 构造可携带可选最小/最大值(如 ModuleValue("health", "当前血量", QueryPeriod::QUARTER_SECOND, "m_iHealth", 0, 100),空表示无上下限):弹窗显示 当前值 \| 最小值/最大值(无范围显示 NULL),写入时自动钳制到范围。
| 日志 | set_log_callback() / log() | 日志回调注入;插件用 PLUGIN_LOG(this, level, ...) 宏上报 |
插件动态库必须通过 extern "C" 导出以下三个符号(include/plugin/plugin_export.h 提供 PLUGIN_EXPORT 宏):
// 编译插件时定义 PLUGIN_BUILD(见 12.6 构建示例)
extern "C" {
PLUGIN_EXPORT int get_plugin_api_version(); // 返回 PLUGIN_API_VERSION,宿主加载时校验
PLUGIN_EXPORT IPlugin* create_plugin(); // 创建实例(插件内部 new)
PLUGIN_EXPORT void destroy_plugin(IPlugin*); // 销毁实例(插件内部 delete)
}宿主在加载插件后通过 attach_host() 注入宿主上下文,插件在生命周期方法中调用:
| 能力 | 方法 | 说明 |
|---|---|---|
| 数值 | register_module_values() / unregister_module() / set_value() |
注册/注销模块、写入数值(变化触发规则计算) |
| 数据接收 | listen_data() / register_data_handler() / unregister_data_handler() / stop_listening_data() |
共享 DataListener:监听端口、按 (source, type) 注册处理器;无信封数据回退默认来源 |
| 配置 | get_config_value() / set_config_value() |
读写宿主合并配置(写入持久化到 user 配置,如 GSI 插件的 app.gsi.*) |
| 基础信息 | base_period_ms() |
当前调度基准周期(用于 throttle 等计算) |
| 通知 | notify() |
用户可见通知(宿主弹窗,如“需重启游戏”) |
数据包信封格式(自定义协议建议采用): {"source": "<模块名>", "type": "<信息类型>", "data": {...}};无信封数据(如 CS2 GSI)回退到监听器默认来源。
- 日志转发: 插件内部不直接使用
LOG_MODULE,统一使用PLUGIN_LOG(this, level, ...)宏(类名/方法名自动为插件名称与函数名),由宿主统一记录。 - 内存隔离: 插件实例在插件内
new,宿主仅调用destroy_plugin销毁;禁止跨模块 new/delete、禁止静态全局变量。 - 错误处理: 可能失败的操作返回
PluginError错误码而非抛出异常。 - 命名: 插件类名以
Plugin结尾(如CS2GsiPlugin);导出函数使用标准三件套固定名。 - ABI 兼容: 插件必须与主程序使用同一工具链构建(本项目 MinGW g++ + Qt 6.9.3),否则无法加载。
- API 版本: 接口不兼容变更时递增
PLUGIN_API_VERSION,主程序加载时校验。
# 插件目标:SHARED + PLUGIN_BUILD + 接口头文件路径
add_library(my_plugin SHARED
module/my/MyPlugin.h
module/my/MyPlugin.cpp
src/module/ModuleValue.cpp # ModuleValue 为纯 C++ 模型,编入插件以构造数值
)
target_compile_definitions(my_plugin PRIVATE PLUGIN_BUILD)
target_include_directories(my_plugin PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include/plugin
${CMAKE_CURRENT_SOURCE_DIR}/include/module
)
target_link_libraries(my_plugin PRIVATE Qt::Core Qt::Network) # 按需
if(WIN32)
set_target_properties(my_plugin PROPERTIES PREFIX "") # 去掉 lib 前缀
endif()
# 构建后复制到插件扫描目录
add_custom_command(TARGET my_plugin POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE:my_plugin>"
"$<TARGET_FILE_DIR:${PROJECT_NAME}>/module"
)- 主程序启动时扫描插件目录(
*.dll/*.so/*.dylib),默认只扫描不加载,模块页显示“暂未加载”与“启用”按钮;app.module.scan_load=true时扫描即加载(调试用)。 - 加载流程:
QLibrary动态加载 →get_plugin_api_version校验 → 依赖校验 →create_plugin→attach_host+set_log_callback→initialize()(插件在此注册数值与数据处理器)。 - 卸载流程:
can_unload()检查 →uninitialize()→cleanup()→destroy_plugin()→QLibrary::unload。 - 加载失败时记录原因,模块页显示“加载失败”。
CS2 GSI 插件按 player.steamid 区分数据归属:首次收到有效数据时记录为本地玩家基准,之后比较——一致为自身(个人状态类数值正常更新:血量/护甲/金钱/闪光/烟雾/燃烧/回合击杀/爆头/总伤害/装备价值/总击杀/助攻/死亡/MVP 等),不一致为队友(个人数值不更新,仅团队/地图类数值更新:CT/T 得分、连续失利次数、炸弹状态、地图阶段)。字符串字段按固定集合映射为数字(如 team CT/T→3/2、bomb.state→1-4、map.phase→0-6),不固定的值放弃该字段。
此 WebSocket 服务扮演中央中转站的角色,负责在电脑客户端(第三方终端)与 DG-Lab 手机 App 之间中继消息(如绑定指令、强度控制、心跳包等),使两者能通过 WebSocket 进行数据交换。该服务仅支持 郊狼脉冲主机 3.0。
- 安装 Node.js: 建议版本 12.x 或更高。您可以在 Node.js 官网 下载并安装。
- 获取服务脚本: 从官方仓库获取后端代码,项目结构如下:
官方仓库后端代码项目结构
socket/
└── v2/ # ✅ 推荐使用
├── backend/ # WebSocket 后端 (Node.js)
│ ├── src/
│ │ ├── index.js # 主入口,启动服务器 & 消息路由
│ │ ├── config.js # 配置管理(支持 .env 环境变量)
│ │ ├── connection.js # 连接管理(注册、配对、断开)
│ │ ├── message.js # 消息处理(验证、转发、强度/波形)
│ │ ├── timer.js # 定时器管理(波形消息队列发送)
│ │ └── logger.js # 日志模块(winston)
│ └── package.json
└── frontend/ # 前端控制页面 (HTML+CSS+JS)
打开终端,进入 socket/v2/backend 目录,执行以下步骤:
步骤一: 安装依赖
cd socket/v2/backend
npm install步骤二: 配置环境变量(可选)
在 socket/v2/backend 目录下创建 .env 文件,可配置以下参数:
| 变量 | 默认值 | 说明 |
|---|---|---|
PORT |
9999 | WebSocket 服务端口 |
HEARTBEAT_INTERVAL |
60000 | 心跳间隔(毫秒) |
DEFAULT_PUNISHMENT_TIME |
1 | 波形发送频率(每秒次数) |
DEFAULT_PUNISHMENT_DURATION |
5 | 波形默认持续时间(秒) |
LOG_LEVEL |
info | 日志级别 |
步骤三: 启动服务
# 生产模式
npm start
# 开发模式(自动重启)
npm run dev服务默认监听端口 9999。
- 保持 Node.js 服务在后台运行(终端窗口不要关闭)。
- 启动您的客户端程序(如 DG-LAB-Client 或其他第三方终端)。
- 客户端连接 WebSocket 服务后,服务端会分配
clientId并返回给客户端。 - 客户端根据 WebSocket 地址和
clientId生成二维码,格式如下:https://www.dungeon-lab.com/app-download.php#DGLAB-SOCKET#ws://你的服务器地址:端口/clientId
- 确保手机与电脑连接在 同一个局域网 下。
- 打开 DG-Lab 官方手机 App(需要 3.x 及以上版本,支持 WebSocket)。
- 在 App 中打开 SOCKET 功能 → 点击连接服务器 → 扫描客户端生成的二维码。
- 扫描后,App 将自动连接到中转服务并完成配对。
配对成功后,您即可进行强度调节、波形发送等操作。
-
客户端 ID 必须唯一: 服务端生成的
clientId必须全局唯一,推荐使用 UUID v4。 -
消息格式要求: 除初始连接时
targetId可为空外,所有消息必须包含type、clientId、targetId、message四个字段且值不为空。 -
本地调试与正式使用:
- 本地调试时可以使用
ws://协议 - 若需公网访问,建议使用
wss://协议以保证通信安全
- 本地调试时可以使用
-
保持服务运行: 启动服务的终端窗口不要关闭,关闭后服务会终止。
-
检查防火墙: 如果手机无法连接,检查电脑防火墙是否允许 WebSocket 端口(默认 9999)的入站连接。
-
默认端口确认: 服务默认监听
9999端口,具体请以您下载的脚本中实际定义的端口为准。 -
规则文件安全: 不要删除默认的
rules.json,删除其他规则文件前请确保已保存重要规则。
相关资源:
- 官方仓库: https://github.com/DG-LAB-OPENSOURCE/DG-LAB-OPENSOURCE
- 常见问题文档:
socket/QA/Websocket_open_source_QA_Chinese.txt(位于仓库中)
如有其他问题,请联系官方邮箱: service@dungeon-lab.com
👉 部署或连接过程中遇到问题?请查看 常见问题 - 本地 WebSocket 中转服务
目录树
DG-LAB-Client/
├── .github/ # GitHub 配置目录
│ └── workflows/ # CI/CD 工作流
│ ├── build.yml # 构建与测试工作流
│ └── release.yml # 发布工作流
├── assets/ # 静态资源(图片等)
│ └── normal_image/
│ ├── main_image.png # 主界面图片
│ ├── check_white.svg # 勾选框白色勾号(深色强调色主题)
│ └── check_dark.svg # 勾选框深色勾号(浅色强调色主题)
├── config/ # 默认配置文件目录
│ ├── main.json # 主配置
│ ├── system.json # 系统配置
│ ├── user.json # 用户配置
│ └── rules/ # 规则文件目录
│ └── rules.json # 规则定义
├── module/ # 插件源码目录(运行时扫描目录为可执行文件旁 module/)
│ ├── example/ # 示例空壳插件(验证插件接口与导出约定)
│ └── gsi/ # CS2 GSI 插件(路径查找、配置生成、SteamID 归属区分)
├── include/ # 公共头文件(按分类子目录存放,含 UI 文件)
│ ├── core/ # 核心基础设施:配置系统 + 日志系统
│ │ ├── AppConfig.h # 应用配置接口
│ │ ├── AppConfig_impl.hpp # 配置实现模板
│ │ ├── AppConfig_utils.hpp # 配置包装器工具类
│ │ ├── ConfigManager.h # 配置管理器
│ │ ├── ConfigManager_impl.hpp # 配置管理器模板实现
│ │ ├── MultiConfigManager.h # 多配置管理器
│ │ ├── MultiConfigManager_impl.hpp # 多配置管理实现模板
│ │ ├── ConfigStructs.h # 配置数据结构
│ │ ├── DefaultConfigs.h # 默认配置生成
│ │ ├── DebugLog.h # 调试日志接口
│ │ ├── DebugLog_utils.hpp # 日志工具函数
│ │ ├── Console.h # 控制台输出
│ │ ├── LogExporter.h # 日志导出器(导出设置与清理)
│ │ ├── LogExportSettingsDialog.h # 日志导出设置对话框
│ │ └── ProcessChecker.h # 进程运行状态检查
│ ├── bridge/ # Python 子进程通信
│ │ └── PythonSubprocessManager.h # Python 子进程管理
│ ├── rule/ # 规则引擎(含规则编辑 UI)
│ │ ├── Rule.h # 规则实体
│ │ ├── RuleManager.h # 规则管理器
│ │ ├── RuleManager_impl.hpp # 规则管理实现模板
│ │ ├── FormulaBuilderDialog.h # 公式构建对话框
│ │ ├── ParentEditDialog.h # 规则父级编辑对话框
│ │ ├── ComboBoxDelegate.h # 下拉框委托
│ │ └── ValueModeDelegate.h # 值模式委托
│ ├── module/ # 数值模块
│ │ ├── ModuleValue.h # 数值模型与查询周期枚举
│ │ ├── Module.h # 数据模块(一组数值)
│ │ ├── ModuleManager.h # 数值模块管理器(周期调度)
│ │ ├── ModuleValuesDialog.h # 模块数值展示对话框
│ │ └── DataListener.h # 通用数据接收器(多模块注册分发)
│ ├── plugin/ # 插件接口
│ │ ├── IPlugin.h # 插件纯虚基类(生命周期、自描述、日志回调)
│ │ └── plugin_export.h # 导出宏与 API 版本定义
│ ├── ui/ # 界面层(主窗口 + 通用控件)
│ │ ├── DGLABClient.h # 主窗口类定义
│ │ ├── DGLABClient.ui # Qt Designer 界面文件
│ │ ├── DGLABClient_impl.hpp # 主窗口模板实现
│ │ ├── DGLABClient_utils.hpp # 主窗口工具函数
│ │ ├── EditableLabel.h # 可编辑标签控件
│ │ ├── StyledComboBox.h # 统一下拉框控件(弹出样式与圆角)
│ │ ├── SampledWaveformWidget.h # 波形采样控件
│ │ ├── ThemeSelectorDialog.h # 主题选择对话框
│ │ └── IpSelector.h # IP 选择器单例
│ └── README.md # 头文件分类说明
├── licenses/ # 第三方许可证文件
│ ├── LICENSE.GPLv2.txt # QT 的 GPLv2 许可证
│ ├── LICENSE.LGPLv3.txt # QT 的 LGPLv3 许可证
│ └── LICENSE.MIT.txt # nlohmann/json 的 MIT 许可证
├── python/ # Python 后端脚本
│ ├── Bridge.py # 桥接模块(与 C++ 交互)
│ ├── WebSocketCore.py # WebSocket 核心逻辑
│ ├── PathFinder.py # 跨平台路径查找工具(Steam 游戏目录等)
│ └── README.md # Python 脚本说明
├── qcss/ # Qt 样式表(共 14 个主题文件)
│ ├── light.qcss # 浅色模式
│ ├── night.qcss # 深色模式
│ ├── charcoal_pink.qcss # 炭黑甜粉
│ ├── deepsea_cream.qcss # 深海奶白
│ ├── vine_purple_tea_green.qcss # 藤紫钛绿
│ ├── offwhite_camellia.qcss # 无白茶花
│ ├── dark_blue_clear_blue.qcss # 捣蓝清水
│ ├── klein_yellow.qcss # 克莱因黄
│ ├── mars_green_rose.qcss # 马尔斯玫瑰
│ ├── hermes_orange_navy.qcss # 爱马仕深蓝
│ ├── tiffany_blue_cheese.qcss # 蒂芙尼奶酪
│ ├── china_red_yellow.qcss # 中国红黄
│ ├── vandyke_brown_khaki.qcss # 凡戴克棕卡其
│ └── prussian_blue_fog.qcss # 普鲁士雾灰
├── screenshot/ # 截屏资源文件
│ ├── others/ # 其他截屏
│ ├── pages/ # 页面截屏
│ └── themes/ # 主题截屏
├── src/ # C++ 源文件(按分类子目录存放)
│ ├── core/ # 核心基础设施:配置系统 + 日志系统
│ │ ├── AppConfig.cpp # 应用配置实现
│ │ ├── ConfigManager.cpp # 配置管理器实现
│ │ ├── MultiConfigManager.cpp # 多配置管理器实现
│ │ ├── ConfigStructs.cpp # 配置数据结构实现
│ │ ├── DefaultConfigs.cpp # 默认配置生成实现
│ │ ├── DebugLog.cpp # 调试日志实现
│ │ ├── Console.cpp # 控制台输出实现
│ │ ├── LogExporter.cpp # 日志导出器实现
│ │ ├── LogExportSettingsDialog.cpp # 日志导出设置对话框实现
│ │ └── ProcessChecker.cpp # 进程运行状态检查实现
│ ├── bridge/ # Python 子进程通信
│ │ └── PythonSubprocessManager.cpp # Python 子进程管理实现
│ ├── rule/ # 规则引擎(含规则编辑 UI)
│ │ ├── Rule.cpp # 规则实体实现
│ │ ├── RuleManager.cpp # 规则管理器实现
│ │ ├── FormulaBuilderDialog.cpp # 公式构建对话框实现
│ │ ├── ParentEditDialog.cpp # 规则父级编辑对话框实现
│ │ ├── ComboBoxDelegate.cpp # 下拉框委托实现
│ │ └── ValueModeDelegate.cpp # 值模式委托实现
│ ├── module/ # 数值模块
│ │ ├── ModuleValue.cpp # 数值模型实现
│ │ ├── Module.cpp # 数据模块实现
│ │ ├── ModuleManager.cpp # 数值模块管理器实现
│ │ ├── ModuleValuesDialog.cpp # 模块数值展示对话框实现
│ │ └── DataListener.cpp # 通用数据接收器实现
│ ├── ui/ # 界面层(主窗口 + 通用控件)
│ │ ├── DGLABClient.cpp # 主窗口实现
│ │ ├── EditableLabel.cpp # 可编辑标签实现
│ │ ├── StyledComboBox.cpp # 统一下拉框控件实现
│ │ ├── SampledWaveformWidget.cpp # 波形采样控件实现
│ │ ├── ThemeSelectorDialog.cpp # 主题选择对话框实现
│ │ └── IpSelector.cpp # IP 选择器实现
│ └── README.md # 源码分类说明
├── .editorconfig # 编辑器代码风格配置
├── .gitattributes # Git 属性配置(换行符等)
├── .gitignore # Git 忽略文件规则
├── CHANGELOG.md # 更新日志
├── CMakeLists.txt # CMake 主构建脚本
├── CMakePresets.json # CMake 预设配置
├── CONTRIBUTING.md # 贡献指南
├── CodingStyle.md # 代码规范文档
├── DGLABClient.qrc # Qt 资源收集文件
├── LICENSE.txt # MIT 许可证
├── NOTICE.txt # 声明
├── NextStep.md # 项目未来计划方向
├── README.md # 项目说明文档
├── main.cpp # 程序入口
└── qt.cmake # Qt 相关 CMake 配置片段
点击展开查看界面截图(共 6 张)
| 首页 | 配置页 | 规则文件管理页 |
|---|---|---|
![]() |
![]() |
![]() |
| IP 选择器 | 主题选择器 | 公式构建器 | 调试控制台 (Windows) |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
更多主题截图请查看
screenshot/themes/目录,共 14 种配色方案。
- 文件编码与换行: UTF-8 without BOM,换行符使用 CRLF(Windows 风格),文件末尾保留一个空行。
- 缩进与空白: 使用 4 个空格缩进(不使用 Tab),左括号采用 K&R 风格(与语句同行)。
- 命名规则: 类名用
PascalCase,变量/函数名用snake_case,常量/宏用UPPER_CASE;Python 模块名使用PascalCase。 - 导入顺序: 自定义模块 > 第三方库 > 标准库,每组内按字母顺序排列。
- 注释规范: 独占一行的注释
//后跟空格,与代码同缩进;行尾注释与代码间隔一个 Tab,//后跟空格。
详细的编码规范请参阅项目根目录下的 CodingStyle.md。
注意: 仅需最后一次提交/发布时确保符号规范即可,无需在日常开发中严格遵守。
Q1: CMake 配置时找不到 Qt
现象:
Could not find a package configuration file provided by "Qt6" or "Qt5"
解决方案:
- 确保 Qt 已正确安装,并且
CMAKE_PREFIX_PATH指向 Qt 的安装目录(例如C:/Qt/6.5.0/msvc2019_64)。 - 或者设置环境变量
Qt6_DIR/Qt5_DIR。 - 在 Linux 上可以通过包管理器安装 Qt 开发包(如
qt6-base-dev),CMake 通常能自动找到。
Q2: Python 子进程启动失败,提示找不到模块
现象: 程序启动后日志显示 Bridge.py 报错 ModuleNotFoundError: No module named 'websockets' 或 qrcode。
解决方案:
- 确认已执行
pip install websockets qrcode[pil]。 - 检查 Python 环境: 确保运行客户端时使用的 Python 解释器与安装依赖的是同一个。可以在命令行执行
pip show websockets查看安装位置。 - 若使用虚拟环境,需要在 CMake 配置时指定
-DPython_ROOT_DIR指向虚拟环境的路径。
Q3: nlohmann/json 下载失败
现象: CMake 配置过程中报错,无法从 GitHub 下载 json.hpp。
解决方案:
- 检查网络连接,确保能够访问
raw.githubusercontent.com。 - 手动下载 json.hpp 并放入构建目录的
/include/nlohmann/下(根据 CMake 输出路径调整)。 - 或者修改
CMakeLists.txt,改用本地已下载的头文件路径。
Q4: 编译时出现 C++20 相关语法错误
现象: 编译器报错如 'std::span' is not a member of 'std' 或要求 C++20 标准。
解决方案:
- 确认编译器版本: GCC ≥10、Clang ≥12、MSVC ≥2022。
- 在 CMake 配置时显式指定 C++ 标准:
-DCMAKE_CXX_STANDARD=20。 - 若使用旧版 IDE(如 VS2019),需要升级到 VS2022 或安装支持 C++20 的工具集。
Q5: 配置文件修改后不生效
现象: 修改了 user.json 中的某个值,但程序运行时使用的还是旧值。
解决方案:
- 确认修改的文件是正确的(注意优先级:
user.json>system.json>main.json)。如果system.json或main.json中定义了相同路径的配置,它们会被覆盖,但不会删除。 - 重新执行 CMake 构建项目或手动将修改后的配置文件复制到构建目录的
/config/下。 - 检查 JSON 格式是否正确(如多余的逗号),可以使用在线 JSON 校验工具。
Q6: 配置文件丢失后如何恢复?
现象: 误删了 config/ 目录下的某个 JSON 文件,程序启动报错或使用默认值。
解决方案:
- 程序内置了默认配置(定义在
DefaultConfigs.cpp),丢失的文件会在运行时自动重新生成(前提是目录存在)。只需确保config/目录可写。 - 也可以从源码仓库中复制
config/目录下的示例文件到运行目录。
Q7: 更改客户端所使用的端口遇到问题?
更改方法:
- 编译运行客户端后,打开
配置页面,在端口中输入 你需要更换的端口 并点击右侧的确定,若显示端口更改成功即完成更改。 - 在默认的配置文件
system.json中app.websocket.port即是所用端口
若在项目根目录更改,请确保构建输出目录下的配置文件也应用更改!端口范围建议使用 1024
65535 之间的数字,避免使用系统保留端口(01023)。
现象
通过上述方式设置了端口,但应用的仍然是旧的或默认端口 9999。
解决方法
- 点击
确定后根据错误信息更换端口再次点击确定尝试更改。 - 尝试重新执行 CMake 构建项目或手动自定义的 JSON 规则文件放入构建目录的
config/下。
Q8: 规则文件在 UI 中不显示
现象: 将自定义的 JSON 规则文件放入 config/rules/ 目录,但在客户端的规则页面中看不到。
解决方案:
- 检查文件名是否包含默认配置文件
main.json中项rule.key指定的关键字(默认为rule)。例如my_rules.json包含rule字样,可以被识别;settings.json则不会。 - 确认规则文件的 JSON 格式正确,且根对象包含
"rules", "version", "DGLABClient"键。 - 尝试重新执行 CMake 构建项目或手动自定义的 JSON 规则文件放入构建目录的
config/rules/下。
Q9: 规则评估生成的命令不符合预期
现象: 调用 RuleManager::evaluate_command("rule_name", args...) 返回的 QJsonObject 中 value 字段的值不正确(如始终为 0 或未按公式计算),或者通道/模式与设置不符。
可能原因及解决方案:
| 原因 | 解决方法 |
|---|---|
传入的参数个数与规则中的 {} 占位符数量不匹配 |
检查规则模式中 {} 的个数,确保传入参数数量一致。若参数不足,表达式中的剩余 {} 不会被替换,导致求值失败(日志会输出“参数数量不匹配”错误)。 |
| 值表达式求值失败(如语法错误、除零等) | 查看日志中的 Rule::computeValue 错误信息,表达式错误会输出 表达式求值失败: ...。修正 value_pattern 中的表达式(例如确保运算符正确、括号匹配)。 |
| 计算结果被模式钳位规则修正 | 模式 2(“设为”)将值钳位到 [0, 200];模式 3/4(“连减”/“连增”)钳位到 [1, 100]。如果期望的值超出范围,会被自动限制,这是正常行为。 |
| 通道无效(非 "A" 或 "B") | Rule::normalize_channel 会将通道规范化为 "A"/"B",无效通道(如 "C"、空字符串)会导致命令中使用默认通道 1(即 A)。检查规则定义中的 channel 字段是否为 "A" 或 "B"(大小写不敏感)。 |
| 模式值超出 0~4 范围 | 模式仅支持 0~4,若配置了其他值,行为未定义。请在规则文件中使用正确的模式编号。 |
调试建议:
- 启用 DEBUG 日志级别(
app.log.console_level = 0),查看RuleManager和Rule模块输出的详细日志,包括加载的规则参数、表达式求值过程和结果。 - 使用
RuleManager::get_rule_value_pattern("rule_name")获取原始表达式,手动验证计算逻辑。 - 若使用 Qt 界面中的公式构建器,注意保存前检查括号平衡性。
Q10: 公式构建器中的合法性检查报错
现象: 在“配置”页面的“值模式”列双击打开公式构建器,无法保存。
可能原因及解决方案:
| 原因 | 解决方法 |
|---|---|
表达式中包含空格(如 a + b) |
移除所有空格,或使用花括号包裹变量名(如 {a}+{b}) |
变量名未用花括号包裹(如直接写 a、price) |
所有变量必须写成 {变量名} 形式,例如 {a}+{b} |
使用了非法字符(如字母、小数点、等号等未在 + - * / ( ) { } 中的符号) |
只允许运算符、括号和花括号;变量必须放入 {} 内 |
| 表达式为空 | 输入有效的公式表达式 |
| 括号不匹配(缺少右括号或有多余右括号) | 检查左右括号数量是否一致,确保 ( 后有对应的 ) |
运算符连续出现(如 a++b)或出现在开头/结尾(如 +a 或 a+) |
检查运算符是否成对出现在操作数之间,不能以运算符开头或结尾 |
缺少闭合的 }(如 {a) |
确保每个 { 都有对应的 } |
左括号位置不正确(如 a( 或 {a}( 等连续操作数后紧跟左括号) |
左括号前必须是运算符或表达式开头,不能直接跟在操作数后 |
右括号前缺少操作数(如 () 或 a+()) |
括号内必须有有效的表达式,不能为空 |
表达式以运算符结尾(如 a+) |
最后一个字符不能是运算符 |
Q11: 主程序无法连接到 Python 子进程(TCP 连接失败)
现象: 日志显示 Failed to connect to Python bridge: Connection refused。
解决方案:
- 确认
Bridge.py是否正常启动。查看控制台输出(如果开启了调试控制台)。 - 检查端口是否被占用。
Bridge.py默认使用第一个可用的端口(从 5000 开始递增),主程序会从子进程的 stdout 解析端口号。 - 防火墙可能阻止了本地回环连接,尝试暂时关闭防火墙测试。
Q12: Python 子进程启动后立即退出
现象: 主程序启动后日志显示 Python process exited with code X。
解决方案:
- 手动运行
python Bridge.py查看错误输出。常见原因: 缺少依赖(websockets)、Python 版本过低(需要 3.9+)、文件路径错误。 - 确保
WebSocketCore.py与Bridge.py在同一目录下。 - 检查配置文件中的
python.bridge_path是否正确指向Bridge.py。
如: "bridge_path": "./python/Bridge.py"
Q13: 波形控件不显示或波形无变化
现象: 添加了 SampledWaveformWidget 但界面显示空白或波形静止。解决方案:
确认已正确调用 input_data() 传入有效数值(0~1)。
检查采样定时器是否启动: 默认在构造函数中自动启动,若手动停止需重新调用 sampletimer.start()。
确认控件大小不为 0,否则 paintEvent 可能无法正常绘制。
查看日志是否有 SampledWaveformWidget 模块的错误输出。
Q14: 如何同时显示多条波形曲线?
解决方案: 使用多监听器功能。
- 调用
add_listener(name, color)添加监听器,每个监听器对应一条曲线。 - 为每个监听器分别调用
input_data(listener_name, value)输入数据。 - 控件会自动为每个监听器维护独立的采样缓冲区,并以各自颜色绘制波形。
- 可通过
set_listener_color(name, color)动态修改曲线颜色。
Q15: 如何移除不再需要的曲线?
解决方案: 调用 remove_listener(name),注意不能移除默认的 "default" 监听器。移除后该监听器的数据会被清除,不再绘制。
Q16: 端口被占用,无法启动服务
现象: 启动 Node.js 服务时报错 Error: listen EADDRINUSE: address already in use :::9999。
解决方案:
- 检查是否有其他程序占用了 9999 端口,可以通过
lsof -i :9999(Linux/macOS)或netstat -ano | findstr :9999(Windows)命令查看。 - 更换其他端口,在
.env文件中修改PORT配置。
若更换端口,请修改客户端所使用的端口,详细请参考 常见问题 - 配置问题
Q17: 手机 App 无法连接到 WebSocket 服务
可能原因及解决:
| 原因 | 解决方法 |
|---|---|
| 手机与电脑不在同一局域网 | 确保手机连接与电脑相同的 Wi-Fi 网络 |
| 防火墙拦截了端口 | 在防火墙中放行 WebSocket 服务使用的端口(默认 9999) |
| 二维码中的地址错误 | 检查二维码中的 IP 地址是否为电脑的正确局域网 IP,不要使用 127.0.0.1 或 localhost |
| 服务未正常启动 | 检查终端窗口是否有错误输出,确保服务正在运行 |
| 某些 VPN 会拦截非代理流量 | 关闭 VPN |
Q18: 服务启动后控制台没有输出信息
这是正常现象。根据官方仓库说明,服务启动后控制台不会输出任何内容。如需查看调试信息,可参考官方仓库提供的调试信息开关方式。
Q19: 配对失败,提示 400 或 401
错误码说明:
| 错误码 | 说明 |
|---|---|
| 400 | 此 ID 已被其他客户端绑定 |
| 401 | 要绑定的目标客户端不存在 |
| 402 | 收信方和寄信方不是绑定关系 |
| 404 | 未找到收信人(离线) |
解决方法: 检查二维码是否正确生成了 clientId,确保客户端与服务端的连接未中断,并确认没有重复绑定。
Q20: 消息发送失败,提示 405
原因: JSON 消息长度超过了 1950 字符,APP 会丢弃该消息。
解决方法: 将消息拆分为多条发送,确保每条消息长度在 1950 字符以内。
Q21: 日志在 Qt 界面不显示或显示等级不对
现象: 程序运行时,首页的日志窗口没有输出,或者只显示 ERROR 级别,而配置中设置了 INFO。
解决方案:
- 检查配置文件中的
app.log.ui_log_level值(0=DEBUG,1=INFO,2=WARN,3=ERROR,4=NONE)。例如需要显示 INFO 及以上,应设置为 1。 - 确认没有在代码中动态修改 UI 日志的 sink 级别(
DebugLog::set_log_sink_level("qt_ui", level))。 - 如果使用
LOG_MODULE宏,确保模块名称和函数名称正确,且日志等级不低于全局设置。
Q22: Windows 调试控制台不出现
现象: 在配置文件中设置了 "debug": true,但程序启动时没有弹出黑色控制台窗口。
解决方案:
- 仅当编译为 Debug 配置且
app.debug为 true 时才会创建控制台。Release 模式下可能被优化掉。 - 检查是否在 Windows 平台(该功能依赖
<windows.h>,Linux/macOS 无效)。 - 尝试以管理员身份运行程序。
Q23: EditableLabel 编辑后文本未保存或验证不通过
现象: 双击可编辑标签,修改文本后按回车或失去焦点,文本恢复原值或显示验证失败。
解决方案:
- 检查是否设置了验证器(
set_validator),且输入内容不符合验证规则(例如QIntValidator限定了范围)。 - 确保没有在
text_edited信号的槽函数中拒绝修改(如恢复旧值)。 - 可编辑标签默认允许所有输入,若需要限制输入格式,请正确配置验证器。
Q24: IP 自动选择得到 127.0.0.1,但明明有真实网卡
现象: IpSelector::auto_select_ip() 返回 127.0.0.1,而电脑实际有有效的局域网 IP。
解决方案:
- 检查黑白名单是否误将真实网卡过滤。默认黑名单包含
vmware,virtual,docker,vbox,如果您的真实网卡名称包含这些关键词(例如虚拟机网卡),请通过show_selection_dialog()移除相关关键词或手动选择 IP。 - 也可以通过代码动态修改黑白名单:
IpSelector::instance()->set_blacklist(您的列表)。 - 如果所有网卡都被过滤,或没有找到任何非回环 IP,函数会返回
127.0.0.1作为保底。
欢迎提交 Issue 和 Pull Request。在贡献前请确保:
- 代码遵循现有风格(缩进 4 空格,使用
#pragma once,命名规范)。更多细节请参考 编码规范 及 CONTRIBUTING.md。 - 请务必使用 UTF-8 without BOM 编码提交代码。
- 添加或修改功能时更新相关文档(如 README.md)。
- 确保本地测试通过(编译通过,功能正常)。
- 对于较大的改动,请先开 Issue 讨论。
详细的贡献流程、代码提交规范及 Pull Request 指南,请查阅 CONTRIBUTING.md
本项目自身源代码采用 GNU General Public License v3.0 only(GPL-3.0-only)开源。详情请参阅项目根目录下的 LICENSE 文件。
本项目依赖的第三方组件适用不同的许可证:
- Qt 框架(Core, Gui, Widgets, Network, Qml): GNU Lesser General Public License v3.0(LGPLv3)
- nlohmann/json: MIT 许可证
有关第三方许可证的完整声明和文本,请查看 NOTICE.txt 及 licenses/ 目录。
- 作者: [CrimsonSeraph]
- BiliBili: 浪天幽影(UID: 1741002917)
- X: 𝒞𝓇𝒾𝓂𝓈𝑜𝓃𝒮𝑒𝓇𝒶𝓅𝒽✟升天✟(@CrimSeraph_QwQ)
- 项目主页: https://github.com/CrimsonSeraph/DG-LAB-Client






