diff --git a/AGENTS.md b/AGENTS.md index eea3278..bc507ee 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,58 +1,71 @@ # 项目长期约定 -- 项目作者:`suyu`。 +- 项目作者:`suyu` +- 文件、终端输出和源代码统一使用 UTF-8 + +## 新会话阅读顺序 + +1. 先读 `docs/ai/handoff.md`,确认当前状态、未提交改动、验证结果和下一步 +2. 涉及模块边界、数据流、运行模式或持久化时,再读 `docs/architecture.md` +3. 按任务读取下方索引中的业务文档;Markdown 没有答案时再查 `docs/pdf/` + +三个入口文档各自只负责一类信息: + +- `AGENTS.md`:长期有效的开发、构建和安全约束 +- `docs/architecture.md`:稳定的分层、模块边界、核心模型和数据流 +- `docs/ai/handoff.md`:当前进度、工作区状态、最近验证和下一步;更新时替换旧状态,不追加开发流水账 ## 关键文件索引 -- C++ 代码规范:`docs/C++代码规范.md`。 -- 需求参考文档:`docs/0_综合平台编程器_修改.md`。 -- 推荐开发顺序:`docs/开发顺序.md`。 -- Qt 应用源码根目录:`app/`。 -- 本机构建输出目录:`build/`(不纳入 Git)。 -- Qt 一键构建运行脚本:`scripts/build_and_run_qt.ps1`。 -- XDH-60T4-E 硬件与接线要点:`docs/XDH-60T4-E硬件与接线要点.md`。 -- XDH-60T4-E 指令与 Modbus 要点:`docs/XDH-60T4-E指令与Modbus要点.md`。 -- PLC 官方手册:`docs/pdf/`。 -- 当前开发进度和未完成事项:`docs/ai/handoff.md`(创建后每次新会话优先阅读;不要把动态进度写入本文件)。 -- 架构、模块边界和数据流:`docs/architecture.md`(创建后优先阅读)。 -- 查阅资料时,先读取 UTF-8 编码的 Markdown 文档;只有 Markdown 中找不到所需信息时,才查阅 `docs/pdf/` 内的 PDF 手册。 +- 当前交接:`docs/ai/handoff.md` +- 架构与数据流:`docs/architecture.md` +- C++ 规范:`docs/C++代码规范.md` +- 原始需求:`docs/0_综合平台编程器_修改.md` +- 数量边界最终规则:`docs/用户使用/数量边界确认方案.md` +- 工程 JSON 格式:`docs/工程格式说明.md` +- 开发阶段顺序:`docs/开发顺序.md`(阶段参考,当前状态以 handoff 为准) +- XDH-60T4-E 硬件接线:`docs/XDH-60T4-E硬件与接线要点.md` +- XDH-60T4-E 指令与 Modbus:`docs/XDH-60T4-E指令与Modbus要点.md` +- PLC 官方手册:`docs/pdf/` +- Qt 源码:`app/` +- 构建输出:`build/`(不纳入 Git) ## 技术栈与构建 -- 使用 C++、Qt 5.15.2 和 Qt Widgets;UI 默认使用 Qt Designer 的 `.ui` 文件。 -- 使用 qmake,不使用 CMake。 -- Qt MinGW 套件根目录:`D:\Qt5.15.2\5.15.2\mingw81_64`。 -- 构建前在 PowerShell 7 中设置 Qt 工具链路径;MinGW 工具目录为常见安装位置,如实际安装位置不同,以本机路径为准: - -```powershell -$env:QTDIR = 'D:\Qt5.15.2\5.15.2\mingw81_64' -$env:Path = "$env:QTDIR\bin;D:\Qt5.15.2\Tools\mingw810_64\bin;$env:Path" -qmake <项目文件>.pro -mingw32-make -j2 -``` - -- 优先在独立构建目录执行 qmake 和 `mingw32-make`,不要将生成物混入源代码目录。 -- 文件读写、终端查看和源代码统一使用 UTF-8。 -- 一键构建并运行 Debug 程序:`pwsh -NoLogo -NoProfile -File .\scripts\build_and_run_qt.ps1`。 -- 一键构建并运行 Release 程序:`pwsh -NoLogo -NoProfile -File .\scripts\build_and_run_qt.ps1 -Configuration Release`。 -- 打开 ui 文件:` & 'D:\Qt5.15.2\5.15.2\mingw81_64\bin\designer.exe' '.\app\src\ui\main_window.ui'` - -## Git 提交约定 - -- 每次提交只包含一个逻辑完整的改动;提交前必须完成对应的构建或测试验证。 -- 提交信息使用 `类型: 简短说明` 格式,例如 `feat: 建立 Qt 工程基线`。 -- 类型使用 `feat`、`fix`、`docs`、`test`、`refactor` 或 `chore`。 - -## 架构与运行模式 - -- 保持 UI、领域、服务和基础设施分层;业务逻辑不得堆入 `MainWindow` 或 Qt 槽函数。 -- HMI 只绑定 M/D 寄存器仓库,不得直接读写串口;串口通信必须异步,不能阻塞 UI。 -- M 区和 D 区的项目可用地址均为 `0~4000`;Modbus 代码使用从 `0` 开始的原始地址。 -- 离线模式使用虚拟 M/D,并运行软件逻辑执行器。 -- 真机模式使用 PLC 的真实 M/D,软件逻辑执行器必须停止;切换时先读取 PLC 数据,不自动写入离线虚拟值。 -- 真机联动设备时 PLC 必须处于 RUN;STOP 仅用于安全通信验证。 - -## 注意 -- 生成注释的文本结尾别加逗号 `。`。 -- 你写完代码需要测试真机PLC可以直接连 COM3,9600 8e1配置。 -- 离线仿真不会把PLC程序下载到真机PLC的,不做这个。 \ No newline at end of file +- 使用 C++17、Qt 5.15.2、Qt Widgets 和 qmake,不使用 CMake +- UI 默认使用 Qt Designer `.ui` 文件;不要把静态表单重新堆回 C++ +- Qt MinGW 套件:`D:\Qt5.15.2\5.15.2\mingw81_64` +- Windows 命令优先使用 PowerShell 7;显式调用使用 `pwsh -NoLogo -NoProfile -Command`,脚本使用 `pwsh -NoLogo -NoProfile -File` +- 复杂 PowerShell 逻辑写入 `.ps1` 后执行,避免嵌套引号;排查兼容问题时先确认 `$PSVersionTable.PSVersion` 和 `$PSVersionTable.PSEdition` +- 在独立构建目录运行 qmake 和 `mingw32-make`,不要把生成物写进 `app/` +- Debug 构建运行:`pwsh -NoLogo -NoProfile -File .\scripts\build_and_run_qt.ps1` +- Release 构建运行:`pwsh -NoLogo -NoProfile -File .\scripts\build_and_run_qt.ps1 -Configuration Release` +- 打开主界面:`& 'D:\Qt5.15.2\5.15.2\mingw81_64\bin\designer.exe' '.\app\src\ui\main_window.ui'` + +## 代码与架构约束 + +- 保持 `UI -> Services -> Domain` 依赖方向,基础设施实现服务契约;`domain` 不依赖 Qt、串口或文件系统 +- 业务规则放领域或服务层,不得堆入 `MainWindow`、Qt 槽函数或图元绘制代码 +- HMI 和运行界面只访问寄存器仓库,不直接访问串口;PLC 通信必须异步,不能阻塞 UI +- 编辑操作通过服务完成并保持原子性,失败时不得留下部分修改 +- 数量边界统一定义在 `app/src/domain/project_limits.h`,修改时同步更新数量边界文档和边界测试 +- M/D 项目地址范围固定为 `0~4000`;Modbus 使用从 `0` 开始的原始地址 +- 离线模式使用虚拟 M/D 并运行软件逻辑执行器 +- 真机模式使用 PLC 读回缓存并停止软件逻辑执行器;切换时先读 PLC,不复制离线值 +- 本项目不生成、编译或下载 PLC 程序,不得把本地梯形图轨迹当成 PLC 内部程序轨迹 +- 代码注释只解释不明显的约束,末尾不要加句号 `。` + +## 验证与安全 + +- 代码改动必须构建相关测试;共享模型、持久化、运行模式或 UI 工作流改动还要扩大回归范围 +- 提交前至少完成对应自动化测试、Release 构建和 `git diff --check` +- 涉及 PLC 通信或真机运行模式的代码,使用 `COM3 / 9600 / 8E1 / 站号 1` 做真实读写验证;先读取原值,测试后恢复并读回确认 +- STOP 用于安全通信验证;带真实设备的联动测试必须在确认接线和地址后切到 RUN +- 纯文档、领域校验或离线执行器改动不要求连接 PLC,但要在 handoff 中说明未做真机测试的原因 +- 离线仿真不会向 PLC 下载程序 + +## Git 提交 + +- 不覆盖或回退工作区中来源不明的改动,开始前先检查 `git status` 和相关 diff +- 每次提交只包含一个逻辑完整改动,提交前完成对应验证 +- 提交信息使用 `类型: 简短说明`,类型限 `feat`、`fix`、`docs`、`test`、`refactor`、`chore` diff --git a/docs/ai/handoff.md b/docs/ai/handoff.md index 0f3232c..78594e1 100644 --- a/docs/ai/handoff.md +++ b/docs/ai/handoff.md @@ -1,221 +1,54 @@ -# Current Handoff +# 当前开发交接 -- Goal: 完成综合平台编程器的结构化梯形图、HMI、离线仿真和 Modbus RTU 真机链路 -- Branch: `main` -- Current status: 第 1~10 步的软件实现与自动化测试已完成,待连接 XDH-60T4-E 进行真机安全验证和整理最终交付资料 +> 更新日期:2026-08-19。本文件只保留当前状态和下一步;完成事项合并成结论,不追加按日期排列的开发日志。 -## 当前能力 +## 当前状态 -- 梯形图条件使用递归 `Node / Wire / Series / Parallel` 表达式树,可表示 `A OR (B AND C)`、嵌套串并联和无地址横线 -- 编辑器使用固定行列网格和正交连线,支持当前节点或横线后串联、单节点并联、连续范围横线旁路、删除节点或网络以及设置输出线圈 -- 触点、横线和竖线连接段都可以选择;横线可由触点原位替换,竖线映射到对应并联支路,内部表达式容器不作为通用选择对象暴露 -- `F11/F12` 分别插入横线和建立连续范围旁路,`Shift+F11/Shift+F12` 分别删除所选横线和竖线支路 -- 执行器递归求值并生成节点、表达式、网络轨迹,离线运行时画布显示导通路径和故障位置 -- HMI 和梯形图直接配置 M/D 地址,不依赖预先创建的数据点 -- HMI 控件描述注册表统一维护现有 8 种控件的稳定名称、默认值、绑定规则和运行值类型 -- HMI ProgressBar 使用 D 区 Word 值和可配置范围显示百分比,支持越界钳制、JSON 往返和运行态只读展示 -- `ActiveRegisterRepository` 在离线虚拟仓库与 PLC 缓存仓库之间切换 -- `PlcCommunicationService` 使用 Qt SerialBus 异步连接、轮询和单点写入,UI 不阻塞 -- 真机连接收集 HMI、报警定义和梯形图实际引用地址,按 M/D 分区并合并相邻地址读取 -- 真机模式要求首次读取完成,不复制离线值,不运行 PC 软件逻辑执行器 -- 通信错误停止轮询并保留最后一次有效缓存,状态栏和日志显示错误 -- 通信故障立即撤销当前连接的首读资格;真机运行中发生故障会自动返回编辑态,重新进入真机前必须重新连接并完成新一轮首读 -- PLC 故障态可直接“重新配置”,连接前自动清理旧串口会话,不再要求用户先手动点击断开 -- PLC 日志区分连接阶段串口无法打开与已连接后的本地串口意外中断;后者自动转为已断开并禁用重复断开操作 -- 主窗口静态控件已迁入 `main_window.ui`,PLC 参数由独立配置对话框收集后再发起连接 -- 主窗口界面协调已拆为 `ProjectWorkspaceController`、`PropertyPanelController` 和 - `RuntimePanelController`,分别负责工程树与会话选择、属性编辑、运行监控与仿真反馈; - `MainWindow` 保留窗口级动作、工程文件操作和跨区域模式切换 -- 已增加运行监控工作台:离线仿真同时显示 HMI、梯形图轨迹和自由监控,真机运行只显示 HMI 和自由监控 -- 自由监控支持 `M0~M4000`、`D0~D4000` 地址输入、连续批量添加、去重、删除、清空和最多 64 项限制 -- 自由监控读取活动寄存器仓库,离线使用虚拟 M/D,真机使用 PLC 缓存,通信故障保留并标记最后有效值 -- HMI 按钮支持 TouchWin 同类的置 ON、置 OFF、取反和瞬时 ON,默认瞬时 ON -- 工程支持多个有序 HMI 页面和多个有序控制逻辑,工程树可选择切换、新增、重命名、删除、启停和上下移动 -- 主窗口使用会话态当前页面 ID、当前逻辑 ID,所有编辑入口不再固定操作第一项 -- 工程持久化初始 HMI 页面;运行态由 `HmiNavigationService` 从初始页启动并处理本地 PageJump 导航 -- HMI 已开放不绑定寄存器的 `Label` 和独立 `PageJump` 创建入口,属性区按控件类型显示绑定、按钮操作或目标页面 -- HMI 已增加 AlarmList 和报警配置入口,支持 M ON、D 高限、D 低限,统一显示当前报警和确认状态;运行态无报警时隐藏,报警恢复后立即移除 -- 离线执行器按工程顺序扫描所有启用逻辑,禁用草稿不阻止运行;轨迹按逻辑 ID 隔离,运行工作台可切换当前投影 -- 梯形图已支持上升沿、下降沿、T/C 常开/常闭触点、非保持 TON、CTU/CTD、MOVE 和 ADD/SUB;边沿与计数都按上升沿扫描语义执行,TON 按 `steady_clock` 计算实际 ET -- T/C 是 `T0~T4000`、`C0~C4000` 的离线仿真内部资源;C 计数器的 CV 保存到 D 地址,PV 支持常量或 D 地址,复位使用 M 地址;T/C 资源本身不读写 M/D,也不进入 Modbus 轮询,同一资源只允许一个已启用驱动 -- ADD/SUB 使用 `int32_t` 中间值并将结果饱和到 `-32768~32767`;越界只在运行轨迹的 `WordTraceValue::overflow` 标记,不停止扫描 -- 工程已支持 M/D 地址注释和 `LadderRung` 网络注释;地址注释只保存地址与文本,不混入当前值和引用状态 -- HMI 运行按钮已区分悬停、按下、释放和禁止写入外观,运行态不再显示编辑选中框,可写按钮使用手型光标 -- PLC 轮询集合已扩展为 HMI、报警定义、梯形图和自由监控地址的并集,连接后可动态增删监控地址 -- 已在真实 `COM3 / 9600 / 8E1 / 站号 1` 上完成只读联机验证,项目通信服务可完成 M/D 首次读取并进入真机运行前置状态 +- 分支:`main` +- 核心软件闭环已经可用:HMI 编辑、结构化梯形图、离线仿真、工程 JSON、自由监控和 Modbus RTU 真机读写均已实现 +- 当前工作区有未提交改动,主要是全项目数量边界、梯形图最大 11 列、无条件输出语义及配套测试和文档;开始工作前先看 `git status` 和相关 diff,不要重置 +- `docs/二次开发/` 是已有未跟踪资料,不能因当前任务删除 +- 当前没有代码阻塞;下一里程碑是实际设备 RUN 联动和交付整理 -## 关键决策 +## 已实现能力 -- 工程格式继续使用 `1.0`,当前文件保存 HMI、报警定义和梯形图表达式树,不保留 `dataPoints` 结构及兼容代码 -- 工程 `1.0` 直接要求 `alarmDefinitions`,不为缺少该字段的旧草稿增加迁移或默认值 -- AlarmList 不直接保存报警逻辑,项目级 `AlarmService` 统一管理运行状态;当前不设置提示、警告、故障等级 -- 当前 `1.0` 直接要求 `initialHmiPageId` 和 PageJump 的 `targetPageId`,开发阶段不为同版本旧草稿补默认值或迁移分支 -- 页面跳转保存目标页面稳定 ID,不保存名称或数组索引;编辑器当前页面和当前逻辑属于会话状态,不写入工程 -- 按钮操作是工程 `1.0` 的必填字段,不兼容或迁移缺少该字段的旧按钮数据 -- 不读取旧的 `stages/branches` 结构,不添加旧格式迁移和兼容层 -- 梯形图编辑结构而不是自由线段,避免悬空线、环路和多个输出路径 -- 横线是保存 `columnSpan` 的恒真 `Wire` 叶子,竖线由 `Parallel` 边界派生,不保存竖线或像素坐标 -- 网格仅作为编辑投影,不进入工程格式;服务层把连续范围操作原子转换为规范化表达式树 -- 条件节点继续使用 `std::variant` 的独立配置类型,不向通用节点添加无关字段 -- HMI、梯形图和通信共享 `M0~M4000`、`D0~D4000` 地址范围 -- 已删除统一数据点表、主窗口数据点 Dock 和属性区数据点下拉框;M/D 注释作为独立的工程级地址元数据实现 -- PLC 读写使用从 `0` 开始的原始 Modbus 地址 -- HMI 写请求不乐观修改 PLC 缓存,由下一次轮询确认真实值 -- 真机运行时 PLC 内部程序是控制源,软件逻辑执行器保持停止 -- 真机运行不显示本地梯形图导通轨迹,避免把本地工程误表示为 PLC 内部程序的真实执行状态 -- 自由监控表属于当前调试会话,不进入工程 `1.0` JSON,不重新引入数据点表 -- 工程 `1.0` 直接要求顶层 `registerComments` 和每个网络的 `comment` 字段,不增加旧工程迁移或缺省兼容 -- 50 ms Qt 定时器只调度离线扫描;每个 TON 不创建独立 Qt 定时器,真机模式继续停止整个软件逻辑执行器 -- 项目梯形图不下载到 PLC;真机模式停止本地软件逻辑执行器,只轮询新指令实际引用的 M/D 地址 -- `Wire` 不绑定寄存器且不进入 M/D 轮询;离线轨迹使用上游输入电流决定横线是否显示导通 +- 工程格式为严格的 `1.0`,保存多 HMI 页面、初始页、报警定义、M/D 注释、多控制逻辑和结构化梯形图;不读取旧 `dataPoints` 或 `stages/branches` 格式 +- HMI 现有 8 类控件:Button、Indicator、NumericDisplay、NumericInput、Label、PageJump、AlarmList、ProgressBar +- 梯形图使用 `Node / Wire / Series / Parallel` 表达式树和正交网格,不保存自由线段;前 10 列为条件区,输出最多位于第 11 列 +- 梯形图支持 M 触点与上下沿、T/C 触点、普通/置位/复位线圈、TON、CTU/CTD、MOVE、ADD/SUB +- 只有输出、没有条件的网络按恒真执行;只有条件、没有输出的启用网络不能运行;空网络扫描时跳过 +- 离线执行器按控制逻辑和网络顺序扫描,使用虚拟 M/D,并生成按逻辑 ID 隔离的运行轨迹 +- 真机模式只使用 PLC 读回缓存,停止本地逻辑执行器,不下载本地梯形图,也不显示伪造的 PLC 梯形图轨迹 +- 自由监控属于当前会话,支持 M/D 批量添加、去重、读取和单点写入,不写入工程 JSON +- PLC 通信使用异步 Qt Modbus RTU;轮询地址来自 HMI、报警、梯形图和自由监控的引用并集,写入后等待轮询确认,不乐观修改缓存 +- 真机进入要求当前连接完成首次读取;通信故障撤销首读资格并退出真机运行,恢复通信后仍停留在编辑态 +- 所有数量上限和超限处理见 `docs/用户使用/数量边界确认方案.md`,核心常量集中在 `app/src/domain/project_limits.h` -## 自动化验证 +## 当前未提交改动 -以下测试均已完成干净构建并通过: +- 对页面、控件、报警、逻辑、表达式、字符串、JSON、通信参数、轮询规模、日志和撤销历史补齐统一数量限制 +- 单网络条件区从原 256 列收紧为 10 列,输出最多占第 11 列,横线跨度最多 10 列,表达式深度最多 20 层 +- 编辑服务在第 11 个条件加入时原子回退;JSON 加载和领域校验不能绕过边界 +- 无条件输出已经贯通领域校验、编辑服务、JSON 往返、离线执行器、轨迹和画布提示 +- `AGENTS.md`、本 handoff 和 `docs/architecture.md` 已按长期规则、动态状态和稳定架构重新分工精简 -- `domain_tests` -- `logic_editor_service_tests` -- `offline_simulation_service_tests` -- `project_management_tests` -- `hmi_editor_service_tests` -- `runtime_mode_service_tests` -- `plc_runtime_tests` -- `main_window_tests` -- `register_monitor_service_tests` -- `alarm_service_tests` +## 最近验证 -`main_window_tests` 还覆盖 PLC 配置参数往返、独立配置弹窗入口,以及主窗口不再内嵌串口参数控件。 +- 当前数量边界和无条件输出改动已通过:`domain_tests`、`logic_editor_service_tests`、`offline_simulation_service_tests`、`project_management_tests`、`main_window_tests` +- Qt Release 主程序已重新构建并链接成功 +- `git diff --check` 通过;`runtime_mode_service_tests.cpp` 仅有既存 CRLF 转 LF 提示 +- 真实 PLC 已使用 `COM3 / 9600 / 8E1 / 站号 1` 验证首次读取、断线恢复和重新首读 +- 2026-08-19 已读取 `D4000=0`,写入并读回 `1`,随后恢复并读回 `0`;没有下载 PLC 程序 +- 当前无条件输出改动只影响领域校验和离线执行器,真机模式不执行该逻辑,因此没有为此重复写 PLC -本轮边沿、TON 和注释实现增加了:上下沿一次扫描脉冲、不同逻辑同名边沿状态隔离、TON 到时与断开复位、多个 T 独立计时、网络顺序可见性、运行重启状态清理、T 引用完整性、M/D 与网络注释 JSON 往返、严格新字段缺失失败,以及 T 和纯注释地址不进入 PLC 轮询集合的覆盖。 +## 待完成 -本轮多页面和多逻辑实现增加了领域、服务、存储和主窗口集成覆盖:初始页约束、页面引用删除保护、页面/逻辑名称唯一性和顺序、禁用草稿逻辑、多逻辑扫描与轨迹隔离、严格 JSON `1.0` 必填字段、工程树选择切换、Label/PageJump 属性可见性、运行态页面跳转和逻辑轨迹选择。 +1. 在确认接线、地址和负载安全后,将 PLC 切到 RUN,完成 HMI 与实际设备的完整联动测试 +2. 整理最终演示工程、构建说明和验收记录 +3. 复核当前未提交 diff,并按逻辑拆分提交;不要把 `docs/二次开发/` 等来源不明改动混入提交 -2026-08-13 真机排查发现 `QModbusRtuSerialMaster::connectDevice()` 在当前 Windows -串口驱动上会同步触发 `ConnectedState`。旧实现随后再次写入 `Connecting`,导致已经发出的 -读取响应因服务状态不是 `Connected` 而被丢弃,首次读取标志永远不能完成。现已把 -`Connecting` 状态设置移动到 Qt 连接调用之前,并在真实 PLC 上验证首轮 `M0~M2`、`D0` -读取约 288 ms 完成,缓存值为 `M0=1`、`M1=1`、`M2=0`、`D0=0`。主窗口现在会显示 -连接、首读完成和具体中文通信故障,生产代码中的业务错误提示已统一为中文,JSON 字段和协议标记保持不变。 +## 下一会话起点 -PLC 连接初始化会连续通知首读重置、正在连接和已连接。旧主窗口把每次通知都排队处理, -队列执行时读取到的已经是同一个最终状态,因此输出区会连续追加三条相同的“正在首读”。 -现已在主窗口合并同一事件循环内的 PLC 状态刷新,并拒绝连续追加完全相同的输出消息。 -真实 `COM3` 主窗口联调确认“正在首读”和“首次读取完成”各输出一次。 - -本轮已完整删除统一数据点业务,包括领域模型、服务、主窗口 Dock、属性区下拉框、JSON 字段和独立测试工程。当前开发阶段不保留 `dataPoints` 旧结构的迁移或兼容逻辑。主窗口测试明确验证已删除控件不存在,PLC 轮询测试改为验证从 HMI 实际绑定收集地址。 - -本轮梯形图编辑重构还验证了:固定网格渲染、水平/垂直正交布线、工具栏直接建立并联支路,以及连续范围 `A AND ((B AND C) OR D)` 的结构变换和非连续范围拒绝。 - -本轮信捷风格布线增加了结构化横线和可选竖线连接段。自动化覆盖横线跨度边界、JSON 往返、连续范围旁路和非连续范围拒绝、触点原位替换横线、删除后的表达式归一化与撤销,以及 `F11/F12/Shift+F11/Shift+F12` 主窗口交互。横线逻辑值恒真,但只有上游输入导通时才显示运行电流;竖线仍由 `Parallel` 派生,不引入自由线段或 PLC 地址。 - -本轮自由监控与运行工作台实现还验证了:地址字符串解析和边界、连续范围展开、去重和数量 -上限、活动仓库切换、离线工作台三区域同时可见、真机工作台只显示 HMI 与自由监控、离线 -监控读取虚拟 M/D,以及真机运行期间新增地址立即进入 PLC 聚合轮询集合。PLC 动态轮询在 -读取请求空闲后应用新集合,连接首读完成后不会因新增监控地址而回退。 - -运行工作台的 HMI 和梯形图是独立于编辑画布的模型投影。此前同一页面或逻辑 ID 在编辑态 -发生变化后,运行投影的 `setPageId()` / `setLogicId()` 会因 ID 未变化而跳过重建,导致新建 -HMI 控件在离线运行工作台中显示为空白。现在每次同步运行对象都会显式重建两个投影,主窗口 -测试会保留一个已配置 HMI 控件,并断言离线运行视图实际包含该控件,而不再只检查区域可见。 - -Qt Release 应用已完成干净构建,并执行了启动与正常退出冒烟验证。`git diff --check` 无空白错误。 - -本轮修正 PLC 故障恢复状态链。通信服务发生错误时会清除当前连接的首次读取资格,运行模式 -服务同时校验 PLC 必须处于 `Connected` 且当前连接已完成首读;真机运行期间发生故障会自动 -返回编辑态,避免使用旧连接的首读结果重新进入真机运行。故障状态下主窗口开放“PLC 重新 -配置”,用户确认后自动断开并清理旧会话,再发起新连接。旧连接未完成的异步回复使用连接代次 -隔离,不能污染重连后的新会话。 - -PLC 错误分类已拆到独立基础设施模块。连接阶段无法打开串口时直接报告端口不存在或被占用, -保持未连接状态;已连接后收到 `ConnectionError` 或意外进入 `UnconnectedState` 时统一报告本地 -串口连接已中断,自动清理并转为未连接状态,不再依赖错误瞬间的 Windows COM 口枚举结果。 - -PLC 断开状态现已与底层 Qt Modbus 会话保持一致。连接中断类错误会停止轮询、隔离旧回复并 -主动关闭 `QModbusRtuSerialMaster`;再次连接前还会防御性清理残留的底层连接状态,避免界面已 -禁用“断开 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 工程完成干净构建。 - -本轮参照 TouchWin 按钮业务新增四种位操作:置 ON、置 OFF、取反、瞬时 ON。按钮操作 -使用领域枚举并写入工程 `1.0` JSON;运行服务按 Pressed/Released 事件生成写值,瞬时 ON -按下写 1、释放写 0,其他模式释放不重复写入。属性面板只在选中按钮时显示操作模式,新建 -按钮默认瞬时 ON。相关服务、离线闭环、JSON 往返与主窗口测试已覆盖。 - -HMI 图元现已分离编辑选择与运行输入状态。运行按钮悬停时改变底色,按下时按钮面下移并 -加深,释放后回弹;禁止写入时显示灰色且不接收鼠标写操作。运行态关闭图元选择,避免点击 -后出现编辑选中框。主窗口测试使用真实鼠标按下和释放事件验证瞬时按钮写入、视觉状态切换、 -手型光标、禁止写入和无运行选中框。 - -本轮新增最小报警闭环。项目级报警定义支持 M 位为 ON、D 值大于等于阈值、D 值小于等于 -阈值;AlarmService 只维护当前报警的发生时间和确认状态,报警恢复后立即移除,多个 AlarmList 共享状态。 -报警配置和列表展示分别由独立 Qt Designer 对话框与 HMI 图元完成,主窗口只协调服务。报警 -地址已进入 PLC 聚合轮询。工程版本保持 `1.0`,`alarmDefinitions` 为必填字段,不增加兼容分支。 - -2026-08-17 使用 `COM3 / 9600 / 8E1 / 站号 1` 完成报警功能后的真机只读冒烟,成功读取 -`M0~M2 = OFF`、`D0 = 0`,测试结束后正常关闭串口。本次没有执行任何 PLC 写操作。 - -本轮新增 CTU/CTD、MOVE、ADD/SUB 离线逻辑和编辑配置。领域校验限制 C/T 资源及 M/D 地址 -范围,C 计数器要求唯一驱动和已绑定 C 触点;计数按上升沿执行,CV 通过 D 地址读写,CTU -复位为 0、CTD 复位装载 PV,ADD/SUB 越界采用有符号 16 位饱和并记录溢出轨迹。JSON 保存 -加载、离线执行、主窗口入口和真机轮询地址覆盖已通过,项目 Release 构建通过。真机仍只验证 -M/D 通信,不执行或下载这些本地梯形图指令。 - -本轮在不修改领域、服务和通信规则的前提下拆分主窗口三个界面控制器。`main_window.cpp` 从 -约 2200 行降至约 1300 行,原工程树、属性面板和运行刷新实现已从主窗口移除,没有保留重复 -死代码。Qt Release 生产工程和 `main_window_tests` 均使用独立构建目录重新构建并通过, -`git diff --check` 无空白错误。 - -2026-08-17 使用 `COM3 / 9600 / 8E1 / 站号 1` 完成本轮重构后的真机只读复核,成功读取 -`M0~M2 = OFF`、`D0 = 0`,测试结束后正常关闭串口,全程没有执行 PLC 写操作。 - -本轮建立无 Qt 依赖的 HMI 控件描述注册表,将 Button、Indicator、NumericDisplay、 -NumericInput、Label、PageJump 和 AlarmList 的工程稳定类型名、界面名称、默认 ID 前缀、 -默认文字、默认尺寸、绑定规则和运行值类型集中维护。领域校验、编辑创建、运行读取、JSON -编解码和属性面板已改为查询注册表,原有重复类型辅助函数和分支表已删除;专用绘制与交互 -行为保持原模块边界。注册表完整性测试覆盖枚举和稳定名称双向查询、名称唯一性、默认尺寸、 -M/D 绑定与运行值匹配及未知类型拒绝。10 个自动化测试工程全部重新构建并通过,Qt Release -主工程完成独立构建,`git diff --check` 无空白错误。 - -本轮新增 HMI ProgressBar 控件。进度条使用 D 区 Word 绑定,保存最小值、最大值和是否显示百分比, -运行态将读回值钳制到配置范围后绘制水平填充,不接收鼠标写入。属性面板支持范围编辑,JSON -严格要求 `minimumValue`、`maximumValue` 和 `showValue`,领域测试覆盖无效范围、越界映射和 -M/D 绑定边界;主窗口测试覆盖创建、属性编辑、运行绘制和只读交互。 - -此前的 7 种控件已升级为 8 种控件,ProgressBar 的稳定类型名为 `progressBar`,默认范围为 -`0~100`,默认尺寸为 `240x36`。 - -2026-08-17 使用 `COM3 / 9600 / 8E1 / 站号 1` 完成本轮注册表重构后的真机只读复核, -成功读取 `M0~M2 = OFF`、`D0 = 0`,测试结束后正常关闭串口,全程没有执行 PLC 写操作。 - -2026-08-18 完成本轮信捷风格横线/竖线功能后的生产通信服务只读回归。`COM3 / 9600 / 8E1 / 站号 1` 在约 288 ms 完成 `M0~M2` 和 `D0` 首次读取,结果为 `M0=0`、`M1=0`、`M2=0`、`D0=0`;真机模式前置状态切换成功,并在约 407 ms 正常断开。探针只调用读取、模式切换和断开接口,没有执行 PLC 写入或程序下载。Qt Release 主程序也已完成最新构建、窗口启动和退出码 0 的正常关闭冒烟。 - -## 待完成事项 - -1. 在 PLC STOP 状态确认 COM2 参数和 RS-485 A/B 接线 -2. 用安全测试地址读取 `M0`、`D0` -3. 写入 `M0`、`D0` 并由 PLC 监控和下一次轮询确认 -4. 真机复核电脑侧 USB 转串口拔除、PLC 侧 A/B 断开、超时和重新连接日志及恢复流程 -5. PLC 切换 RUN 后执行完整 HMI 与设备联动测试 -6. 整理测试记录、构建说明和演示工程 - -## 建议下一步 - -运行 Release 应用,配置实际 COM 口、站号、波特率和校验方式,在 PLC STOP 状态完成 `M0`、`D0` 首次安全读写。未确认设备程序与负载安全前不要在 RUN 状态写控制地址。 +1. 运行 `git status --short`,确认当前未提交和未跟踪文件仍在 +2. 根据任务读取 `docs/architecture.md`、数量边界文档或硬件/Modbus 要点,不再从旧开发日志推断现状 +3. 修改代码后运行受影响测试和 Release 构建;涉及真机通信时按 `AGENTS.md` 做 COM3 读写及恢复验证 diff --git a/docs/architecture.md b/docs/architecture.md index 56168be..a78db95 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,26 +1,23 @@ # 综合平台编程器架构 -## 目标与边界 +## 目标与范围 -项目采用 UI、领域、服务和基础设施分层。HMI 编辑、结构化梯形图、离线仿真和真机联机共用工程模型,UI 不直接依赖文件系统、串口或 Modbus 实现。 +项目是单工程、单 PLC、多 HMI 页面和多控制逻辑的 Qt 桌面编程器,提供 HMI 编辑、结构化梯形图、离线仿真和 Modbus RTU 真机联机。 -当前目标是完成单工程、单 PLC、多 HMI 页面和多控制逻辑的基础设备控制闭环,不生成、编译或下载 PLC 程序,不实现 XDPPro 的完整 POU/指令集,也不提供任意自由画线。 +本项目不生成、编译或下载 PLC 程序,不读取 PLC 内部程序与网络轨迹,不实现完整 XDPPro 指令集,也不支持任意自由画线。真机运行时 PLC 内部程序是唯一控制源,PC 只读写工程实际引用的 M/D 地址。 -## 目录与依赖 +## 分层与依赖 ```text -app/ -├── integrated_platform.pro qmake 工程入口 -├── src/ -│ ├── main.cpp 对象创建与依赖注入 -│ ├── ui/ Qt Widgets 视图和界面控制器 -│ ├── domain/ 工程、HMI、梯形图和寄存器模型 -│ ├── services/ 编辑、仿真、运行模式和通信契约 -│ └── infrastructure/ JSON、PLC 缓存和 Qt Modbus RTU 实现 -└── tests/ 单元测试与集成测试工程 +app/src/ +├── domain/ 无 Qt 依赖的模型、值对象、校验和仓库接口 +├── services/ 编辑、工程、仿真、运行模式和通信契约 +├── infrastructure/ JSON、PLC 缓存、Qt Modbus RTU 实现 +├── ui/ Qt Widgets 视图、对话框和界面控制器 +└── main.cpp 对象创建与依赖注入 ``` -生产代码保持以下依赖方向: +生产代码依赖方向固定为: ```text UI -> Services -> Domain @@ -28,183 +25,120 @@ Infrastructure -> Services contracts + Domain main.cpp -> UI + Services + Infrastructure ``` -- `domain` 不依赖 Qt、串口和文件系统 -- `services` 编排用例,通过接口使用工程存储、寄存器和 PLC 通信 -- `infrastructure` 实现 JSON 工程文件、PLC 缓存和异步 Modbus RTU -- `ui` 只提交操作并展示模型、运行轨迹和错误状态 -- `main.cpp` 负责组合依赖,不承载业务规则 +- `domain` 不依赖 Qt、文件系统或串口 +- `services` 执行业务用例和原子编辑,通过接口使用工程存储与 PLC 通信 +- `infrastructure` 实现 JSON 工程存储、PLC 缓存和异步 Modbus RTU +- `ui` 只提交命令并投影模型、轨迹和错误,不直接读写串口或承载业务校验 +- `main.cpp` 只组合对象,不实现业务规则 -主窗口的菜单、工具栏和属性表单由 Qt Designer 的 -`main_window.ui` 定义。界面交互按职责由三个 UI 控制器协调: +主窗口静态布局由 `main_window.ui` 管理。UI 协调分为: -- `ProjectWorkspaceController` 负责工程树、页面/逻辑会话选择和工程对象刷新 -- `PropertyPanelController` 负责 HMI/梯形图选择状态、属性面板和编辑器信号 -- `RuntimePanelController` 负责运行监控工作台、运行值刷新和离线执行器反馈 - -`MainWindow` 负责组合这些控制器、处理窗口级菜单和跨区域模式切换,不重复实现工程校验或寄存器业务规则。真实 PLC 的串口与轮询参数由独立的 `PlcConnectionDialog` 收集。 - -## 核心领域模型 - -| 文件 | 职责 | +| 组件 | 职责 | | --- | --- | -| `register_address.*` | 定义 `M0~M4000`、`D0~D4000` 地址和值域规则 | -| `register_repository.*` | 定义 M/D 统一读写接口和离线内存实现 | -| `active_register_repository.*` | 在虚拟仓库与 PLC 缓存仓库之间切换当前数据源 | -| `register_monitor_model.*` | 保存当前调试会话的自由监控 M/D 地址集合 | -| `alarm_model.*` | 定义项目级 M ON、D 高限和 D 低限报警条件 | -| `hmi_model.*` | 定义页面、控件、布局和寄存器绑定 | -| `hmi_control_registry.*` | 集中描述 HMI 控件的稳定名称、默认值、绑定规则和运行值类型 | -| `control_logic_model.*` | 定义梯形图表达式树、边沿/触点/比较/TON 节点和输出线圈 | -| `project_model.*` | 聚合 HMI 页面、报警定义和控制逻辑并执行工程校验 | -| `runtime_state.*` | 定义编辑、离线运行和真机运行状态机 | - -工程文件格式保持 `1.0`。当前 `1.0` 直接保存有序 `hmiPages`、`initialHmiPageId`、有序 `alarmDefinitions`、工程级 `registerComments`、有序 `controlLogics` 和梯形图表达式树,不读取其他结构,也不包含迁移和兼容代码。加载必须先完成 JSON 解析、版本检查和领域校验,成功后才能替换当前工程;保存使用原子提交。完整字段定义见 `docs/工程格式说明.md`。 +| `ProjectWorkspaceController` | 工程树、当前页面/逻辑会话选择和编辑对象刷新 | +| `PropertyPanelController` | HMI/梯形图选择、属性表单和编辑服务调用 | +| `RuntimePanelController` | 运行工作台、运行值刷新、仿真轨迹和故障反馈 | +| `MainWindow` | 窗口级动作、工程文件操作、控制器组合和模式切换 | -`HmiPage.id` 和 `ControlLogic.id` 是稳定内部标识,名称可修改,数组顺序分别决定工程树页面顺序和离线逻辑扫描顺序。`MainWindow` 保存 `current_hmi_page_id_` 与 `current_logic_id_` 作为编辑会话选择,`ProjectWorkspaceController` 负责将它们同步到工程树和编辑画布,它们不进入 JSON。`Project.initialHmiPageId` 是运行态每次启动 HMI 时使用的持久化初始页,与编辑器当前正在查看的页面相互独立。 +## 工程模型与持久化 -`Project::validate()` 允许未绑定控件、待配置节点和未完成网络作为编辑草稿保存。`Project::validateForRunning()` 额外拒绝未绑定 HMI、待配置节点、空条件和缺少输出的网络,并检查已启用逻辑中的 T 触点必须有唯一的 TON 驱动。 +```text +Project +├── metadata +├── hmiPages[] + initialHmiPageId +├── alarmDefinitions[] +├── registerComments[] +└── controlLogics[] + └── rungs[] + ├── condition + └── output +``` -HMI 控件公共描述由无 Qt 依赖的 `hmi_control_registry` 模块统一提供。当前 8 种控件的工程稳定类型名、 -界面名称、默认 ID 前缀、默认文字、默认尺寸、M/D 绑定规则和运行值类型只在注册表中定义一次。 -领域校验、编辑服务、运行服务、JSON 存储和属性面板共同查询该描述;控件绘制、按钮写入、 -PageJump 导航、AlarmList 展示和 ProgressBar 范围计算等专用行为仍留在各自模块,不进入公共描述表。 +- 页面、逻辑和节点使用稳定 ID;名称可修改,数组顺序决定工程树顺序和离线扫描顺序 +- 当前编辑页面和当前逻辑是 UI 会话状态,不写入工程 +- HMI 控件类型、默认值、绑定规则和运行值类型由无 Qt 依赖的 `hmi_control_registry` 统一描述 +- 需要寄存器的 HMI 控件、报警条件和梯形图节点直接保存 M/D 地址;项目不再维护统一 `dataPoints` 表 +- `registerComments` 只是 M/D 地址元数据,不保存当前值或引用位置,也不会让地址自动进入 PLC 轮询 -HMI 按钮操作作为工程 `1.0` 的强类型字段保存,支持 `置 ON`、`置 OFF`、`取反` 和 -`瞬时 ON`。新建按钮默认使用瞬时 ON;按钮按下和释放事件由 UI 转交运行服务解释,UI -不直接决定写入值。当前版本不迁移缺少按钮操作字段的旧工程文件。 +工程 JSON 格式固定为严格的 `1.0`。加载顺序为 JSON 解析、版本检查、字段解析、领域校验,全部成功后才能替换当前工程;保存使用原子提交。当前版本不读取旧 `dataPoints`、`stages/branches` 或缺少必填字段的同版本草稿。字段定义见 `docs/工程格式说明.md`。 -`Label` 是不绑定 M/D 的固定文本控件。`PageJump` 是独立控件,使用强类型 `targetPageId` 引用目标页面稳定 ID,同样不绑定 M/D。编辑态点击 PageJump 只执行选择和拖动;离线或真机运行态点击后由 `HmiNavigationService` 在本地切换运行页面,不发出 PLC 写请求,因此 PLC 通信写权限不可用时也不影响页面导航。被 PageJump 引用的页面不能删除,初始页必须先切换后才能删除,工程至少保留一个页面。 +校验分两级: -`AlarmList` 是不绑定单个寄存器的运行状态投影。项目级 `AlarmDefinition` 只支持 M 位为 ON、D 值大于等于阈值和 D 值小于等于阈值三种条件,不设置提示、警告、故障等级。`AlarmEditorService` 负责定义增删改,`AlarmService` 统一读取活动寄存器仓库并维护当前报警的发生时间和确认状态,所有页面上的 AlarmList 共享这份状态。报警条件恢复后立即删除对应运行记录;运行态没有报警时 AlarmList 隐藏,出现报警时显示并置顶。返回编辑态时清空运行记录,工程 JSON 只保存报警定义。 +- `validate()` 保证可保存结构合法,允许未绑定控件、待配置节点和未完成网络作为编辑草稿 +- `validateForRunning()` 检查运行所需绑定和已启用逻辑;禁用草稿不阻止运行 -`ProgressBar` 绑定 D 区字地址,以 `minimumValue` 和 `maximumValue` 将读回值钳制并映射到 -`0~100%`。进度条配置还保存 `showValue`,最小值必须严格小于最大值;控件只读,不产生 PLC -写请求。运行值读取沿用 `HmiRuntimeService` 的 Word 读取契约,绘制层只接收运行值和领域配置。 +数量常量集中在 `domain/project_limits.h`,最终业务口径见 `docs/用户使用/数量边界确认方案.md`。领域、服务、JSON 和 UI 入口必须使用同一组常量,不能各自维护数字。 ## 结构化梯形图 -网络条件采用递归表达式: +网络条件保存为规范化表达式树: ```text ConditionExpression -├── Node 单个触点或比较条件 -├── Wire 恒真的横线叶子,保存网格列跨度 -├── Series 子表达式依次串联,执行 AND -└── Parallel 子表达式并联,执行 OR +├── Node 触点、边沿或比较条件 +├── Wire 无地址横线,保存条件区列跨度 +├── Series 子表达式执行 AND +└── Parallel 子表达式执行 OR ``` -例如 `A OR (B AND C)` 表达为: - -```text -Parallel -├── Node(A) -└── Series - ├── Node(B) - └── Node(C) -``` - -`LogicEditorService` 支持在当前节点或横线后串联插入,并为单个叶子或同一网络中的连续逻辑范围建立并联支路。例如选择 `B、C` 后建立并联会得到 `A AND ((B AND C) OR D)`。横线使用 `Wire` 叶子保存 `1~256` 列跨度;围绕连续范围建立竖线时,服务层原子创建一个同跨度的横线旁路和 `Parallel` 容器。服务层负责校验范围连续性和子表达式完整性,拒绝跨网络、非连续或截断内部子表达式的选择。删除后会归一化空容器和只有一个子项的容器,防止无效嵌套进入执行器。 - -`LogicEditorWidget` 将表达式树投影到固定行列网格,串联节点横向占列,并联分支纵向占行,所有连接只使用水平线和竖线。触点、横线和竖线连接段都可以在画布选择;竖线连接段映射到对应的并联子表达式,删除竖线会删除该支路并归一化结构。内部 `Series / Parallel` 容器仍不作为通用 UI 对象暴露。竖线来自 `Parallel` 边界,不保存为独立领域对象;网格和像素坐标只是编辑投影,不持久化自由线段,因此不会产生悬空线、环路和多输出路径。 - -工具栏中的常开、常闭、上升沿、下降沿、T 触点和比较条件在没有选择时追加到当前网络末尾,选中一个条件节点时插入到该节点后;选中横线后使用条件动作会原位替换横线。`F11` 在所选条件或横线后插入横线,没有选择时追加;`F12` 为所选连续范围建立横线旁路和竖线连接;`Shift+F11` 和 `Shift+F12` 分别删除所选横线和竖线支路。并联支路按钮继续用于新增可配置条件分支。新增节点保持待配置状态,并自动选中供属性区继续编辑。 - -条件节点使用 `std::variant` 保存独立配置类型,当前包含 M 常开/常闭触点、M 上升沿/下降沿触点、T 常开/常闭触点、C 常开/常闭触点和 D 值与常量比较。输出支持普通、置位、复位 M 线圈、非保持 TON、CTU/CTD、MOVE 以及 ADD/SUB。边沿节点在每个扫描周期比较当前 M 值与该逻辑节点自己的上一扫描值,只产生一个扫描周期的脉冲。T 地址是 `T0~T4000` 的离线资源,C 地址是 `C0~C4000` 的离线计数器资源,两者都不是 M/D 地址。新增节点类型时应增加独立配置类型及校验、存储和执行分支,不能向通用节点堆叠无关字段。 - -CTU/CTD 的 C 编号只标识软件执行器中的计数器实例;计数当前值 CV 显式保存到 D 地址,PV 支持常量或 D 地址,复位输入使用 M 地址。CTU 和 CTD 都只在网络条件的上升沿执行一次计数,C 触点读取对应计数器的完成状态。C 资源在已启用逻辑中必须只有一个计数指令驱动,C 触点必须能够找到该驱动,因此不会把 C 误当作需要 Modbus 读取的外部寄存器。 - -## 执行与运行反馈 +输出是网络最右侧的单独节点。`LogicEditorService` 把串联、并联、旁路、替换和删除转换为表达式树操作,失败时回退整个编辑;`LogicEditorWidget` 只把树投影成正交网格。竖线由 `Parallel` 边界派生,网格坐标和自由线段不持久化,因此领域模型不会产生悬空线、环路或多输出路径。 -`SoftwareLogicExecutor` 按 `controlLogics` 数组顺序扫描所有已启用逻辑,再按网络顺序递归求值 `Node / Wire / Series / Parallel`。`Wire` 的逻辑值恒真,但导通轨迹等于其输入电流,因此上游断开时横线不会错误显示为导通。禁用逻辑仅保留结构校验,未完成的禁用草稿不会阻止离线运行。普通线圈每周期写入网络结果,置位和复位线圈只在网络成立时写入;前面逻辑或网络的写入对后面逻辑和网络在同一扫描周期立即可见。 +单网络前 10 列为条件区,输出最多位于第 11 列;横线只能占条件区。第 11 个条件、过深表达式或过多并联行会在编辑和加载阶段被拒绝,具体数值以数量边界文档为准。 -上升沿和下降沿是标准的一次扫描脉冲,运行状态按逻辑 ID 和节点 ID 隔离。TON 是正常非保持接通延时:输入接通后记录 `steady_clock` 起点,`ET` 使用实际经过的毫秒数,达到 `PT` 后 `Q` 为真;输入断开立即清零 `ET/Q`。TON 的输出状态保存在离线执行器的 T 资源表中,T 触点只读取对应 TON 的 `Q`。CTU 计数值在 `0~32767` 范围内递增,达到 PV 后保持完成;CTD 复位时装载 PV,在 `0~32767` 范围内递减,达到 0 后保持完成。ADD/SUB 使用有符号 16 位 D 值,先以更宽的中间类型计算,超出 `-32768~32767` 时饱和到边界并在运行轨迹标记 `overflow`,不会因单次算术溢出停止整个扫描。MOVE、ADD/SUB 只有网络条件成立时才写入目标 D,并在同一扫描周期对后续网络可见。软件不为每个 TON 创建 Qt `QTimer`,现有 50 ms `QTimer` 只负责扫描调度,因此不会与 Qt 事件循环或 PLC 轮询形成额外定时器线程。 +网络运行语义: -每次扫描生成按逻辑 ID 分区的 `LogicTraceSnapshot`,记录节点、表达式、网络和线圈的导通状态,避免不同逻辑中重复的 `rung-1`、`contact-1` 相互覆盖。运行工作台的逻辑下拉框只切换当前轨迹投影,所有已启用逻辑仍持续执行。逻辑画布以绿色显示导通节点和路径;执行错误携带逻辑、网络和节点上下文,画布选择并以红色突出故障位置。 +- 空网络不执行 +- 有条件但没有输出的已启用网络不能运行 +- 有输出但没有条件的网络是恒真网络,每次扫描执行输出 +- `Wire` 的逻辑值为真,但运行轨迹是否导通仍取决于上游电流 -`OfflineSimulationService` 以 50 ms 目标周期驱动扫描,启动时复制逻辑快照、清空虚拟 M/D 并重置边沿和 T 运行状态。故障或停止后清理这些运行状态;重新启动不会继承上次边沿历史或 TON 的 ET/Q。该周期只提供调度精度,不提供硬实时保证。 +节点配置使用 `std::variant` 的独立类型。当前条件包括 M 触点与上下沿、T/C 触点和 D 比较;输出包括普通/置位/复位 M 线圈、TON、CTU/CTD、MOVE、ADD/SUB。新增指令必须增加独立配置、校验、JSON、执行、编辑和测试分支,不能向通用节点堆叠无关字段。 -## M/D 地址使用 +`SoftwareLogicExecutor` 按 `controlLogics` 和网络数组顺序扫描已启用逻辑,前面网络的 M/D 写入在同一扫描周期对后面网络可见。T/C 是离线执行器内部资源,不映射 PLC T/C 区,也不进入 Modbus 轮询;计数器 CV/PV 等显式 M/D 引用仍使用统一仓库。扫描轨迹按逻辑 ID 分区,避免不同逻辑中的重复节点 ID 相互覆盖。 -HMI 控件和梯形图节点直接绑定 `M0~M4000`、`D0~D4000` 地址,不要求预先创建数据点。地址是程序和通信的真实标识,属性面板只编辑区域与地址。 +## 数据源与运行模式 -M/D 是外部地址类型:M 保存位输入、复位信号和线圈结果,D 保存字操作数、MOVE/ADD/SUB 目标以及计数器 CV。T/C 是软件逻辑资源类型,只在离线模式维护运行状态,不在工程中映射为 PLC 的 T/C 区寄存器;`Wire` 只是无地址的逻辑结构,不进入 M/D 引用收集和 PLC 轮询。项目不把梯形图下载到 PLC,因此真机模式只通信访问节点实际引用的 M/D 地址。 +运行界面只访问 `RegisterRepository`。`ActiveRegisterRepository` 根据模式切换实际数据源: -工程级 `registerComments` 只保存 M/D 地址和注释文本,作为地址元数据供编辑器和注释对话框使用,不保存当前值、引用位置或缓存状态,也不会因为存在注释而加入 PLC 轮询。`LadderRung.comment` 保存单个网络的说明文本。两者在 JSON 中分开建模,不能重新引入混合“名称、注释、当前值、引用位置”的数据点表。 - -## 自由监控与运行工作台 - -自由监控是当前调试会话中的寄存器诊断工具,不属于工程模型,也不写入工程 JSON。用户可输入 -`M0~M4000` 或 `D0~D4000`,并按连续个数批量添加地址。监控表最多保留 64 个去重地址,M -值显示为 `ON/OFF`,D 值按带符号 16 位十进制显示。 - -`RegisterMonitorService` 负责地址解析、范围展开、去重、增删和通过活动寄存器仓库读取值。 -`FreeMonitorWidget` 只投影服务结果,不直接访问虚拟仓库、PLC 缓存或串口。监控地址在离线和 -真机运行期间均可增删,因为它们属于调试操作而不是工程结构编辑。 - -主窗口在进入运行模式后自动打开运行监控工作台: - -| 模式 | 同时显示 | 统一数据源 | -| --- | --- | --- | -| 离线运行 | 可导航 HMI、当前所选逻辑轨迹、自由监控 | 虚拟 M/D | -| 真机运行 | HMI、自由监控 | PLC 缓存 | - -真机模式不显示本地梯形图导通轨迹。当前项目不能读取 PLC 内部程序和网络执行轨迹,本地 -梯形图也不保证与 PLC 中的程序一致,因此不得把本地条件推演伪装成 PLC 实际运行状态。 - -## 寄存器与 Modbus RTU - -`HmiRuntimeService` 和运行界面只访问 `RegisterRepository`。`ActiveRegisterRepository` 在两种数据源之间切换: - -- 离线运行使用 `VirtualRegisterRepository` -- 真机运行使用 `PlcRegisterRepository` 的最近一次有效缓存 +```text +编辑:UI -> Editor Services -> ProjectService -> Project + └── ProjectStorage -> JSON -`PlcCommunicationService` 使用 `QModbusRtuSerialMaster` 异步连接、读取和写入。轮询集合由 -HMI 引用、报警定义、梯形图中的 M/D 引用和自由监控地址合并而成,去重后按区域合并相邻地址。 -T/C 触点、TON、CTU/CTD 的 C 资源、横线和工程级注释不会进入该集合;只有计数器 CV、PV 的 D 引用、复位 M 以及 MOVE/ADD/SUB 的 M/D 条件和操作数会进入集合。M 使用线圈,D 使用保持寄存器;所有 Modbus 地址均为从 `0` 开始的原始地址。 +离线:Runtime UI -> ActiveRegisterRepository -> VirtualRegisterRepository + ^ + OfflineSimulationService(SoftwareLogicExecutor) -PLC 连接后可以动态增删自由监控地址。存在读取请求时,新的轮询集合在当前响应处理完成后 -应用,旧响应仍按发出时的读取块解析。连接级首次读取完成后,动态增加地址不会撤销真机 -运行资格;新增地址自身在首次读回前显示“等待读取”。如果连接首次读取尚未完成,新的完整 -轮询集合会共同参与首次读取判定。 +真机:Runtime UI -> ActiveRegisterRepository -> PlcRegisterRepository(cache) + ^ +PLC <-> PlcCommunicationService <------------------┘ +``` -HMI 写入只表示异步请求已受理,不乐观修改 PLC 缓存,实际值由后续读回确认。协议错误或断线会停止轮询、保留最后一次有效值并进入故障状态,错误通过运行模式服务交给 UI 展示。 -PLC 未响应或通信超时且本地串口仍然打开时,通信服务停止正常高频轮询,每次探测失败后等待 -2 秒,再只读探测一次当前轮询集合的首个地址。探测失败不重复打印同一超时日志;探测成功后进入恢复状态, -重新读取全部轮询地址并重建当前连接的首次读取资格,完整首读完成后才恢复为已连接状态。 +| 模式 | 允许工程编辑 | 寄存器来源 | 软件逻辑执行器 | 进入条件 | +| --- | --- | --- | --- | --- | +| 编辑态 | 是 | 无运行数据源 | 停止 | 运行态先退出 | +| 离线运行 | 否 | 虚拟 M/D | 运行 | 工程通过运行校验 | +| 真机运行 | 否 | PLC 读回缓存 | 停止 | PLC 已连接并完成本次首读 | -通信故障会立即撤销当前连接的首次读取资格。若故障发生在真机运行态,运行模式服务自动返回 -编辑态,PLC 最后一次有效缓存仍保留用于日志和故障诊断,但不能作为再次进入真机运行态的依据。 -重新进入真机运行必须等待当前串口会话恢复或重新连接 PLC,并重新完成首次读取。 +离线和真机不能直接互切。进入真机不复制或下发离线值,也不显示本地梯形图导通轨迹。 -通信超时后的自动探测只恢复通信和缓存,不自动恢复真机运行。接线或 PLC 供电恢复后,应用 -完成新的首次读取并继续停留在编辑态,由用户手动决定是否重新进入真机运行。“断开 PLC” -在超时和恢复过程中保持可用,用于关闭并释放仍然打开的本地串口会话。 +自由监控是会话级诊断工具,不属于 `Project`,不写入 JSON。它通过活动仓库读取和单点写入 M/D;真机写入仍必须经过连接与首读门槛。 -通信服务根据 Qt Modbus 错误和串口是否曾成功打开区分连接阶段失败与运行中连接中断。连接阶段 -无法打开串口时直接保持未连接状态;已经连接后收到 `ConnectionError` 或意外进入 -`UnconnectedState` 时,统一报告本地串口连接中断,自动清理会话并进入未连接状态。错误日志 -独立保留,不会为了显示故障而继续把串口会话表示为已连接。 +## Modbus RTU 通信 -## 运行模式边界 +`PlcCommunicationService` 使用 `QModbusRtuSerialMaster` 异步连接、轮询和单点写入,UI 线程不等待串口响应。Modbus 地址使用从 `0` 开始的原始地址。 -| 模式 | 允许编辑 | 寄存器来源 | 软件逻辑执行器 | 进入约束 | -| --- | --- | --- | --- | --- | -| 编辑态 | 是 | 无运行数据源 | 停止 | 运行态必须先退出 | -| 离线运行 | 否 | 虚拟 M/D | 运行 | 工程通过运行校验 | -| 真机运行 | 否 | PLC 读回缓存 | 停止 | PLC 已连接且首次读取完成 | +轮询集合由 HMI、报警、梯形图中的显式 M/D 引用和自由监控地址合并,按区域去重并合并相邻地址。T/C 内部资源、横线和注释不进入轮询。动态修改监控地址时,当前请求按旧快照解析,空闲后再应用新集合。 -离线运行与真机运行不得直接互切。进入真机模式不复制或下发离线值;PLC 内部程序是控制源,PC 端软件逻辑执行器必须停止。 +PLC 缓存只保存最后一次成功读回值。写请求受理后不乐观修改缓存,界面等待后续轮询确认真实值。真机运行资格属于当前连接代次:必须完成完整首读,旧连接的异步回复不能污染重连后的状态。 -## 测试边界 +通信状态包括 `Disconnected / Connecting / Connected / Recovering / Faulted`。连接失败或物理断线会关闭串口会话并进入 `Disconnected`;超时、协议或读写故障会停止正常轮询、保留最后有效缓存并进入 `Faulted`。所有通信故障都会撤销首读资格;若当时处于真机运行,运行模式服务立即返回编辑态。可恢复超时探测成功后进入 `Recovering` 并重新完整首读,但不会自动重新进入真机运行。 -| 优先级 | 测试对象 | 重点内容 | -| --- | --- | --- | -| 高 | 领域模型 | 地址边界、表达式校验与归一化、工程唯一性 | -| 高 | 编辑服务 | 嵌套串并联插入、删除和地址配置 | -| 高 | 执行与模式服务 | 递归求值、轨迹、离线闭环、仓库切换和首次读取约束 | -| 高 | 工程存储 | `1.0` 往返、缺失字段、非法结构和不支持版本 | -| 中 | PLC 通信 | 缓存有效性、地址集合、异步读写和错误反馈 | -| 中 | UI | 编辑、运行切换、选择和错误定位工作流 | -| 冒烟 | 真机 | STOP 状态安全读写 M0/D0,随后 RUN 联动 | +## 扩展规则 -自动化测试不依赖真实 PLC。串口接线、PLC 参数和设备 RUN 行为只能通过现场真机测试确认。 +- 新业务先确定所属领域模型和服务用例,再添加 UI;不得从 UI 直接改工程容器或串口状态 +- 编辑操作必须在服务层校验并原子提交,失败不能污染撤销历史或留下半成品 +- 新持久化字段必须同步领域校验、严格 JSON 往返、缺失/非法字段测试和格式说明;是否升级格式版本必须明确决定 +- 新数量边界必须进入 `project_limits.h`、数量边界文档和上下界测试 +- 新 HMI 类型优先扩展 `hmi_control_registry`,专用交互留在对应服务和图元 +- 不重新引入数据点表、自由线段、UI 直连串口或真机执行本地梯形图 +- 自动化测试按风险覆盖领域、服务、存储、通信和 UI;真实串口、接线和设备 RUN 联动必须现场验证 diff --git a/docs/工程格式说明.md b/docs/工程格式说明.md index 8322b19..bd67235 100644 --- a/docs/工程格式说明.md +++ b/docs/工程格式说明.md @@ -137,7 +137,7 @@ ProgressBar 使用绑定 D 地址的有符号 16 位字值,并将其限制在 {"id": "wire-1", "kind": "wire", "columnSpan": 2} ``` -`columnSpan` 是必填整数,范围为 `1~256`。它表示横线占用的逻辑网格列数,不是像素长度。竖线不保存为独立对象,而是由 `Parallel` 表达式的支路边界派生。画布像素坐标、自由连线和运行轨迹是 UI 或运行会话投影,不进入 JSON;横线不绑定寄存器,也不产生 M/D 轮询地址。节点配置的类型和值域如下: +`columnSpan` 是必填整数,范围为 `1~10`。它表示横线占用的条件区逻辑网格列数,不是像素长度。单个网络前 10 列是条件区,第 11 列固定放输出指令。竖线不保存为独立对象,而是由 `Parallel` 表达式的支路边界派生。画布像素坐标、自由连线和运行轨迹是 UI 或运行会话投影,不进入 JSON;横线不绑定寄存器,也不产生 M/D 轮询地址。节点配置的类型和值域如下: ```json {"type": "edgeContact", "address": {"area": "M", "index": 0}, "mode": "rising"} @@ -158,4 +158,4 @@ MOVE、ADD 和 SUB 只能把结果写入 D 地址,源操作数和算术左右 保存顺序为领域校验、JSON 序列化、`QSaveFile` 原子提交。加载顺序为 JSON 语法、`formatVersion`、必填字段和类型、领域关系校验,全部成功后才替换当前工程。 -工程保存允许未绑定的寄存器控件和未完成梯形图草稿。进入离线运行还必须通过运行校验:所有 HMI 寄存器控件完成绑定、所有 PageJump 目标有效、所有报警定义合法、所有启用逻辑完整;禁用草稿逻辑不阻止运行。 +工程保存允许未绑定的寄存器控件和未完成梯形图草稿。进入离线运行还必须通过运行校验:所有 HMI 寄存器控件完成绑定、所有 PageJump 目标有效、所有报警定义合法、所有启用逻辑包含输出指令;禁用草稿逻辑不阻止运行。只有输出而没有条件的网络表示左侧电源线直接接通,运行时按恒真条件执行。 diff --git a/docs/用户使用/数量边界确认方案.md b/docs/用户使用/数量边界确认方案.md new file mode 100644 index 0000000..1d04308 --- /dev/null +++ b/docs/用户使用/数量边界确认方案.md @@ -0,0 +1,93 @@ +# 数量边界确认方案 + +这份表就是本项目最终采用的数量规则。表里的数字是确定值,不是建议值。达到上限还能正常使用,再多一个就会被软件拒绝;只有日志这类临时信息会自动删除最旧内容。 + +## 1. 必须做硬限制的项目 + +| 类别 | 项目 | 最小值 | 最大值 | 超出后怎么处理 | 为什么必须限制 | +| --- | --- | ---: | ---: | --- | --- | +| PLC 软元件 | M 地址 | 0 | 4000 | 拒绝保存、加载或连接使用 | 项目需求已经明确限定 | +| PLC 软元件 | D 地址 | 0 | 4000 | 拒绝保存、加载或连接使用 | 项目需求已经明确限定 | +| PLC 软元件 | T 定时器编号 | 0 | 4000 | 拒绝保存或运行 | 和本项目 M/D 范围保持一致,低于信捷硬件范围 | +| PLC 软元件 | C 计数器编号 | 0 | 4000 | 拒绝保存或运行 | 和本项目 M/D 范围保持一致,低于信捷硬件范围 | +| 数值 | D 字、比较值、阈值、普通常量 | -32768 | 32767 | 编辑和加载时拒绝越界值,运算溢出时压到边界值 | D 是有符号 16 位数据 | +| 计数器 | 常量预设值 PV | 0 | 32767 | 负数或更大值直接拒绝 | 16 位普通计数不能使用负预设值 | +| 计数器 | D 地址提供的 PV、当前值 CV | 0 | 32767 | 读到负数按 0 使用,超过范围按 32767 使用 | 计数语义不能出现负数量 | +| 定时器 | TON 预设时间 | 1 ms | 86400000 ms | 拒绝保存或运行 | 0 没有定时意义,最大值固定为 24 小时 | +| 工程文件 | 单个 JSON 文件大小 | 0 | 16 MiB | 读取 JSON 前直接拒绝 | 防止超大文件一次性占满内存 | +| HMI | 页面数量 | 0 | 128 | 第 129 个页面不能创建或加载 | 控制工程树和运行页面的内存开销 | +| HMI | 单页控件数量 | 0 | 512 | 第 513 个控件不能创建或加载 | 保证编辑画布和运行刷新可控 | +| HMI | 页面宽度、高度 | 1 | 8192 | 拒绝保存或加载 | 防止异常尺寸撑大画布 | +| HMI | 控件宽度、高度 | 1 | 8192 | 拒绝保存或加载;控件还必须完整放在页面内 | 防止异常尺寸和越界绘制 | +| HMI | 单控件扩展属性 | 0 对 | 64 对 | 第 65 对属性不能加载或保存 | 防止属性对象无限增长 | +| 报警 | 报警定义数量 | 0 | 512 | 第 513 条报警不能创建或加载 | 报警每个刷新周期都要判断 | +| 注释 | M/D 软元件注释数量 | 0 | 8002 | 超出后拒绝保存或加载 | M0~M4000 和 D0~D4000 每个地址最多一条,共 8002 条 | +| 控制逻辑 | 控制逻辑数量 | 0 | 128 | 第 129 组不能创建或加载 | 控制工程树和扫描工作量 | +| 控制逻辑 | 每组逻辑的网络数量 | 0 | 1024 | 第 1025 个网络不能创建或加载 | 每次软件扫描都要执行这些网络 | +| 单个网络 | 条件表达式节点和叶子总数 | 0 | 4096 | 超出后编辑操作回退,JSON 加载失败 | 防止单个网络过度复杂 | +| 单个网络 | 表达式嵌套深度 | 1 层 | 20 层 | 第 21 层在解析时直接拒绝 | 10 列条件区下,第 20 层是规范化串并联结构能够达到的最深边界 | +| 单个容器 | 串联或并联子项数量 | 2 | 256 | 第 257 项不能加入或加载 | 控制单层表达式规模 | +| 单个网络 | 条件区列数 | 0 | 10 | 第 11 个条件不能加入,加载失败 | 0 列表示无条件输出;最多 10 列保证网络容易查看 | +| 单个网络 | 总列数 | 1(无条件线圈) | 11 | 第 11 列固定放输出指令 | 普通网络前 10 列放条件;无条件线圈可以直接从左侧电源线接通 | +| 单个网络 | 逻辑网格行数 | 1 | 256 | 超出后编辑操作回退,加载失败 | 防止并联支路纵向无限扩展 | +| 横线 | 单段横线跨度 | 1 列 | 10 列 | 拒绝编辑或加载 | 横线只能占用条件区,不能占用输出列 | +| 字符串 | ID、格式版本、页面跳转目标 ID | 0 字节 | 128 个 UTF-8 字节 | 拒绝保存或加载;业务要求非空的 ID 仍必须非空 | ID 只用于稳定识别,不需要长文本 | +| 字符串 | 工程名、页面名、控件文本、报警文本、注释 | 0 字节 | 4096 个 UTF-8 字节 | 拒绝保存或加载;业务要求非空的字段仍必须非空 | 给中文说明留足空间,同时防止异常长文本 | +| 字符串 | HMI 属性名称 | 1 字节 | 128 个 UTF-8 字节 | 拒绝保存或加载 | 属性名是短标识 | +| 字符串 | HMI 属性值 | 0 字节 | 4096 个 UTF-8 字节 | 拒绝保存或加载 | 属性值可能保存显示配置,但不能无限增长 | +| 自由监控 | 去重后的监控地址 | 0 | 64 | 第 65 个监控地址不能加入 | 这是用户临时监控区,不应拖慢 PLC 轮询 | +| 编辑历史 | 可撤销记录 | 0 | 100 | 新操作加入后自动删除最旧记录 | 历史记录会保存工程快照,占用内存 | +| 输出面板 | 日志条数 | 0 | 1000 | 加新日志前自动删除最旧一条 | 日志只用于查看近期状态 | +| PLC 通信 | 站号 | 1 | 247 | 不允许发起连接 | 248~254 虽然信捷手册可表示,但属于 Modbus 保留地址,本项目不使用 | +| PLC 通信 | 波特率 | 9600 | 115200 | 只接受 9600、19200、38400、57600、115200 | 只开放本项目已经验证的串口档位 | +| PLC 通信 | 数据位 | 7 | 8 | 只接受 7 或 8 | 串口帧格式必须明确 | +| PLC 通信 | 停止位 | 1 | 2 | 只接受 1 或 2 | 串口帧格式必须明确 | +| PLC 通信 | 响应超时 | 100 ms | 30000 ms | 不允许发起连接 | 太短容易误判,太长会让故障等待时间不可接受 | +| PLC 通信 | 失败重试次数 | 0 | 5 | 不允许发起连接 | 采用信捷手册给出的 Modbus 重试范围 | +| PLC 通信 | 轮询周期 | 50 ms | 10000 ms | 不允许发起连接 | 太快会压满串口,太慢会让画面失去实时性 | +| PLC 通信 | 单个 Modbus 读块 | 1 个 | 120 个 | 自动拆成多个读块 | 信捷 RTU 数据区最多 252 字节,120 个字安全留有余量 | +| PLC 通信 | 去重后的总轮询地址 | 0 | 1024 | 拒绝连接或拒绝更新轮询表 | 限制每轮通信总工作量 | +| PLC 通信 | 总轮询读块 | 2(无配置时保底读取 M0、D0) | 64 | 拒绝更新轮询表 | 地址太分散时,即使总数不大也会产生大量报文 | +| PLC 写入 | 同时未完成的写请求 | 0 | 1 | 前一笔没完成时拒绝新写入 | 串行写可以避免请求堆积和写入顺序混乱 | + +补充说明:空工程允许 0 个 HMI 页面;一旦已经创建页面,用户在编辑器里不能把最后一个页面删除。校验方式只能选无校验、奇校验或偶校验,常用真机配置仍是 `COM3、9600、8E1`。 + +梯形图网络只放一个线圈或其他输出指令也是合法的。它表示条件一直成立,软件每次扫描都会执行这个输出;普通线圈会一直导通,MOVE、ADD/SUB 等指令会每次扫描执行。只有条件却没有输出仍属于未完成网络,不能进入运行态。 + +## 2. 不需要再单独限制的项目 + +| 项目 | 最终结论 | 原因 | +| --- | --- | --- | +| 当前同时触发的报警条数 | 不另设上限 | 它天然不会超过 512 条报警定义 | +| 页面跳转目标数量 | 不另设上限 | 目标只能来自最多 128 个现有页面 | +| PLC 缓存中的 M/D 数量 | 不另设上限 | 缓存由 `0~4000` 地址范围和 1024 个轮询地址共同限制 | +| 单次按钮点击、数值输入、保存和加载次数 | 不限制 | 每次操作完成后不会持续积累同类对象 | +| 软件连续运行时间 | 不限制 | 运行时间本身不是内存集合,也不是 PLC 资源 | +| 离线仿真的累计扫描次数 | 不限制 | 只保存一个 64 位计数,不保存每次扫描的完整副本 | +| 工程文件路径长度 | 不在业务层另设上限 | 交给 Windows 和 Qt 的文件接口处理,工程内容仍受 16 MiB 限制 | +| 可用 COM 端口列表数量 | 不另设上限 | 列表来自操作系统,项目不持久化累积这些端口 | +| 报警阈值、进度条范围、普通 HMI 数值的额外业务范围 | 不再加第二套范围 | 统一使用 D 的 `-32768~32767`,进度条只额外要求最小值小于最大值 | +| 加减法参与次数 | 不单独限制 | 执行量已经受 128 组逻辑、每组 1024 个网络和单网络复杂度限制 | + +## 3. 这些数字从哪里来 + +- `docs/pdf/0_综合平台编程器_修改.pdf` 第 2 页明确要求 M 区和 D 区使用 `0~4000` +- `XD、XL系列可编程控制器用户手册(基本指令篇)` PDF 第 272 页的 XDH/XLH 表中,实际 M/D 资源达到 `0~20479`,T/C 达到 `0~4095`。本项目仍统一收紧为 `0~4000`,不会超出 XDH-60T4-E 的资源范围 +- 同一手册 PDF 第 28 页说明 D 是有符号 16 位寄存器,16 位普通计数范围为 `1~32767`。本项目允许 PV 为 0,表示尚未设定或立即完成,不允许负数 +- 同一手册 PDF 第 274 页说明 Modbus RTU 数据区最多 252 字节,站号最大可到 254。本项目单读块固定为 120 个,站号固定为 `1~247` +- 同一手册 PDF 第 311 页给出 Modbus 重试次数 `0~5`、回复超时 `0~65535`。本项目采用重试 `0~5`,并把超时进一步收紧为 `100~30000 ms`,避免无限等待 +- 页面、控件、报警、逻辑、JSON 大小、日志等数字在信捷手册里没有规定,所以按本软件的内存、绘制、扫描和通信成本给出固定安全预算 + +## 4. 用户实际会看到什么 + +- 正常编辑时,达到页面、控件、报警、逻辑或网络上限后,软件会直接提示具体上限,不会先添加再留下坏工程 +- 手工改 JSON 绕过界面也没用,加载时会重新检查文件大小、数组数量、字符串长度、页面尺寸和表达式深度 +- PLC 参数不只在对话框里限制,真正连接前还会再检查一次 +- 超过 1000 条输出日志时,只删除最旧日志,不影响工程和 PLC 数据 +- PLC 写请求正在等待回复时,新写请求会被拒绝,等前一笔完成后再操作即可 + +## 5. 本次验收结果 + +- Release 主程序已经重新构建通过,数量边界相关的 10 组自动化测试全部通过 +- 2026-08-19 使用真实 PLC 在 `COM3、9600、8E1、站号 1` 下完成读写验证:先读取 `D4000=0`,写入 `1` 后读回 `1`,再恢复并读回 `0` +- 真机验证没有下载 PLC 程序,测试结束后 `D4000` 已恢复原值