# Logger01 **Repository Path**: weekend/logger01 ## Basic Information - **Project Name**: Logger01 - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-12 - **Last Updated**: 2026-10-31 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Logger01 采集设备调试工具(Qt5 Widgets):通过 TCP 或串口连接 M20x 系列采集设备(支持主机模块串接多台从机),下发采集参数、控制推流启停、查询设备信息,并实时打印解析后的数据帧与原始收发报文。 ## 功能 - **多设备管理**:网格卡片视图,勾选设备后对其执行批量操作;设备列表持久化(config.json) - **局域网设备发现**:UDP 广播搜索在线设备(IP/型号/序列号),右键可复制;每台设备可下发"动态ip"指令,令其在 eth0 上重新 DHCP 获取地址 - **TCP / 串口 / HU(USB) 三传输**:TCP 自动绕过系统代理;串口自动枚举可用端口,参数可配置;HU 设备经 CH375DLL 访问 USB 批量端点(需厂商驱动,运行时自动探测 DLL) - **参数下发**(异步队列,不阻塞界面):采集速度(1s/100ms/10ms/1ms)、通道类型(协议类型表 29 种,统一应用到 8 通道),等待设备 `set ok` 应答,超时可配置 - **推流控制**:启动/停止命令按链路自动选择(网口 `00 02` / 串口 `00 01` 变体) - **设备信息查询**:解析从机信息帧(设备号/型号/协议版本/通道数/通道类型/采样周期) - **数据帧解析**:流式扫描(处理粘包/半包/坏帧重同步),解码设备号/包计数/传输参数/时间戳/数值(float 等常见类型) - **日志**:界面实时展示 + 按天分文件双写;原始收发全量 hex + 解析摘要每包必记;区分协议应答与坏数据 ## 协议要点(M20x数据传输协议V2) - 设备为 **TCP 服务端**(默认端口 **8655**),连接后不主动推流,必须下发启动命令 - modbus 格式,广播地址 FF,CRC16-MODBUS 低字节在前 - 关键命令:启动推流 `FF 10 00 32 ... 00 02`(串口变体 `... 00 01`)、停止 `... 00 00`、速度 `00 17`、通道类型 `00 11`、设备信息 `00 35` - 设备应答:参数设置回 `set ok %02d`,设备信息回文本行 + N×32 字节二进制帧 - 串接从机共用一条主机链路,靠数据帧内设备号区分 ## 设备发现(UDP 广播 16880) "设备发现"页基于厂商私有 UDP 协议(逆向自其"设备可视化工具"),与 M20x 数据协议无关。原理是"广播喊口令,在线设备报名字": - 设备固件常驻监听 **UDP 16880** 端口 - 主机向 `255.255.255.255:16880` 广播 ASCII `"upload"`,在线设备以单播回包到广播包的源端口: ``` IP: model: <型号> SNUM: <序列号> ``` - 设备真实 IP 取**应答包源地址**,不信任报文内 `IP:` 字段(部分固件该字段有缺陷:输出 `"IP: ("` 或残留跨网段旧值);结果按 IP 去重,并过滤本机广播回环 - 程序优先绑定 16880 端口(与原工具一致),被占用时退回临时端口——设备是回源端口应答的,绑定位置不影响收包 - "动态ip"按钮广播 `"<序列号>+udhcpc -i eth0"`:设备端把 `+` 之后的文本交给 shell 执行,让目标设备在 eth0 上重新走 DHCP(序列号为空时所有在线设备都会执行),下发 2 秒后自动重搜刷新 IP 注意:该协议本质是不鉴权的远程命令通道(`+` 后可携带任意命令),设备发现只是其中最无害的一条指令,仅限内部可信网段使用。 ## 构建 - Qt 6.8.3(msvc2022_64)+ CMake ≥3.16 + VS2022 - `CMakeLists.txt` 中 `STATIC_QT` 开关(默认 OFF,`package.bat` 缺省按静态出包):ON 使用静态 Qt(`msvc2022_64-static` 前缀,静态 CRT + 平台插件内链),OFF 使用动态 Qt - 静态 Qt 自建脚本:`build-qt6-static.bat`(qtbase + qtserialport + qtmultimedia + qtspeech,安装到 `F:\Qt\Qt6.8.3\6.8.3\msvc2022_64-static`) ### Tab 模块 除"设置"页外的顶层界面各自独立成 CMake 模块:`tab_capture`(采集调试)/ `tab_tpconfig`(设备配置)/ `tab_voice`(语音调试,还需 Qt Multimedia/TextToSpeech)/ `tab_discovery`(设备发现);AI 助手为 exe 内置(面板 UI + AgentCore,显示依附采集模块)。模块**只含业务核心(无 UI)**,页面 UI 全部在主程序 —— Widgets 与 QML 两套前端共用同一批核心,核心加载成功才挂载对应页面,形态随 `STATIC_QT` 切换: | | 静态 Qt(含 RK3588/Linux) | 动态 Qt(Windows) | |---|---|---| | 模块产物 | 静态库链进 exe | DLL 收进包内 `tabs\` 子目录 | | 裁剪方式 | `-DLOGGER_TAB_<名>=OFF` 编译期裁剪(不编译不链接,两套前端都无此页) | 不生成(或不打包)对应 DLL;主程序运行时逐个探测 `tabs\*.dll`,**有就加载,没有就不加载** | | 跨界机制 | 同一二进制 | Qt 插件接口(`src/tabs/tabPlugin.h`:`createCore` 返回业务核心对象 + `*Ops` 能力接口 + 字符串式信号),基础设施符号由 exe 导出(`ENABLE_EXPORTS`),Tab DLL 链接其导入库 | 模块名: `capture`(采集调试)/ `tpconfig`(设备配置)/ `voice`(语音调试,还需 Qt Multimedia/TextToSpeech)/ `discovery`(设备发现)。 ## 第三方组件 | 组件 | 版本 | 来源 | 位置 | 许可证 | 用途 | |---|---|---|---|---|---| | [spdlog](https://github.com/gabime/spdlog) | **v1.14.1** | github gabime/spdlog(官方 release tarball) | `src/3rdparty/spdlog` | MIT | 日志库:`linfo/lwarn` 等 fmt 语法宏,取代 qInfo/qWarning 流式输出(无引号、编译期格式检查) | - 源码经**裁剪入库**:只保留 `include/ src/ cmake/ CMakeLists.txt LICENSE README.md`,删去 `tests/ bench/ example/ logos/ scripts/ appveyor.yml`,构建零联网(原 FetchContent 方案每个构建目录首次 configure 都要下载一次,已弃用) - 通过 `add_subdirectory(src/3rdparty/spdlog EXCLUDE_FROM_ALL)` 引入,header-only(`spdlog::spdlog_header_only`),使用其捆绑的 fmt,无外部依赖 - 升级方法:从官方 release 下载新版 tarball,同样裁剪后覆盖 `src/3rdparty/spdlog`,并更新本表版本号 - 其余第三方:`voice/3rdparty/`(sherpa-onnx 运行库,不入库,见 `voice/README.md`) ## 打包 ``` package.bat # 缺省静态: 一次出两个包 — # Logger01_<日期>__static 全量 (~270MB, 含语音模型/sherpa DLL) # Logger01_<日期>__static_capture 仅采集调试 Tab (~21MB) package.bat dynamic # 动态: windeployqt 收集依赖 + tabs\ 内 Tab 模块 DLL package.bat static voice,discovery # 静态裁剪: 显式列表出单包 (语音/设备发现不编译不链接) package.bat dynamic voice # 动态裁剪: 不生成 tab_voice.dll ``` - 产物输出到 `package\`;cmd 把逗号当参数分隔符,`voice,discovery` 与 `voice discovery` 等价;可裁模块:`capture / tpconfig / voice / discovery / ai / datapreview` - `_static_capture` 计划裁掉 tpconfig/voice/discovery/ai 四个模块,且**不带语音模型与 sherpa-onnx DLL**(ASR 编译为桩),适合只需要采集调试的场景 - "出包计划"模式:静态且不带裁剪参数时一次出上面两个包,其余情况(dynamic 或显式裁剪列表)出单个包。每个计划用**独立构建目录** `build\<计划名>`,避免不同裁剪组合共用构建目录时 CMake 缓存互踩 - Tab 开关每次显式全量传入再按裁剪列表置 OFF,防止上一次裁剪状态残留在 CMake 缓存里 - QML 前端依赖:动态包由 windeployqt 按 `--qmldir src/qmlui/qml` 自动收集(QtQuick/Controls/Material 等);静态包 QML 已内链进 exe,无需额外文件 动态包裁剪也可以直接删包内 `tabs\tab_<名>.dll`,下次启动该 Tab 即不存在。 ## QML 前端 同一 exe 内置两套前端,业务同源(模块核心、基础设施、RPC/MCP/AI 工具链完全共用): ``` Logger01.exe # 默认 Widgets 界面 (功能完整) Logger01.exe --qml # QML 界面 (Material 样式, 亮暗随 config.json 的 theme) Logger01.exe --widgets # 显式 Widgets (命令行优先级高于 config) ``` 也可在 `config.json` 写 `"ui": "qml"` 切换默认前端(缺省 `"widgets"`,首次运行自动写入该字段)。 页面与 Widgets 前端全量对齐:采集调试(设备网格/采集配置/操作/日志/实时数值/统计)、设备发现、设备配置(串口读写/菜单项编辑写回)、语音调试(节点状态灯/事件日志/参数滑条)、设置、AI 抽屉(语音唤醒自动弹出)。两套界面可随时互换,配置与设备状态实时共享。密度:Material Dense 紧凑变体 + 12px 全局字号(系数见 `src/qmlui/qmlUi.cpp`)。 ## 使用 1. 点"添加"输入 `ip:端口`(如 `192.168.0.123:8655`)或选择串口及参数;地址自动去重。不知道设备 IP 时,可在"设备发现"页点"搜索设备"广播查找在线设备(协议见下文) 2. 勾选设备(勾选 = 参与批量操作),点"连接";双击卡片可编辑地址 3. (可选)在"采集配置"区选好速度/通道类型,点"下发配置",等待设备回 `set ok` 4. 点"启动"开始推流,"停止"结束;点"查询设备"查看串接的从机信息 5. 命令超时可在操作区调整(默认 5000ms,修改立即生效) 注意:设备推流状态会跨连接保持——程序异常退出后重连,数据会自动恢复(无需再点启动);主动停止即可关闭推流。 ## 代码结构 ``` src/ ├── main.cpp # 装配: 初始化日志 → 读配置 → 前端选择 (--qml/config) → 启动 RPC ├── mainwindow.h/.cpp # Widgets 主窗口壳: 模块组装 + "设置"页 + AI Dock + RPC 委托 ├── logSink.h # 日志界面输出端抽象 (sink: Widgets 文本框 / QML LogModel) ├── logHistory.h/.cpp # 会话日志历史回看游标 (滚近顶部向前续载, 两种日志视图共用) ├── widgetsLogSink.h/.cpp # Widgets 日志 sink (跟随/冻结/历史回看, 原 Logger 内置逻辑) ├── rpcTarget.h # RPC 控制目标抽象 (Widgets/QML 壳各自实现) ├── tabPlugins.h/.cpp # Tab 模块插件加载 (静态适配器 + 动态 DLL 探测, 双前端共用) ├── ui/ # Widgets 页面 (宿主侧): capturePage 采集页 / aiDock AI 停靠面板 ├── qmlui/ # QML 前端: qmlUi 装配 + logModel/settingsController/ │ aiPanelController 控制器 + qml/ 页面 (Material) ├── core/export.h # LOGGER_API 模块边界导出宏 (动态 Tab 模式) ├── tabs/ │ ├── tabPlugin.h # 宿主 <-> 模块跨界契约 (纯逻辑插件接口 + 能力接口 + TabContext) │ ├── capture/ # tab_capture「采集调试」核心: CaptureSession (设备列表模型/ │ │ 客户端接线/采集控制) + RPC 能力; 页面 UI 在 src/ui/ │ └── discovery/ # tab_discovery「设备发现」: UdpDiscovery 核心 + 发现页 (宿主侧) ├── tpconfig/ # tab_tpconfig「设备配置」: TPConfig 菜单协议串口读写 ├── voice/ # tab_voice「语音调试」: VAD/ASR/TTS 链路 + 调试面板 (可选编译) ├── devices/ # 基础设施: 设备客户端 (M 卡 TCP/串口, H 卡 USB) + 信息/帧解析 │ (udpDiscovery.h/.cpp 归属 tab_discovery 模块) ├── agent/ # AI 助手核心: OpenAI 兼容 function calling 工具循环 ├── appconfig.h/.cpp # config.json 读写 (设备列表 + 命令超时 + 采集配置 + AI 配置) ├── recordConfig.h/.cpp # 采集配置 (速度/通道类型/量程, 仅协议可下发的设备侧参数) ├── logger.h/.cpp # 日志双写单例 (界面文本框 + 会话分文件 + 历史回看) ├── liveValues.h/.cpp # 实时数值仓库 (解析线程写 / GUI 定时读) ├── statsStore.h/.cpp # 运行统计仓库 (链路/帧层/系统健康) ├── rpcServer.h/.cpp # 外部控制 RPC (TCP + 行 JSON, 见下文) ├── protocol.h # 协议常量唯一来源(命令帧/类型表/档位) ├── themeManager.h/.cpp # 亮色/深色/跟随系统主题 ├── app.rc / resources.qrc # exe 图标与窗口图标资源 └── shiliu.ico ``` ## 外部控制 RPC 程序启动后监听所有网卡的 `7777` 端口(`config.json` 的 `rpcPort` 可改,0 = 禁用),局域网内其它机器可通过 `<本机IP>:7777` 访问(无鉴权,仅限内部工具使用),每行一个 JSON 命令: | 命令 | 参数 | 说明 | |---|---|---| | `status` | - | 设备列表及状态 | | `start` / `stop` | - | 启动/停止推流(勾选且已连接的设备) | | `apply_config` | `speed`(0~3), `channelType` | 下发采集配置 | | `query_devices` | - | 查询从机信息(结果看日志) | | `connect` / `disconnect` | `addr` | 连接/断开指定设备 | | `last_raw` | - | 最近一次原始字节录制(采集页勾选「录制 .raw」)的文件路径,空 = 尚无录制;「数据预览」页的「载入最近录制」即基于此语义 | 服务端同时兼容裸 TCP 行 JSON 和 HTTP 两种方式,`GET /状态命令` 可直接查询: ``` curl http://127.0.0.1:7777/status ``` curl 测试示例(全部命令): ```bat rem 查询设备列表及状态 curl -s http://127.0.0.1:7777 -d "{\"cmd\":\"status\"}" rem 启动 / 停止推流 curl -s http://127.0.0.1:7777 -d "{\"cmd\":\"start\"}" curl -s http://127.0.0.1:7777 -d "{\"cmd\":\"stop\"}" rem 下发采集配置 (speed: 0=1s 1=100ms 2=10ms 3=1ms; channelType: 如 15=0x0F K型) curl -s http://127.0.0.1:7777 -d "{\"cmd\":\"apply_config\",\"speed\":0,\"channelType\":15}" rem 查询从机信息 (结果看 Logger01 日志) curl -s http://127.0.0.1:7777 -d "{\"cmd\":\"query_devices\"}" rem 连接 / 断开指定设备 curl -s http://127.0.0.1:7777 -d "{\"cmd\":\"connect\",\"addr\":\"192.168.0.123:8655\"}" curl -s http://127.0.0.1:7777 -d "{\"cmd\":\"disconnect\",\"addr\":\"192.168.0.123:8655\"}" ``` Python 示例: ```python import socket, json s = socket.create_connection(("127.0.0.1", 7777)) s.sendall(b'{"cmd":"status"}\n') print(s.recv(65536).decode()) ``` ### 收发调试(`cmd=link`) 收发调试页的 RPC 能力(`op` 经 `{"cmd":"link","op":...}` 调用,全部同步等应答): | op | 参数 | 说明 | |---|---|---| | `open` / `close` | `device=设备记录名`(直取记录参数)或 `type=serial`(`serialPort/baud/dataBits/parity/stopBits`) 或 `tcp_client`/`tcp_server`/`udp`/`udp_server`(`host/port/localPort`,显式键可覆盖设备记录);`type=attach` 附着采集链路 | 开/关链路 | | `status` / `log` | `lines` | 链路状态 / 收发日志尾部 | | `ports` | - | 本机串口列表 | | `specs` / `mappings` | `spec` 过滤 | 指令集列表 / 映射库 `{id,name,request,expect,note,noCrc}` | | `send` | `mapping=名称或MC id`(可 `spec` 校验)或 `hex=`+可选`crc`;`waitMs≤5000` | 发一条并同步等应答,响应带 `sent/rxLen/rx/got/note` | | `testspec` | `spec`, `waitMs`(缺省 800), `only=[id\|name,...]` 可选子集 | 逐条发集内指令等应答,返回每条 `{id,name,sent,rxLen,rx,ok}` | | `templates` | - | 解析模板列表 `[{name,columns}]` | | `template` | `name` | 切解析模板 | | `periodic` | `on=bool`, `ms`(10~600000, 缺省 1000) | 周期发送开关(同页面「周期」勾选) | | `clear` | - | 清零:日志/暂存/收发计数 + 接收保存缓冲 | | `save` | `path`(缺省 `link_时间戳.pkg`,相对 exe 工作目录) | 保存累计接收,响应 `{path,bytes,wrapped}`(wrapped=缓冲已滚动仅含最新) | | `scripts` / `script_get` | `name` | 指令脚本列表 / 完整场景 JSON | | `script_set` | `name`(可选,覆盖场景内名), `scenario=场景JSON`(或字段平铺) | 脚本入库/更新(`script_get` 逆操作) | | `runscript` | `name`, 可选 `waitMs` 覆盖 | 执行库中脚本(见下) | | `runseq` | `scenario=场景JSON 对象`(或把 `steps` 平铺进请求) | 执行内联场景,响应同 `runscript` | **ok 判定口径**(`testspec`/脚本一致):映射期望应答可解析为模板 → 收到数据后在共享缓冲里按模板锚点/结构定位应答帧,匹配成功即过(推流混杂帧被锚点跳过不干扰);为「无应答 (空映射)」→ **传输层送达确认**为过(TCP=排空发送缓冲且对端接收层已确认收到, 串口/UDP=写成功;收到数据与否不影响判定)。外部脚本按响应的 `expect` 自行判定即可(`tools/devconsole.py` 已实现)。 **存储**:指令调试数据(解析模板/指令映射/指令集/设备/上行指令/脚本)v35 起整体外置在 exe 旁 **`instructions.json`**(独立指令配置),不再占用共享 `config.json`(旧 `linkDebug` 节首次加载自动迁移并删除)。指令映射带显式 `noCrc` 开关:`true` = 无帧尾 CRC 帧(Modbus TCP/MBAP、文本帧,帧原样保存原样发送,不拼 CRC 也不参与加载自愈);缺省 = Modbus RTU 帧(帧尾 CRC16 自动校验/修复)。 **指令脚本**:存 `instructions.json` 的 `scripts`,页面「脚本…」按钮管理(编排/执行/导入/导出),步骤类型:`send`(指令集内指令) / `raw`(原始 hex, 可 `crc`) / `delay` / `loopBegin..loopEnd`(可嵌套) / `assert`(`reply`/`noreply`/`contains`/`template`=应答匹配指定解析模板) / `run`(调用其他脚本, 嵌套≤3 层) / `template`(切解析模板, 值=模板名) / `open`(打开设备, 值=设备记录名或 connection 参数对象; 已开则先关再开, 保证脚本从确定状态起步)。执行模型:所有收到字节即时入**共享接收缓冲**,断言(模板/包含/有应答)与后续判定都从缓冲读取;`send`/`raw`/`assert` 的 `waitMs` = **超时**(等待数据的最长时限,数据一到立即判定通过进入下一步,满超时仍不满足=本步失败;0=用脚本缺省等待);无应答哨兵指令以送达确认判过。每步可带 `onFail`(`stop`/`continue`, 缺省 `stop`)按步覆盖失败策略(全局「首败即停/缺省等待」已退役,脚本级 `stopOnFail`/`waitMs` 字段仅作旧场景兼容);`open` 的 `waitMs` = 打开/连接超时 (缺省 3000ms),结果行带回链路描述。场景 JSON 即对外契约: ```json { "name": "TP1000H 读抽检", "connection": { "type": "tcp", "host": "192.168.0.154", "port": 502 }, "waitMs": 800, "stopOnFail": true, "steps": [ { "send": "TP1000H·读采集档位(FC03 寄存器0)", "delayAfter": 200 }, { "assert": "reply" }, { "loopBegin": 3 }, { "send": "TP1000H·读存储开关(FC03 寄存器2)", "delayAfter": 300 }, { "assert": "reply" }, { "loopEnd": true } ] } ``` - `connection` 缺省 = 用 App 当前已开链路;写了则仅在**链路未开时**按它开(已开则沿用,执行完不关)。`connection` 支持设备名快捷写法 `{"device": "M208 - TCP"}`(直取设备记录全参数,显式键可逐项覆盖)。 - `send` 的值 = MC id 或映射名(改指令名后建议同步更新脚本,或用 MC id 引用)。 任意能发 HTTP 的客户端即可整场回放(一次 POST 拿全部逐步结果): ```bat rem 执行库中脚本 curl -s http://127.0.0.1:7777 -d "{"cmd":"link","op":"runscript","name":"TP1000H 读抽检"}" rem 执行内联场景 (scenario 里直接放上面格式的场景 JSON) curl -s http://127.0.0.1:7777 -d "{"cmd":"link","op":"runseq","scenario":{"steps":[{"send":"MC033"}],"waitMs":800}}" ``` HTTP 注意:应答同步等待(`waitMs` 最长 5 秒),客户端超时要设大于它;浏览器页面 `fetch` 受 CORS 限制(服务端不发跨域头),curl/Postman/后端代码不受影响。 `tools/devconsole.py` 是上述契约的 Python 示例消费者(交互终端 + 批处理,见 `python tools/devconsole.py --help`): ```bat python tools/devconsole.py rem 交互终端: open/ls/send/run/... python tools/devconsole.py --script "TP1000H 读抽检" python tools/devconsole.py --spec HTPRecorder --only MC033,MC035 python tools/devconsole.py --scenario tools/scenarios/tp1000h.json ``` ## MCP 接入(AI 控制) `tools/mcp_server.py` 把上述 RPC 暴露为 MCP 工具,AI 助手(Claude Desktop/Cursor 等)可直接控制 Logger01: 1. `pip install mcp` 2. 在 AI 客户端的 MCP 配置(如 `claude_desktop_config.json`)中加入: ```json { "mcpServers": { "logger01": { "command": "python", "args": ["F:/git/Hello/Logger01/tools/mcp_server.py"] } } } ``` 3. 重启 AI 客户端后即可对话控制:"启动采集" / "把采样速度调成 10ms" / "现在设备什么状态" 工具清单:`get_status` / `start_gather` / `stop_gather` / `apply_config(speed, channel_type)` / `query_devices` / `connect_device(addr)` / `disconnect_device(addr)` ### 只有 AI API 地址时(无 MCP 客户端) 用 `tools/ai_agent.py` 走 function calling 循环(OpenAI 兼容接口),填好 `API_URL/API_KEY/MODEL` 后: ``` python tools/ai_agent.py "启动采集并告诉我设备状态" ``` ## 对象结构 单线程事件循环(GUI 线程)承载全部对象,并发靠 Qt 事件驱动 + 每连接异步命令队列,无锁。 ``` QApplication ├─ Logger (单例) 日志双写: 界面文本框 + 按天分文件 │ └─ logView_ ────(引用)────▶ MainWindow::logView_ │ ├─ RpcServer (QTcpServer, 监听所有网卡:7777) │ ├─ 裸 TCP 行 JSON / HTTP POST(GET /命令) 双协议 │ ├─ 防火墙降级: 用户拒绝弹窗 → netsh 检测阻止规则 → 降级 127.0.0.1 │ └─ dispatch ──调用──▶ MainWindow 公有控制接口 │ deviceStatusList / startGatherAll / stopGatherAll / │ applyConfigAll / queryDevicesAll / connectDevice / disconnectDevice │ └─ MainWindow (QMainWindow) ├─ deviceModel_ (QStandardItemModel, 网格卡片数据源) │ └─ item×N: IpPortRole/SerialRole/CheckState/StatusText ── ClientRole ──▶ CommClient ├─ deviceList_ (QListView) ── DeviceCardDelegate (自绘卡片) ├─ clients: CommClient ×N (parent=MainWindow) │ ├─ TCP: tcpSocket_ (NoProxy) 串口: serialPort_ │ ├─ pendingCmds_ + cmdTimer_ 异步命令队列 (set ok 应答/超时推进, 不阻塞 UI) │ ├─ infoBuffer_/awaitingInfo_ 查询设备信息应答 │ ├─ streamBuffer_ 推流帧扫描 (DataFrame::tryExtract) │ └─ 静态 crc16()/withCrc() CRC16-MODBUS ├─ config_ (AppConfig) │ ├─ devices_: {addr, serial, checked} ×N ◀─▶ config.json │ ├─ commandTimeoutMs_ 操作区 SpinBox 即时应用 │ ├─ rpcPort_ RPC 端口 (0=禁用) │ └─ gather_ (GatherConfig): speed + channelType ├─ dataLogCount_: QHash<(连接,从机设备号), 收包数> └─ 信号连接 (每个 CommClient 一套, 同线程直连, 槽内 sender() 取来源) connected/disconnected/errorOccurred(msg,transient) ─▶ 卡片状态+按钮(勾选驱动) deviceInfoReceived ─▶ 从机概要日志 gatherDataReceived ─▶ 每包必记日志 外部 (可选): tools/mcp_server.py ──TCP JSON──▶ RpcServer AI MCP 工具桥 ``` 数据流(以串口设备为例): ``` 设备推流 ─▶ readyRead ─▶ onReadyRead ─┬─ cmdBusy_? ▶ cmdReplyBuffer_ (等 set ok) ▶ 队列推进 ├─ awaitingInfo_? ▶ infoBuffer_ ▶ DeviceInfo 解析 └─ 否则 ▶ streamBuffer_ ▶ DataFrame 解析 ▶ 日志/信号 ``` ## AI 助手(程序内) 右侧"AI 助手"面板:对话方式直接操作设备。AI 通过本机 RPC 执行工具(status/start/stop/apply_config/query_devices),调用过程在面板中透明可见(⚙ 行)。 配置 `config.json` 的 `ai` 节后重启程序: ```json { "ai": { "apiUrl": "https://你的API地址/v1/chat/completions", "apiKey": "你的KEY", "model": "模型名", "tools": [ ... ] // 工具表(function calling schema), 首次运行自动生成默认值, 可编辑 } } ``` 首次运行时若 config.json 不存在会自动生成包含全部默认项(含 5 个默认工具)的配置文件;`ai.tools` 即发送给 AI 的工具说明书,可自行增删改(改坏可删除该字段恢复默认)。 要求 OpenAI 兼容接口(chat/completions + tools)。面板可停靠/浮动/关闭,通过菜单"查看"可重新打开。 ## 日志字体 `config.json` 的 `log.fonts` 为字体回退链(按顺序取第一个系统可用的字体,类似 Chrome 的 font stack,全部缺失则用 Qt 默认): ```json { "log": { "fonts": ["Cascadia Mono", "Consolas", "Menlo", "SF Mono", "Ubuntu Mono", "Ubuntu Sans Mono", "Courier New", "Microsoft YaHei"] } } ``` 首次启动会自动写入默认值;中英混排建议最后一个放中文字体(如 Microsoft YaHei)。 ## 提交规范 供人工与自动化任务(AI 助手等)共同遵循;任务完成即提交,无需逐次请示: - **小步提交**:一个提交只做一个逻辑改动,按最小完整规模拆分(功能 A 与功能 B 分开提交;多任务并行改动同一批文件时,先还原不属于本任务的改动、单独提交本任务,再恢复提交另一个任务) - **提交信息**:一行式中文,格式 `模块: 摘要 — 关键细节`,细节写清动机、行为变化与验证方式(如"dev_static_fresh 与 qt6-static Release 均编译通过"),与 `git log` 既有风格一致 - **不提交**:`config.json`(exe 旁的运行时配置)、`instructions.json`(指令调试独立配置)、构建产物、与本任务无关的零散文件(如 `.zcodeignore`) - **提交前**:改完即编译验证(`cmake --build build/dev_static_fresh --config Release`),编译不过不提交 ## 注意 - Qt 静态链接为 LGPL 授权,对外分发闭源软件时需遵守相应义务;自用/内部工具无碍 - 运行依赖:静态版单 exe 即可;动态版需 Qt DLL 与 VC 运行库