From e9c7fda31409e7eec05a71e68920debc4f6d98ec Mon Sep 17 00:00:00 2001 From: suyu <1643689728@qq.com> Date: Fri, 14 Aug 2026 15:36:37 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=AE=8C=E5=96=84=E5=A4=9A=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E5=A4=9A=E9=80=BB=E8=BE=91=E5=B7=A5=E7=A8=8B=E8=AF=B4?= =?UTF-8?q?=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ai/handoff.md | 24 +++++++++++ docs/architecture.md | 25 ++++++++---- docs/工程格式说明.md | 84 ++++++++++++++++++++++++++++++++++++++ docs/开发顺序.md | 3 +- 4 files changed, 128 insertions(+), 8 deletions(-) create mode 100644 docs/工程格式说明.md diff --git a/docs/ai/handoff.md b/docs/ai/handoff.md index 5face85..e02eb43 100644 --- a/docs/ai/handoff.md +++ b/docs/ai/handoff.md @@ -24,6 +24,11 @@ - 自由监控支持 `M0~M4000`、`D0~D4000` 地址输入、连续批量添加、去重、删除、清空和最多 64 项限制 - 自由监控读取活动寄存器仓库,离线使用虚拟 M/D,真机使用 PLC 缓存,通信故障保留并标记最后有效值 - HMI 按钮支持 TouchWin 同类的置 ON、置 OFF、取反和瞬时 ON,默认瞬时 ON +- 工程支持多个有序 HMI 页面和多个有序控制逻辑,工程树可选择切换、新增、重命名、删除、启停和上下移动 +- 主窗口使用会话态当前页面 ID、当前逻辑 ID,所有编辑入口不再固定操作第一项 +- 工程持久化初始 HMI 页面;运行态由 `HmiNavigationService` 从初始页启动并处理本地 PageJump 导航 +- HMI 已开放不绑定寄存器的 `Label` 和独立 `PageJump` 创建入口,属性区按控件类型显示绑定、按钮操作或目标页面 +- 离线执行器按工程顺序扫描所有启用逻辑,禁用草稿不阻止运行;轨迹按逻辑 ID 隔离,运行工作台可切换当前投影 - HMI 运行按钮已区分悬停、按下、释放和禁止写入外观,运行态不再显示编辑选中框,可写按钮使用手型光标 - PLC 轮询集合已扩展为 HMI、梯形图和自由监控地址的并集,连接后可动态增删监控地址 - 已在真实 `COM3 / 9600 / 8E1 / 站号 1` 上完成只读联机验证,项目通信服务可完成 M/D 首次读取并进入真机运行前置状态 @@ -31,6 +36,8 @@ ## 关键决策 - 工程格式继续使用 `1.0`,当前文件只保存 HMI 和梯形图表达式树,不保留 `dataPoints` 结构及兼容代码 +- 当前 `1.0` 直接要求 `initialHmiPageId` 和 PageJump 的 `targetPageId`,开发阶段不为同版本旧草稿补默认值或迁移分支 +- 页面跳转保存目标页面稳定 ID,不保存名称或数组索引;编辑器当前页面和当前逻辑属于会话状态,不写入工程 - 按钮操作是工程 `1.0` 的必填字段,不兼容或迁移缺少该字段的旧按钮数据 - 不读取旧的 `stages/branches` 结构,不添加旧格式迁移和兼容层 - 梯形图编辑结构而不是自由线段,避免悬空线、环路和多个输出路径 @@ -60,6 +67,8 @@ `main_window_tests` 还覆盖 PLC 配置参数往返、独立配置弹窗入口,以及主窗口不再内嵌串口参数控件。 +本轮多页面和多逻辑实现增加了领域、服务、存储和主窗口集成覆盖:初始页约束、页面引用删除保护、页面/逻辑名称唯一性和顺序、禁用草稿逻辑、多逻辑扫描与轨迹隔离、严格 JSON `1.0` 必填字段、工程树选择切换、Label/PageJump 属性可见性、运行态页面跳转和逻辑轨迹选择。 + 2026-08-13 真机排查发现 `QModbusRtuSerialMaster::connectDevice()` 在当前 Windows 串口驱动上会同步触发 `ConnectedState`。旧实现随后再次写入 `Connecting`,导致已经发出的 读取响应因服务状态不是 `Connected` 而被丢弃,首次读取标志永远不能完成。现已把 @@ -103,6 +112,21 @@ PLC 断开状态现已与底层 Qt Modbus 会话保持一致。连接中断类 禁用“断开 PLC”但重连仍提示连接已经启动。串口无法打开时,输出区只通过 PLC 状态刷新记录 一次故障,连接操作不再重复追加同一错误,但仍保留警告对话框。 +PLC 未响应或通信超时且本地串口仍打开时,服务停止正常轮询,每次探测失败后等待 2 秒再发送 +一次单地址只读探测,不重复输出相同的超时日志。探测成功后进入 `Recovering` 状态,重新读取全部轮询地址; +完整首读完成后才恢复 `Connected`。故障和恢复过程始终停留在编辑态,不自动回到真机运行, +“断开 PLC”继续用于释放本地串口会话。 + +2026-08-14 使用真实 `COM3 / 9600 / 8E1 / 站号 1` 完成只读恢复测试。连续 5 次读取 +`M0~M2、D0` 的完整首读耗时为 `304 / 304 / 288 / 271 / 287 ms`,平均约 `291 ms`;串口 +进入已连接状态平均约 `51 ms`。真机运行时拔掉 PLC 侧 RS-485 A/B 后,服务进入 `Faulted`, +运行模式同步退回 `Editing`;接回线后约 `7.05 s` 进入 `Recovering`,再用约 `224 ms` 完成 +完整首读并恢复 `Connected`,最终仍保持 `Editing`。测试结束后再次首读耗时 `288 ms`,确认 +恢复探针已释放 COM3。底层 Qt SerialBus 单次复测中,连接状态约 `58 ms`,读取 `M0~M2` +到 `96 ms`,继续读取 `D0` 到 `128 ms`,单次读响应约 `32~38 ms`;生产服务完整首读的主要 +额外耗时来自 `200 ms` 正常轮询节拍。本次测试全程没有写入 PLC,读回值为 +`M0=0、M1=0、M2=0、D0=0`。 + 本轮新增自动化测试覆盖故障自动退回编辑态、首读资格撤销、故障后禁止重新进入真机、故障态 直接重连清理旧会话、主窗口重新配置入口和五类核心通信错误。`plc_runtime_tests`、 `runtime_mode_service_tests`、`main_window_tests` 均通过,Qt Release 工程完成干净构建。 diff --git a/docs/architecture.md b/docs/architecture.md index a36af36..5ca4cff 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -4,7 +4,7 @@ 项目采用 UI、领域、服务和基础设施分层。HMI 编辑、结构化梯形图、离线仿真和真机联机共用工程模型,UI 不直接依赖文件系统、串口或 Modbus 实现。 -当前目标是完成基础设备控制闭环,不生成、编译或下载 PLC 程序,不实现 XDPPro 的完整指令集,也不提供任意自由画线。 +当前目标是完成单工程、单 PLC、多 HMI 页面和多控制逻辑的基础设备控制闭环,不生成、编译或下载 PLC 程序,不实现 XDPPro 的完整 POU/指令集,也不提供任意自由画线。 ## 目录与依赖 @@ -51,7 +51,9 @@ main.cpp -> UI + Services + Infrastructure | `project_model.*` | 聚合 HMI 页面和控制逻辑并执行工程校验 | | `runtime_state.*` | 定义编辑、离线运行和真机运行状态机 | -工程文件格式保持 `1.0`。当前版本直接保存 HMI 和梯形图表达式树,不读取旧的 `stages/branches` 和 `dataPoints` 结构,也不包含旧格式迁移和兼容代码。加载必须先完成 JSON 解析、版本检查和领域校验,成功后才能替换当前工程;保存使用原子提交。 +工程文件格式保持 `1.0`。当前 `1.0` 直接保存有序 `hmiPages`、`initialHmiPageId`、有序 `controlLogics` 和梯形图表达式树,不读取其他结构,也不包含迁移和兼容代码。加载必须先完成 JSON 解析、版本检查和领域校验,成功后才能替换当前工程;保存使用原子提交。完整字段定义见 `docs/工程格式说明.md`。 + +`HmiPage.id` 和 `ControlLogic.id` 是稳定内部标识,名称可修改,数组顺序分别决定工程树页面顺序和离线逻辑扫描顺序。`MainWindow` 保存 `current_hmi_page_id_` 与 `current_logic_id_` 作为编辑会话选择,它们不进入 JSON。`Project.initialHmiPageId` 是运行态每次启动 HMI 时使用的持久化初始页,与编辑器当前正在查看的页面相互独立。 `Project::validate()` 允许未绑定控件、待配置节点和未完成网络作为编辑草稿保存。`Project::validateForRunning()` 额外拒绝未绑定 HMI、待配置节点、空条件和缺少输出的网络。 @@ -59,6 +61,8 @@ HMI 按钮操作作为工程 `1.0` 的强类型字段保存,支持 `置 ON`、 `瞬时 ON`。新建按钮默认使用瞬时 ON;按钮按下和释放事件由 UI 转交运行服务解释,UI 不直接决定写入值。当前版本不迁移缺少按钮操作字段的旧工程文件。 +`Label` 是不绑定 M/D 的固定文本控件。`PageJump` 是独立控件,使用强类型 `targetPageId` 引用目标页面稳定 ID,同样不绑定 M/D。编辑态点击 PageJump 只执行选择和拖动;离线或真机运行态点击后由 `HmiNavigationService` 在本地切换运行页面,不发出 PLC 写请求,因此 PLC 通信写权限不可用时也不影响页面导航。被 PageJump 引用的页面不能删除,初始页必须先切换后才能删除,工程至少保留一个页面。 + ## 结构化梯形图 网络条件采用递归表达式: @@ -90,9 +94,9 @@ Parallel ## 执行与运行反馈 -`SoftwareLogicExecutor` 按控制逻辑、网络顺序扫描,并递归求值 `Node / Series / Parallel`。普通线圈每周期写入网络结果,置位和复位线圈只在网络成立时写入;前面网络的写入对后面网络同一扫描周期立即可见。 +`SoftwareLogicExecutor` 按 `controlLogics` 数组顺序扫描所有已启用逻辑,再按网络顺序递归求值 `Node / Series / Parallel`。禁用逻辑仅保留结构校验,未完成的禁用草稿不会阻止离线运行。普通线圈每周期写入网络结果,置位和复位线圈只在网络成立时写入;前面逻辑或网络的写入对后面逻辑和网络在同一扫描周期立即可见。 -每次扫描生成 `LogicTraceSnapshot`,记录节点、表达式、网络和线圈的导通状态。逻辑画布在运行时以绿色显示导通节点和路径;执行错误携带逻辑、网络和节点上下文,画布选择并以红色突出故障位置。 +每次扫描生成按逻辑 ID 分区的 `LogicTraceSnapshot`,记录节点、表达式、网络和线圈的导通状态,避免不同逻辑中重复的 `rung-1`、`contact-1` 相互覆盖。运行工作台的逻辑下拉框只切换当前轨迹投影,所有已启用逻辑仍持续执行。逻辑画布以绿色显示导通节点和路径;执行错误携带逻辑、网络和节点上下文,画布选择并以红色突出故障位置。 `OfflineSimulationService` 以 50 ms 目标周期驱动扫描,启动时复制逻辑快照并清空虚拟 M/D。故障后停止扫描并保留最终寄存器值,HMI 可继续显示但禁止写入。该周期不提供硬实时保证。 @@ -116,7 +120,7 @@ HMI 控件和梯形图节点直接绑定 `M0~M4000`、`D0~D4000` 地址,不要 | 模式 | 同时显示 | 统一数据源 | | --- | --- | --- | -| 离线运行 | HMI、梯形图仿真轨迹、自由监控 | 虚拟 M/D | +| 离线运行 | 可导航 HMI、当前所选逻辑轨迹、自由监控 | 虚拟 M/D | | 真机运行 | HMI、自由监控 | PLC 缓存 | 真机模式不显示本地梯形图导通轨迹。当前项目不能读取 PLC 内部程序和网络执行轨迹,本地 @@ -138,11 +142,18 @@ PLC 连接后可以动态增删自由监控地址。存在读取请求时,新 运行资格;新增地址自身在首次读回前显示“等待读取”。如果连接首次读取尚未完成,新的完整 轮询集合会共同参与首次读取判定。 -HMI 写入只表示异步请求已受理,不乐观修改 PLC 缓存,实际值由后续读回确认。超时、协议错误或断线会停止轮询、保留最后一次有效值并进入故障状态,错误通过运行模式服务交给 UI 展示。 +HMI 写入只表示异步请求已受理,不乐观修改 PLC 缓存,实际值由后续读回确认。协议错误或断线会停止轮询、保留最后一次有效值并进入故障状态,错误通过运行模式服务交给 UI 展示。 +PLC 未响应或通信超时且本地串口仍然打开时,通信服务停止正常高频轮询,每次探测失败后等待 +2 秒,再只读探测一次当前轮询集合的首个地址。探测失败不重复打印同一超时日志;探测成功后进入恢复状态, +重新读取全部轮询地址并重建当前连接的首次读取资格,完整首读完成后才恢复为已连接状态。 通信故障会立即撤销当前连接的首次读取资格。若故障发生在真机运行态,运行模式服务自动返回 编辑态,PLC 最后一次有效缓存仍保留用于日志和故障诊断,但不能作为再次进入真机运行态的依据。 -重新进入真机运行必须重新连接 PLC,并在当前连接中重新完成首次读取。 +重新进入真机运行必须等待当前串口会话恢复或重新连接 PLC,并重新完成首次读取。 + +通信超时后的自动探测只恢复通信和缓存,不自动恢复真机运行。接线或 PLC 供电恢复后,应用 +完成新的首次读取并继续停留在编辑态,由用户手动决定是否重新进入真机运行。“断开 PLC” +在超时和恢复过程中保持可用,用于关闭并释放仍然打开的本地串口会话。 通信服务根据 Qt Modbus 错误和串口是否曾成功打开区分连接阶段失败与运行中连接中断。连接阶段 无法打开串口时直接保持未连接状态;已经连接后收到 `ConnectionError` 或意外进入 diff --git a/docs/工程格式说明.md b/docs/工程格式说明.md new file mode 100644 index 0000000..6a5b208 --- /dev/null +++ b/docs/工程格式说明.md @@ -0,0 +1,84 @@ +# 工程 JSON 1.0 格式说明 + +## 格式策略 + +当前开发阶段只有一个有效工程格式:`1.0`。代码直接读写本文描述的结构,不为早期开发草稿增加同版本默认值、字段推断、迁移分支或双写逻辑。缺少必填字段、字段类型错误、引用不存在或领域校验失败时,加载必须失败且不得替换当前工程。 + +## 顶层结构 + +```json +{ + "formatVersion": "1.0", + "id": "project-1", + "name": "包装线", + "hmiPages": [], + "initialHmiPageId": "", + "controlLogics": [] +} +``` + +所有顶层字段必填。空工程允许 `hmiPages` 和 `controlLogics` 为空,此时 `initialHmiPageId` 必须为空。存在页面时,`initialHmiPageId` 必须引用 `hmiPages` 中的页面 ID。 + +`hmiPages` 与 `controlLogics` 都是有序数组。页面顺序用于工程树显示;控制逻辑顺序是离线扫描顺序。对象 ID 稳定且唯一,名称可修改且在同类对象中唯一。 + +编辑器的当前页面 ID 和当前逻辑 ID 是主窗口会话状态,不进入工程 JSON。每次进入运行态时,HMI 从 `initialHmiPageId` 重新启动。 + +## HMI 页面与控件 + +```json +{ + "id": "page-1", + "name": "主操作页面", + "width": 800, + "height": 480, + "controls": [] +} +``` + +每个控件都要求 `id`、`type`、`bounds`、`text`、`binding` 和 `properties`。`bounds` 包含整数 `x`、`y`、`width`、`height`,矩形必须完整位于页面内。`binding` 为寄存器对象或 JSON `null`。 + +| `type` | 绑定 | 额外必填字段 | +| --- | --- | --- | +| `button` | M 地址或 `null` 草稿 | `buttonOperation` | +| `indicator` | M 地址或 `null` 草稿 | 无 | +| `numericDisplay` | D 地址或 `null` 草稿 | 无 | +| `numericInput` | D 地址或 `null` 草稿 | 无 | +| `label` | 必须为 `null` | 无 | +| `pageJump` | 必须为 `null` | `targetPageId` | + +按钮操作字符串为 `setOn`、`setOff`、`toggle` 或 `momentaryOn`。PageJump 使用目标页面稳定 ID,不使用名称或数组位置: + +```json +{ + "id": "page-jump-1", + "type": "pageJump", + "bounds": {"x": 20, "y": 20, "width": 120, "height": 40}, + "text": "参数设置", + "binding": null, + "properties": {}, + "targetPageId": "page-2" +} +``` + +编辑草稿允许 `targetPageId` 为空字符串,但进入运行态前必须配置为现存页面 ID。任何页面被 PageJump 引用时不得删除。 + +## 控制逻辑 + +```json +{ + "id": "logic-1", + "name": "主控制逻辑", + "enabled": true, + "rungs": [] +} +``` + +`enabled` 决定离线执行器是否扫描该逻辑。所有启用逻辑按 `controlLogics` 数组顺序执行;禁用逻辑仍要求 ID、名称、网络和表达式结构合法,但允许保留未完成节点作为草稿。 + +网络、`Node / Series / Parallel` 条件表达式、触点、比较和线圈字段沿用当前结构化梯形图模型。网格坐标、画布连线和运行轨迹是 UI 或运行会话投影,不进入 JSON。 + +## 校验与读写 + +保存顺序为领域校验、JSON 序列化、`QSaveFile` 原子提交。加载顺序为 JSON 语法、`formatVersion`、必填字段和类型、领域关系校验,全部成功后才替换当前工程。 + +工程保存允许未绑定的寄存器控件和未完成梯形图草稿。进入离线运行还必须通过运行校验:所有 HMI 寄存器控件完成绑定、所有 PageJump 目标有效、所有启用逻辑完整;禁用草稿逻辑不阻止运行。 diff --git a/docs/开发顺序.md b/docs/开发顺序.md index 7962059..78fd835 100644 --- a/docs/开发顺序.md +++ b/docs/开发顺序.md @@ -53,7 +53,7 @@ - 支持按钮、指示灯、数值显示和数值输入等基础控件。 - HMI 控件只绑定统一寄存器仓库,不直接访问仿真器或串口。 -完成标准:能够设计一个基础操作页面,并随工程保存和加载。 +完成标准:能够管理多个有序 HMI 页面,配置初始页、固定文本和页面跳转,并随工程保存和加载。 ## 7. 实现控制逻辑编辑器 @@ -63,6 +63,7 @@ - 定时器不属于当前原始需求范围,只有在后续需求明确时才新增对应配置和执行逻辑。 - 新增节点类型时使用独立配置类型,不向通用节点结构持续堆叠无关字段。 - 编辑器只生成逻辑模型,不直接修改 HMI 或 PLC。 +- 工程可包含多个有序控制逻辑,离线执行器按顺序扫描全部启用项,运行工作台只切换当前轨迹投影。 - 画布按表达式结构自动布线,显示节点或支路选择范围,不允许节点自由拖动,也不保存自由线段。 完成标准:能够表达 `A OR (B AND C)` 等嵌套逻辑,并配置启动、停止和状态保持逻辑。