Просмотр исходного кода

docs: 更新Float32与运行模式说明

main
suyu 3 недель назад
Родитель
Сommit
c628724a98
11 измененных файлов: 260 добавлений и 105 удалений
  1. +2
    -1
      AGENTS.md
  2. +6
    -6
      docs/XDH-60T4-E指令与Modbus要点.md
  3. +5
    -4
      docs/XDH-60T4-E硬件与接线要点.md
  4. +45
    -44
      docs/ai/handoff.md
  5. +40
    -9
      docs/architecture.md
  6. +10
    -14
      docs/二次开发/信捷D寄存器与浮点扩展说明.md
  7. +20
    -4
      docs/工程格式说明.md
  8. +1
    -1
      docs/开发顺序.md
  9. +12
    -9
      docs/测试约定.md
  10. +90
    -0
      docs/用户使用/应用配置说明.md
  11. +29
    -13
      docs/用户使用/数量边界确认方案.md

+ 2
- 1
AGENTS.md Просмотреть файл

@@ -25,6 +25,7 @@
- C++ 规范:`docs/C++代码规范.md`
- 原始需求:`docs/0_综合平台编程器_修改.md`
- 数量边界最终规则:`docs/用户使用/数量边界确认方案.md`
- 应用配置文件:`docs/用户使用/应用配置说明.md`
- 工程 JSON 格式:`docs/工程格式说明.md`
- 开发阶段顺序:`docs/开发顺序.md`(阶段参考,当前状态以 handoff 为准)
- 测试约定:`docs/测试约定.md`
@@ -56,7 +57,7 @@
- 数量边界统一定义在 `app/src/domain/project_limits.h`,修改时同步更新数量边界文档和边界测试
- M/D 项目地址范围固定为 `0~4000`;Modbus 使用从 `0` 开始的原始地址
- 离线模式使用虚拟 M/D 并运行软件逻辑执行器
- 真机模式使用 PLC 读回缓存并停止软件逻辑执行器;切换时先读 PLC,不复制离线值
- 真机模式使用 PLC 读回缓存;每轮完整轮询后在临时仓库推算本地梯形图轨迹,不把本地输出写入 PLC;切换时先读 PLC,不复制离线值
- 本项目不生成、编译或下载 PLC 程序,不得把本地梯形图轨迹当成 PLC 内部程序轨迹
- 代码注释只解释不明显的约束,末尾不要加句号 `。`



+ 6
- 6
docs/XDH-60T4-E指令与Modbus要点.md Просмотреть файл

@@ -16,7 +16,7 @@ XDH 的实际软元件范围远大于项目范围:普通 M 区为 `M0~M199999`

M 是普通辅助继电器,不可直接驱动外部负载;真机设备是否动作由 PLC 内部程序决定。D 是数据寄存器,单个 D 默认按带符号 16 位数处理,范围为 `-32768~32767`。

32 位数据由两个相邻 D 寄存器组成:`D0` 为低字,`D1` 为高字,例如 `D1D0` 组成一个 32 位数。初版 HMI 数值控件默认处理单个 16 位 D;32 位和浮点显示作为后续扩展
32 位数据由两个相邻 D 寄存器组成:起始 D 为低字,后一个 D 为高字,例如 `D10~D11` 组成一个 Float32。HMI 数值控件和自由监控可选择 `Int16` 或 `Float32`,梯形图普通比较、MOVE、ADD、SUB 仍只按 16 位整数执行

来源:基本指令篇印刷页 35、44。

@@ -64,8 +64,8 @@ XDH 的 Modbus 可访问范围为 `M0~M20479` 和 `D0~D20479`,所以项目约
| `0x05` | 写单个线圈 | M 区 | HMI 按钮写一个 M 位。 |
| `0x0F` | 写多个线圈 | M 区 | 后续批量写入使用,初版可不做 UI 入口。 |
| `0x03` | 读保持寄存器 | D 区 | 读取数值、参数和状态。 |
| `0x06` | 写单个保持寄存器 | D 区 | HMI 数值输入写一个 D。 |
| `0x10` | 写多个保持寄存器 | D 区 | 后续批量参数下发使用。 |
| `0x06` | 写单个保持寄存器 | D 区 | Int16 数值输入写一个 D。 |
| `0x10` | 写多个保持寄存器 | D 区 | Float32 一次写入连续两个 D。 |

本期联机不使用 X、Y、S、SM、HD 等其他软元件,也不需要实现 PLC 作为 Modbus 主站的编程指令。

@@ -109,7 +109,7 @@ HMI 控件不直接收发串口数据。通信服务更新联机寄存器缓存

1. 合并 HMI、报警、梯形图和自由监控实际引用的 M/D 地址。
2. 合并相邻地址,分别使用 `0x01` 批量读 M 区、`0x03` 批量读 D 区。
3. 用户点击按钮时使用 `0x05` 写 M 区;提交数值时使用 `0x06` 写 D 区
3. 用户点击按钮时使用 `0x05` 写 M 区;提交 Int16 数值时使用 `0x06` 写一个 D,提交 Float32 时使用 `0x10` 一次写两个连续 D
4. 写入成功后等待下一次轮询确认实际值,不以本地点击状态代替 PLC 响应。
5. 超时、CRC 错误或 Modbus 异常响应时保留最后一次有效值,并在状态栏和日志中报告错误。

@@ -118,9 +118,9 @@ HMI 控件不直接收发串口数据。通信服务更新联机寄存器缓存
| 模式 | 变量来源 | 图形逻辑执行器 |
| --- | --- | --- |
| 离线仿真 | PC 内存中的 M/D | 运行。 |
| 真机联机 | PLC 读回的 M/D 缓存 | 停止。 |
| 真机联机 | 每轮完整读回后的 PLC M/D 缓存副本 | 在临时仓库中只读推算轨迹。 |

真机联机时,PLC 内部程序是唯一控制源。PC 端不执行仿真梯形图,避免同时写入同一批 M/D 地址
真机联机时,PLC 内部程序仍是唯一设备控制源。HMI 按钮和数值输入继续按现有规则写 PLC;本地梯形图的线圈、MOVE、ADD 和 SUB 只修改本轮临时仓库,供后续本地网络计算和轨迹显示,不会写入 PLC。下一轮完整轮询后重新从最新 PLC 缓存开始,因此轨迹是带通信延迟的本地推算结果,不是 PLC 内部真实轨迹

## 6. 首次真机验证步骤



+ 5
- 4
docs/XDH-60T4-E硬件与接线要点.md Просмотреть файл

@@ -57,16 +57,17 @@ PLC 必须先使用信捷官方编程软件或 XINJEConfig 配置工具,将 CO
- 按 PLC 配置发送 Modbus RTU 读写报文;
- 轮询绑定到 HMI 控件的 M/D 地址;
- 将 HMI 按钮和数值输入写入真实 PLC;
- 每轮完整轮询后,根据 PLC 缓存推算本地梯形图轨迹;
- 显示连接状态、超时、CRC 错误和 PLC 异常响应。

### 4.2 PC 软件不负责的内容

- 向 PLC 下载、上传或编译梯形图程序;
- 修改 PLC 的串口配置;
- 在真机模式下执行 PC 内的仿真梯形图
- 将真机本地梯形图的线圈或数据指令结果写入 PLC
- 直接驱动 PLC 的 I/O 输出。

真机模式的唯一控制逻辑来源是 PLC 内已经存在的程序。PC HMI 只是上位机,通过寄存器发送命令和显示状态。
真机模式的唯一设备控制逻辑来源是 PLC 内已经存在的程序。PC HMI 通过寄存器发送命令和显示状态;本地梯形图只在临时寄存器副本上推算轨迹,不参与 PLC 控制

## 5. 联机前检查清单

@@ -75,7 +76,7 @@ PLC 必须先使用信捷官方编程软件或 XINJEConfig 配置工具,将 CO
3. PLC 已配置为 Modbus RTU 从站,并完成断电重启。
4. PC 软件中的站号、波特率、数据位、校验位和停止位与 PLC 一致。
5. 先读取 `M0` 和 `D0` 验证通信,再接入完整 HMI 页面。
6. 真机模式下关闭 PC 端逻辑执行器,避免双重写入寄存器
6. 确认真机界面标注为“本地推算轨迹”,并且本地梯形图输出不写入 PLC

## 6. 已验证联机记录

@@ -90,7 +91,7 @@ PLC 必须先使用信捷官方编程软件或 XINJEConfig 配置工具,将 CO

2026-08-21,用户使用 `json/motor_forward_reverse.json` 在已连接的真实 PLC 上完成真机 RUN 联动手动验收。运行监控 HMI 的正转启动、反转启动和停止操作均可正常联动,正转运行和反转运行状态反馈正常。

该项结果依赖现场 PLC 内部程序、RS-485 接线和实际负载,自动化测试只验证 PC 软件的通信契约、缓存和运行模式隔离,不能替代现场 RUN 验收。真机模式下 PC 不执行本地梯形图,PLC 内部程序是唯一控制逻辑来源
该项结果依赖现场 PLC 内部程序、RS-485 接线和实际负载,自动化测试只验证 PC 软件的通信契约、缓存和运行模式隔离,不能替代现场 RUN 验收。新增真机本地轨迹只读取 PLC 缓存并在临时仓库推算,不会改变这次既有联动记录中的 PLC 程序或 M/D 控制结果

## 7. 资料出处



+ 45
- 44
docs/ai/handoff.md Просмотреть файл

@@ -6,63 +6,64 @@

- 分支:`main`
- 核心闭环可用:HMI 编辑、结构化梯形图、离线仿真、工程 JSON、运行监控、自由监控和 Modbus RTU 真机读写均已实现
- 梯形图条件区已经统一为显式模型:可见横线保存为 `Wire`,明确断路保存为 `Gap`,输出固定在第 11 列
- 工程 JSON 继续严格使用 `1.0`,不增加版本迁移或早期同版本草稿兼容分支
- HMI 进度条控件已经完整移除,编辑器、运行投影和工程 JSON 均不再支持该类型
- 返回编辑态后会拒绝退出前排队的离线扫描轨迹,梯形图不会重新显示旧的绿色导通状态
- 新建工程名称为空或全是空白时会禁用确认按钮,取消输入时继续安全保留当前工程,不再静默接受无效名称
- 第一次保存和工程另存为会用当前工程名称预填 `.json` 文件名,并兼容 Windows 非法字符与保留设备名
- PLC 通信配置支持刷新当前可用串口及按当前站号自动搜索:搜索异步遍历可用串口和全部受支持串口参数,找到后自动回填并进入正式连接,搜索可随时取消
- HMI/梯形图复制粘贴、数量边界收紧和全局对话框标题栏调整仍保留在当前未提交工作区
- 当前没有代码阻塞,Release 功能测试、性能测试和主程序构建均已通过
- D 区 Float32/REAL 读写已接入:HMI 和自由监控可选 Int16/Float32,底层仍保存原始 16 位字,Float32 使用低字/高字连续双 D 编解码
- 真机本地梯形图只读推算已实现:HMI/自由监控继续读写 PLC,本地梯形图只读 PLC 缓存并在临时仓库推算轨迹
- 应用外部配置业务已经完成,固定读取可执行文件目录下的 `config/application.ini`
- 配置支持 6 个体验型数量上限、2 个新建 HMI 页面默认尺寸和 6 个 PLC 串口默认参数
- 配置启动时只读取一次,不热更新,不覆盖已有文件,不回写 PLC 配置窗口中的临时修改
- 当前没有代码阻塞,12 个 Release 功能测试、性能测试和 Release 主程序构建均已通过

## 工作区状态

- 当前未提交功能改动包含 HMI/梯形图复制粘贴、数量边界收紧、对话框标题栏调整、显式 `Wire / Gap` 梯形图连接模型、离线轨迹退出时序修复、HMI 进度条控件移除,以及 PLC 自动搜索
- PLC 自动搜索新增服务层搜索契约、独立 Qt Modbus RTU 探测实现、配置对话框进度/取消交互、主窗口依赖注入、候选覆盖测试和离屏对话框流程测试
- Git 忽略的 `json/` 本地示例工程已同步当前格式;综合测试工程删除了进度条对象和关联说明,电机正反转工程无需内容调整
- `.gitignore`、`AGENTS.md`、未跟踪脚本和测试大纲文档属于工作区已有内容,没有被本轮回退
- 当前未提交修改同时包含应用外部配置功能和真机本地只读推算功能,涉及启动加载、领域与服务动态上限、运行模式、PLC 轮询回调、UI 轨迹投影、测试和文档
- 工程树的新建页面、新建控制逻辑及重命名入口已统一禁止提交空名称或纯空白名称,确认按钮会随输入有效性即时启停
- 新增 `ApplicationSettings`、严格 UTF-8 INI 加载器及独立 `application_settings_tests`
- 新增用户文档 `docs/用户使用/应用配置说明.md`,并同步架构、数量边界、测试约定和 `AGENTS.md` 索引
- `build/` 只保存本地构建和测试输出,不纳入 Git
- 未创建或修改发布包内的默认 INI;正式程序首次运行时由加载器按实际可执行文件目录创建

## 本轮变更结论

- 条件区每一段可见、可选择和可删除横线都持久化为 `Wire(columnSpan)`;没有横线的位置使用 `Gap(columnSpan)` 明确占位
- 有输出的网络必须由 `Node / Wire / Gap` 显式占满前 10 列;直接添加输出生成 `Wire(10) + Output`,一个触点后添加输出生成 `Node + Wire(9) + Output`
- 删除触点会在原位置留下单格 `Gap`;删除横线只断开选中的网格,支持在 `Gap` 上补横线或原位插入触点
- 并联各支路必须显式等宽,短支路补线保存为真实 `Wire`;母线、节点和输出自身端子、并联竖线仍由结构派生
- `Gap` 可以保存草稿,但运行校验会拒绝;执行器不再把 `condition=null + output` 解释为隐式恒真网络
- JSON 继续严格读写 `formatVersion: "1.0"`,新增 `kind: "gap"` 表达式;早期依赖隐式输出连线或隐式并联补线的同版本草稿不会自动迁移
- `RuntimePanelController` 只在仍处于离线运行且执行器状态为 `Running` 时接收扫描轨迹;返回编辑态后的过期排队回调会被丢弃
- HMI 控件枚举、注册描述、专用配置模型、编辑服务比较/默认值、JSON 字段、画布绘制、属性表单、添加动作和工具栏图标中的进度条代码均已删除
- 严格 `1.0` 工程只接受当前注册表中的 7 类 HMI 控件;包含已移除控件类型的工程会按不支持类型拒绝加载,不做同版本迁移
- HMI 编辑和工程管理测试样例已移除该控件,并保留未知 HMI 类型加载失败的通用回归覆盖
- 自动搜索固定使用界面当前站号,按优先级遍历每个可用 PC 串口的 60 组受支持串口参数;不穷举 `1~247` 站号
- 每组参数只读 `D0`,不写 PLC;正常回复或合法 Modbus 异常回复都视为参数匹配,找到后先释放搜索串口再交给原正式连接流程
- 搜索期间配置表单和确认按钮禁用,自动搜索按钮切换为停止命令;取消、无串口和全部失败都有明确结果,找到后自动回填端口、波特率、数据位、校验位和停止位
- 端口下拉框右侧提供刷新按钮;窗口打开和手动刷新时都只保留系统当前实际存在的串口,不再把已经拔掉的上次配置端口重新加入列表
- 默认配置文件包含 `[Config]`、`[ProjectLimits]`、`[HmiDefaults]` 和 `[PlcDefaults]` 四个分区,当前版本固定为 `1`
- 文件不存在时自动创建目录和默认文件,本次使用代码默认值;创建或读取失败时默认值启动,底部输出详细原因并弹一次警告
- 缺失普通字段使用默认值,未知字段忽略;重复字段、版本错误、非法 UTF-8、非整数、越界或不支持的串口枚举值会使整份配置回退
- 领域聚合校验、编辑服务、JSON 保存加载、运行前校验和 UI 使用同一份启动期 `ProjectLimitSettings`
- 用户调低上限后,超限工程在局部对象中解析失败,不替换当前工程、不加载部分内容,也不修改原 JSON
- 全工程 HMI 控件 2048、全工程网络 2048、M/D 地址、16 位数值、梯形图结构、表达式、Modbus 和文件容量继续使用代码硬限制
- `DefaultPageWidth/DefaultPageHeight` 只影响新建页面;已有页面继续使用工程 JSON 中的尺寸
- PLC 默认值初始化主窗口连接配置;不存在的默认 COM 端口仍按现有规则过滤,不会制造无效端口项
- 页面和控制逻辑名称在界面提交前统一去除首尾空白;服务层继续负责空名称、长度和唯一性校验
- 真机每次完整 PLC 轮询成功后,复制梯形图引用的 M/D 到独立临时虚拟仓库,再复用离线扫描规则计算轨迹
- 本地线圈、MOVE、ADD、SUB 只修改本轮临时仓库;前面网络的临时结果可供本轮后续网络使用,下一轮重新从 PLC 最新缓存开始
- 真机轨迹明确标记为“PLC 缓存 · 本地推算轨迹”,不读取、生成、下载或修改 PLC 内部程序和内部轨迹
- 真机梯形图区显示只读边界提示:本地输出不写入 PLC 程序或 M/D,同时说明 HMI 和自由监控仍可写 PLC
- 本地推算失败或 PLC 断线/通信故障时停止推算并退出真机运行;推算故障详情在退出后保留,下一次启动重新清理
- Float32 工程 JSON 固定保存 `dataType: "int16"` 或 `"float32"`;缺少字段直接拒绝加载,不增加旧结构兼容分支
- 普通梯形图比较、MOVE、ADD、SUB 继续按 Int16;普通整数指令可以读取 Float32 占用字,但不能写入其任一 D
- 自由监控完全重复点拒绝,部分重叠允许但提示;Float32 批量地址步长为 2,并显示 D 起始~结束范围
- Float32 写入使用一次写两个连续保持寄存器,真机缓存等待轮询读回;Float32 轮询分块不会拆开两个连续字

## 最近验证

- 2026-08-25 PLC 自动搜索完成后,11 个 Release 功能测试目标全部通过;`plc_connection_dialog_tests` 覆盖开始、取消、进度、参数回填和端口刷新,`plc_runtime_tests` 覆盖 180 组候选优先级、去重和边界
- 2026-08-24 修复新建工程空名称静默失败后,10 个 Release 功能测试目标全部通过,Release 主程序重新编译并链接成功
- 2026-08-24 删除 HMI 进度条后重新 qmake、构建并通过 10 个 Release 功能测试目标:Domain、Alarm、HMI 编辑、梯形图编辑、离线仿真、工程管理、寄存器监控、运行模式、运行面板控制器和 PLC Runtime 全部通过
- 新增真实 `RuntimePanelController + LogicEditorWidget` 无界面回归测试,确认退出前排队的 `scanCompleted` 在编辑态不会恢复绿色轨迹
- 独立 Release 性能目标通过:100 个控制逻辑扫描约 `0.207 ms`,4001 个 M 寄存器完整读写约 `0.048 ms`;CSV 位于 `build/tests/release/performance_tests/benchmark.csv`
- Qt Release 主程序重新构建并链接成功:`build/release/release/integrated_platform.exe`
- 工程管理测试覆盖 `Gap` 严格 `1.0` 往返、`Wire(10)` 恒真输出、拒绝加载 `condition=null + output` 的隐式连线草稿,以及拒绝未知 HMI 控件类型
- `json/full_hmi_ladder_test.json` 和 `json/motor_forward_reverse.json` 均已使用当前 `JsonProjectStorage` 实际加载成功,并通过领域校验
- 2026-08-25 使用新搜索器按站号 `1` 对本机 `COM1/COM2/COM3` 完成 180 组参数的只读 `D0` 尝试,设备未返回 Modbus 响应并正确报告未找到;本次没有写寄存器,不需要恢复原值,也不能作为成功联机证据
- 2026-08-25 执行 `run_qt_tests.ps1 -Configuration Release -Suite All`,12 个 Release 功能测试目标和性能测试全部通过
- 2026-08-25 新增 Float32 编解码、HMI 双字读写、D3999/D4000 边界、HMI 重叠、自由监控步长/重叠/读写和 Float32 轮询元数据测试;All 套件全部通过
- `application_settings_tests` 覆盖首次创建与默认内容、完整加载、缺失和未知字段、重复字段、越界、非整数、版本不兼容、非法 UTF-8 及创建失败回退
- 领域和服务测试覆盖 HMI 页面/控件、报警、控制逻辑/网络动态上限与新建页面默认尺寸
- 工程管理测试覆盖调低页面上限后拒绝两页面工程,并确认当前工程状态保持不变
- 性能基准通过:代表性软件扫描约 `0.203 ms`,4001 个虚拟寄存器边界工作量约 `0.0454 ms`
- 重新执行 qmake 并完成 Release 主程序链接:`build/float32/release/integrated_platform.exe`
- 新增真机测试覆盖:本地临时输出不改变 PLC 源仓库、同一轮后续网络可见、完整轮询只触发一次推算、断线/通信故障停止推算并返回编辑态、推算故障详情保留
- 使用已有 `build/manual_plc_discovery_probe/release/manual_plc_discovery_probe.exe` 在 `COM3 / 9600 / 8E1 / 站号 1` 完成一次真实 D0 探测,结果找到 PLC;本轮未重复执行真实 M/D 写入恢复流程
- 本轮未做完整真机运行轨迹人工验收:改动的本地推算路径只读取 PLC 缓存、输出只写临时仓库,不改变 PLC 通信写入;交付前仍建议现场连接真实 PLC 确认轨迹随完整轮询刷新

## 待完成

1. 人工确认 HMI 工具栏和属性面板不再出现进度条入口或空白属性行,再操作直接输出、触点后输出、横线选中、单格断线、断路补线、断路插入触点、删除触点和保存重载,并确认离线仿真返回编辑态后不保留绿色轨迹
2. 需要继续使用的早期隐式连线 `1.0` 草稿应在当前编辑器中重新保存为显式 `Wire / Gap` 结构,本项目不提供自动迁移
3. 在确认 PLC 已上电、RS-485 接线正确、COM2 已配置为 Modbus RTU 从站且站号正确后,人工验收自动搜索能找到配置并自动完成正式连接
4. 提交时区分本轮显式连接模型、PLC 自动搜索与工作区其他已有改动,避免混入无关未跟踪文件
1. 代码功能无待完成项;提交前按逻辑完整改动提交当前配置、真机本地推算和 Float32 功能、测试及文档
2. 现场使用真实 PLC 做一次 Float32 真机验收:读取 D10/D11 原值,写入一个有限 Float32,确认一次双寄存器请求,轮询读回显示正确,恢复原值并再次读回确认
3. 现场同时确认 HMI 写入仍正常、本地轨迹随每轮 PLC 缓存刷新、PLC 源 M/D 不被本地梯形图输出改变

## 下一会话起点

1. 先运行 `git status --short`,保留工作区全部现有修改,不回退来源不明的文件
2. PLC 设备条件就绪后先在配置窗口保持正确站号,点击自动搜索,确认进度、停止、未找到提示,以及找到后参数回填和正式连接
3. 人工验收 HMI 时确认“更多控件”菜单只保留页面跳转、报警列表和报警配置;验收梯形图时重点确认选中有线网格仍显示横线,删除后只有目标格变为断路,补线后恢复运行资格
4. 修改代码后运行 `pwsh -NoLogo -NoProfile -File .\scripts\run_qt_tests.ps1 -Configuration Release -Suite All`、Release 主程序构建和 `git diff --check`
1. 先运行 `git status --short`,保留当前应用配置和真机本地推算的全部未提交修改
2. 若继续改配置字段,同步更新默认文件内容、严格加载器、配置测试和 `docs/用户使用/应用配置说明.md`
3. 提交前重新运行 Release 测试、主程序构建和 `git diff --check`

+ 40
- 9
docs/architecture.md Просмотреть файл

@@ -4,7 +4,7 @@

项目是面向设备交付、调试和验证工程师的单工程、单 PLC、多 HMI 页面和多控制逻辑 Qt 桌面编程器,提供 HMI 编辑、结构化梯形图、离线仿真和 Modbus RTU 真机联机。

本项目不生成、编译或下载 PLC 程序,不读取 PLC 内部程序与网络轨迹,不实现完整 XDPPro 指令集,也不支持任意自由画线。当前梯形图模型不包含 T/C 触点、TON 或 CTU/CTD。真机运行时 PLC 内部程序是唯一控制源,PC 只读写工程实际引用的 M/D 地址
本项目不生成、编译或下载 PLC 程序,不读取 PLC 内部程序与网络轨迹,不实现完整 XDPPro 指令集,也不支持任意自由画线。当前梯形图模型不包含 T/C 触点、TON 或 CTU/CTD。真机运行时 PLC 内部程序仍是唯一设备控制源;HMI 和自由监控可以正常读写工程引用的 M/D,本地梯形图只读取 PLC 缓存并推算轨迹,不把线圈或数据指令结果写回 PLC

## 分层与依赖

@@ -27,7 +27,7 @@ main.cpp -> UI + Services + Infrastructure

- `domain` 不依赖 Qt、文件系统或串口
- `services` 执行业务用例和原子编辑,通过接口使用工程存储与 PLC 通信
- `infrastructure` 实现 JSON 工程存储、PLC 缓存和异步 Modbus RTU
- `infrastructure` 实现应用配置与 JSON 加载、PLC 缓存和异步 Modbus RTU
- `ui` 只提交命令并投影模型、轨迹和错误,不直接读写串口或承载业务校验
- `main.cpp` 只组合对象,不实现业务规则

@@ -46,7 +46,7 @@ main.cpp -> UI + Services + Infrastructure

- 进入离线或真机运行后,弹出并最大化运行监控窗口;主编程器窗口保留在后面且不改变布局,运行期间由同一份 `ModePolicy` 禁用编辑入口
- 主窗口不保留运行监控页签,也不创建第二份监控控件;工程对象刷新和运行交互始终指向弹窗中的唯一实例
- 离线时显示可交互 HMI、软件梯形图轨迹和自由监控;真机时显示可交互 HMI 和自由监控,不能显示本地梯形图伪轨迹
- 离线时显示可交互 HMI、软件梯形图轨迹和自由监控;真机时同时显示可交互 HMI、自由监控和根据 PLC 缓存计算的“本地推算轨迹”
- HMI 页面跳转、按钮和数值输入都通过同一份 `HmiNavigationService` 和活动寄存器仓库完成;离线写虚拟 M/D,真机写 PLC,仿真停止或通信不可用时禁止写入
- 运行监控只能通过界面内的返回编辑按钮请求 `MainWindow -> RuntimeModeService` 返回编辑态;运行期间系统窗口关闭路径被忽略,离线时先停止软件扫描,真机时保留 PLC 连接但撤销运行会话;应用退出时允许顶层窗口真正关闭

@@ -67,6 +67,7 @@ Project
- 页面、逻辑和节点使用稳定 ID;名称可修改,数组顺序决定工程树顺序和离线扫描顺序
- 当前编辑页面和当前逻辑是 UI 会话状态,不写入工程
- HMI 控件类型、默认值、绑定规则和运行值类型由无 Qt 依赖的 `hmi_control_registry` 统一描述
- HMI 数值控件通过 `RegisterDataType` 选择 `Int16` 或 `Float32`;底层仓库始终保存原始 16 位 D 字,`Float32Codec` 负责低字/高字组合
- 需要寄存器的 HMI 控件、报警条件和梯形图节点直接保存 M/D 地址;项目不再维护统一 `dataPoints` 表
- `registerComments` 只是 M/D 地址元数据,不保存当前值或引用位置,也不会让地址自动进入 PLC 轮询

@@ -77,7 +78,31 @@ Project
- `validate()` 保证可保存结构合法,允许未绑定控件、待配置节点和未完成网络作为编辑草稿
- `validateForRunning()` 检查运行所需绑定和已启用逻辑;禁用草稿不阻止运行

数量常量集中在 `domain/project_limits.h`,最终业务口径见 `docs/用户使用/数量边界确认方案.md`。领域、服务、JSON 和 UI 入口必须使用同一组常量,不能各自维护数字。
数量的绝对硬上限和默认值集中在 `domain/project_limits.h`。启动时可由应用配置收紧的数量使用同一份 `ProjectLimitSettings`,由 `main.cpp` 注入领域校验、编辑服务、JSON 存储和 UI;任何一层都不能另存一套运行上限。最终业务口径见 `docs/用户使用/数量边界确认方案.md`。

## 应用启动配置

`ApplicationSettingsLoader` 在 Qt 应用创建后、其他业务对象创建前读取可执行文件目录下的 `config/application.ini`。配置只读取一次,不热更新,也不写入工程 JSON。

```text
config/application.ini
|
ApplicationSettingsLoader
|
ApplicationSettings(启动后只读)
|-- ProjectLimitSettings -> Domain / Editor Services / JsonProjectStorage / UI
|-- HmiDefaultSettings -> HmiEditorService / PropertyPanelController
`-- PlcDefaults -> MainWindow -> PlcConnectionDialog
```

- 文件不存在时创建默认文件,本次仍使用代码默认值;已有文件永不被自动覆盖
- 缺失的普通字段使用代码默认值,未知字段忽略,两者都记录到底部输出面板
- 重复字段、版本不兼容、非法 UTF-8、类型错误或越界值会使整份文件失效,所有配置回退为代码默认值
- 配置创建或加载严重失败时主窗口继续创建,启动诊断先进入底部输出面板,再异步弹出一次警告框
- HMI 宽高只决定新建页面的初始尺寸;已有页面继续使用工程 JSON 中保存的尺寸
- PLC 默认参数只用于本次进程第一次打开配置窗口时的初值;用户在窗口中的修改只保留在当前进程,不自动回写 INI

字段、范围和用户操作方式见 `docs/用户使用/应用配置说明.md`。

## 结构化梯形图

@@ -110,6 +135,8 @@ ConditionExpression

`SoftwareLogicExecutor` 按 `controlLogics` 和网络数组顺序扫描已启用逻辑,前面网络的 M/D 写入在同一扫描周期对后面网络可见。执行器只通过寄存器仓库读写 M/D,并仅为边沿触点保留跨扫描输入状态;扫描轨迹按逻辑 ID 分区,避免不同逻辑中的重复节点 ID 相互覆盖。

真机模式由 `OnlineLogicMonitorService` 复用同一执行器,但不会把 PLC 仓库直接交给执行器。每次完整 PLC 轮询后,服务把梯形图实际引用的 M/D 从 PLC 缓存复制到独立的临时 `VirtualRegisterRepository`,再执行一轮本地扫描。线圈、MOVE、ADD 和 SUB 只修改本轮临时值,所以前面网络的本地结果仍可供后面网络使用,但任何本地输出都不会写入 PLC;下一轮重新从最新 PLC 缓存开始。

## 数据源与运行模式

运行界面只访问 `RegisterRepository`。`ActiveRegisterRepository` 根据模式切换实际数据源:
@@ -125,17 +152,21 @@ ConditionExpression
真机:运行监控弹窗 -> ActiveRegisterRepository -> PlcRegisterRepository(cache)
^
PLC <-> PlcCommunicationService <--------------------┘

PlcRegisterRepository(cache) --只读复制--> OnlineLogicMonitorService
-> 临时 VirtualRegisterRepository
-> SoftwareLogicExecutor -> 本地推算轨迹
```

| 模式 | 允许工程编辑 | 寄存器来源 | 软件逻辑执行器 | 进入条件 |
| --- | --- | --- | --- | --- |
| 编辑态 | 是 | 无运行数据源 | 停止 | 运行态先退出 |
| 离线运行 | 否 | 虚拟 M/D | 运行 | 工程通过运行校验 |
| 真机运行 | 否 | PLC 读回缓存 | 停止 | PLC 已连接并完成本次首读 |
| 真机运行 | 否 | HMI 使用 PLC 读回缓存;轨迹使用缓存副本 | 本地只读推算 | PLC 已连接并完成本次首读 |

离线和真机不能直接互切。进入真机不复制或下发离线值,也不显示本地梯形图导通轨迹。
离线和真机不能直接互切。进入真机不复制或下发离线值;本地轨迹从最新 PLC 缓存开始推算,只表示 PC 根据缓存计算出的结果,不是 PLC 内部真实轨迹。

自由监控是会话级诊断工具,不属于 `Project`,不写入 JSON。它通过活动仓库读取和单点写入 M/D;真机写入仍必须经过连接与首读门槛。
自由监控是会话级诊断工具,不属于 `Project`,不写入 JSON。每个监控点可选 `Int16` 或 `Float32`,Float32 批量添加按 2 个 D 偏移并显示实际占用范围;完全重复点拒绝,部分重叠点允许添加但提示。它通过活动仓库读取 M/D 或成对读取 Float32,并通过单字或双字请求写入;真机写入仍必须经过连接与首读门槛。

## Modbus RTU 通信

@@ -143,7 +174,7 @@ PLC <-> PlcCommunicationService <--------------------┘

PLC 配置窗口的自动搜索使用独立 `PlcDiscoveryService`,不复用正式轮询主站,避免搜索失败污染连接状态、缓存和首读资格。搜索固定使用界面当前站号,优先当前串口参数,再异步遍历全部可用 PC 串口和项目允许的 60 组波特率、数据位、校验位及停止位组合;每组参数只读 `D0`,正常响应或合法 Modbus 异常响应都能证明端口、站号和串口帧匹配。找到后必须先释放搜索串口,再把参数交给原有正式连接流程;取消或全部失败时不保留搜索连接。

轮询集合由 HMI、报警、梯形图中的显式 M/D 引用和自由监控地址合并,按区域去重并合并相邻地址。横线、断路和注释不进入轮询。动态修改监控地址时,当前请求按旧快照解析,空闲后再应用新集合。
轮询集合由 HMI、报警、梯形图中的显式 M/D 引用和自由监控地址合并,按区域去重并合并相邻地址。Float32 的起始地址同时作为成对读取元数据传入通信层;拆分 120 字上限时会调整边界,保证两个连续保持寄存器不会分到不同请求。横线、断路和注释不进入轮询。动态修改监控地址时,当前请求按旧快照解析,空闲后再应用新集合。真机本地轨迹只在全部读块成功完成一轮后推算一次,不在单个读块更新时重复扫描。

PLC 缓存只保存最后一次成功读回值。写请求受理后不乐观修改缓存,界面等待后续轮询确认真实值。真机运行资格属于当前连接代次:必须完成完整首读,旧连接的异步回复不能污染重连后的状态。

@@ -156,5 +187,5 @@ PLC 缓存只保存最后一次成功读回值。写请求受理后不乐观修
- 新持久化字段必须同步领域校验、严格 JSON 往返、缺失/非法字段测试和格式说明;是否升级格式版本必须明确决定
- 新数量边界必须进入 `project_limits.h`、数量边界文档和上下界测试
- 新 HMI 类型优先扩展 `hmi_control_registry`,专用交互留在对应服务和图元
- 不重新引入数据点表、自由线段、UI 直连串口或真机执行本地梯形图
- 不重新引入数据点表、自由线段、UI 直连串口,也不得把真机本地梯形图的临时输出写入 PLC
- 自动化测试覆盖领域、服务、JSON 存储和 Fake PLC 通信状态;UI 工作流在交付前人工验收,真实串口、接线和设备 RUN 联动必须现场验证

+ 10
- 14
docs/二次开发/信捷D寄存器与浮点扩展说明.md Просмотреть файл

@@ -1,6 +1,6 @@
# 信捷 D 寄存器与浮点扩展说明

> 这份文档给后续二次开发使用,说明 D 区的基本规则和接入浮点时要注意的地方。当前版本仍按 16 位 D 字寄存器实现
> 这份文档给后续二次开发使用,说明 D 区的基本规则和当前 Float32/REAL 实现边界

## 1. D 寄存器到底是什么

@@ -8,7 +8,7 @@

- 按整数解释时,单个 D 的范围是 `-32768~32767`
- PLC 程序、HMI 或上位机决定这些位按什么类型解释
- 当前软件的 `RegisterRepository` 按单个有符号 16 位字读写 D
- 当前软件的 `RegisterRepository` 按原始 16 位字保存 D;HMI 和自由监控通过 `RegisterDataType` 选择解释方式,梯形图普通指令仍按单个有符号 16 位字执行

所以,浮点数可以放进 D 区,但不能只占一个 D。

@@ -39,24 +39,20 @@ Modbus 把每个 D 当作一个 16 位保持寄存器:

## 4. 当前软件边界

当前版本支持:
当前版本支持:

- HMI 数值控件绑定单个 D 字
- 自由监控按有符号 16 位显示和写入
- HMI 数值控件选择 `Int16` 或 `Float32`
- 自由监控选择 `Int16` 或 `Float32`;Float32 批量地址步长为 2,重叠点允许但会提示
- `Float32` 输入支持普通小数和科学计数法,拒绝 NaN、无穷值和 Float32 溢出
- 梯形图的比较、MOVE、ADD/SUB 按 16 位 D 值运行
- PLC 轮询按单个 D 地址收集和合并
- PLC 轮询按单个 D 地址收集和合并,并保证 Float32 的两个字不会跨读请求拆开
- Float32 写入使用一次写两个连续保持寄存器;PLC 缓存等待轮询读回,不做乐观修改

不要为了接入浮点,直接把底层寄存器仓库改成 `float`。底层应该继续保存原始 16 位字,类型转换放在上层的类型解释或编解码模块中
不要为了接入浮点,直接把底层寄存器仓库改成 `float`。底层继续保存原始 16 位字,类型转换集中在 `Float32Codec` 和上层服务

## 5. 后续接入建议

如果以后确实需要温度、压力、流量等带小数的数据,建议按下面顺序做:

1. 先支持 `Float32/REAL`,暂时不做 `Float64/LREAL`
2. 给绑定增加数据类型和占用字数,同时保留起始 D 地址
3. 增加连续地址占用和重叠校验
4. 让 HMI、自由监控、报警和离线仿真都使用同一个浮点编解码规则
5. Modbus 一次读取或写入完整的两个 D,并补充字序、边界和断线测试
后续增加双字、四字或 `Float64/LREAL` 时,复用“类型决定占用字数、连续读写、统一编解码、重叠刷新”的流程。新类型必须增加独立编解码、地址边界、轮询分块和写入请求测试。只有需求明确时才扩展浮点梯形图指令或浮点报警,不能修改现有普通整数指令语义。

只有当浮点数据真的需要参与梯形图比较、报警或运算时,才继续扩展浮点逻辑指令。不要只做一个浮点显示控件,却让其他模块仍按 16 位整数处理同一组 D。



+ 20
- 4
docs/工程格式说明.md Просмотреть файл

@@ -56,14 +56,30 @@
| --- | --- | --- |
| `button` | M 地址或 `null` 草稿 | `buttonOperation` |
| `indicator` | M 地址或 `null` 草稿 | 无 |
| `numericDisplay` | D 地址或 `null` 草稿 | |
| `numericInput` | D 地址或 `null` 草稿 | |
| `numericDisplay` | D 地址或 `null` 草稿 | `dataType` |
| `numericInput` | D 地址或 `null` 草稿 | `dataType` |
| `label` | 必须为 `null` | 无 |
| `pageJump` | 必须为 `null` | `targetPageId` |
| `alarmList` | 必须为 `null` | 无 |

`type` 只接受表中列出的控件类型。其他类型按严格 `1.0` 规则直接拒绝加载,不做兼容迁移。

数值显示和数值输入控件必须额外保存 `dataType`,值只能是 `int16` 或 `float32`:

```json
{
"id": "temperature",
"type": "numericDisplay",
"bounds": {"x": 20, "y": 20, "width": 160, "height": 40},
"text": "温度",
"binding": {"area": "D", "index": 10},
"properties": {},
"dataType": "float32"
}
```

`int16` 只占起始 D 一个字,沿用现有有符号 16 位逻辑。`float32` 使用 IEEE-754 单精度,占用起始 D 和下一个 D,低地址保存低字、高地址保存高字;起始地址只能是 `D0~D3999`。数值控件之间允许相同起始地址和相同类型重复绑定,部分重叠或类型冲突会被拒绝。工程格式仍固定为严格 `1.0`,数值控件缺少 `dataType` 时加载失败。

按钮操作字符串为 `setOn`、`setOff`、`toggle` 或 `momentaryOn`。PageJump 使用目标页面稳定 ID,不使用名称或数组位置:

```json
@@ -168,9 +184,9 @@ AlarmList 只负责显示项目级当前报警,不保存独立触发逻辑,
{"type": "arithmetic", "operation": "add", "left": {"kind": "register", "address": {"area": "D", "index": 2}}, "right": {"kind": "constant", "value": 1}, "destination": {"area": "D", "index": 2}}
```

当前 `LogicNodeConfig` 只接受 `contact`、`edgeContact`、`coil`、`compare`、`move` 和 `arithmetic`。`timerContact`、`counterContact`、`ton`、`counter` 等未支持类型会使工程加载失败,不会被忽略或自动迁移。触点模式为 `normallyOpen / normallyClosed`,边沿模式为 `rising / falling`,线圈模式为 `normal / set / reset`;比较运算支持 `equal / notEqual / lessThan / lessThanOrEqual / greaterThan / greaterThanOrEqual`,算术运算使用 `add / subtract`。上升沿和下降沿在离线执行器中产生一次扫描脉冲。
当前 `LogicNodeConfig` 只接受 `contact`、`edgeContact`、`coil`、`compare`、`move` 和 `arithmetic`。`timerContact`、`counterContact`、`ton`、`counter` 等未支持类型会使工程加载失败,不会被忽略或自动迁移。触点模式为 `normallyOpen / normallyClosed`,边沿模式为 `rising / falling`,线圈模式为 `normal / set / reset`;比较运算支持 `equal / notEqual / lessThan / lessThanOrEqual / greaterThan / greaterThanOrEqual`,算术运算使用 `add / subtract`。上升沿和下降沿在离线执行器及真机本地推算中产生一次扫描脉冲。

MOVE、ADD 和 SUB 只能把结果写入 D 地址,源操作数和算术左右操作数可以是有符号 16 位常量或 D 地址。指令仅在所在网络条件成立时执行;ADD/SUB 使用更宽中间类型计算,结果超出 `-32768~32767` 时饱和到对应边界,并在离线运行轨迹中记录 `overflow: true`,不会中止扫描。
MOVE、ADD 和 SUB 只能把结果写入 D 地址,源操作数和算术左右操作数可以是有符号 16 位常量或 D 地址。指令仅在所在网络条件成立时执行;ADD/SUB 使用更宽中间类型计算,结果超出 `-32768~32767` 时饱和到对应边界,并在本地运行轨迹中记录 `overflow: true`,不会中止扫描。真机本地推算时这些结果只写临时仓库,不写 PLC。

## 校验与读写



+ 1
- 1
docs/开发顺序.md Просмотреть файл

@@ -94,7 +94,7 @@
## 10. 完成运行模式集成

- 离线模式连接虚拟寄存器并启动软件逻辑执行器。
- 真机模式连接 PLC 寄存器缓存并停止软件逻辑执行器
- 真机模式连接 PLC 寄存器缓存;每轮完整轮询后把引用的 M/D 复制到临时仓库,仅推算本地梯形图轨迹,不向 PLC 写本地输出
- 切换到真机模式时先读取 PLC 数据,不自动下发离线仿真值。

完成标准:两种运行模式可安全切换,数据来源和界面状态不会混淆。


+ 12
- 9
docs/测试约定.md Просмотреть файл

@@ -16,17 +16,18 @@

| 测试目标 | 主要契约 |
| --- | --- |
| `domain_tests` | 地址、模型、数量边界和运行前校验 |
| `domain_tests` | 地址、模型、数量边界、Float32 编解码和运行前校验 |
| `application_settings_tests` | 默认配置创建、严格 INI 校验、缺失与未知字段、版本和整份回退 |
| `alarm_service_tests` | 报警定义和运行记录生命周期 |
| `hmi_editor_service_tests` | HMI 编辑、原子删除、历史和导航 |
| `hmi_editor_service_tests` | HMI 编辑、原子删除、历史、导航和 Float32 数值控件读写 |
| `logic_editor_service_tests` | 结构化梯形图编辑、归一化和历史 |
| `offline_simulation_service_tests` | 梯形图扫描语义、跨扫描状态和故障处理 |
| `offline_simulation_service_tests` | 梯形图扫描语义、真机缓存副本推算、只读隔离、跨扫描状态和故障处理 |
| `project_management_tests` | JSON 往返、非法文件和工程保存状态 |
| `register_monitor_service_tests` | 监视地址、读写和活动仓库 |
| `runtime_mode_service_tests` | 编辑、离线和真机状态转换 |
| `runtime_panel_controller_tests` | 离线扫描排队回调和编辑态轨迹清理 |
| `register_monitor_service_tests` | 监视地址、Int16/Float32 读写、批量步长、重叠提示和活动仓库 |
| `runtime_mode_service_tests` | 编辑、离线、真机只读轨迹和仓库切换 |
| `runtime_panel_controller_tests` | 本地扫描排队回调和编辑态轨迹清理 |
| `plc_connection_dialog_tests` | PLC 端口刷新及自动搜索开始、取消、进度和参数回填 |
| `plc_runtime_tests` | PLC 缓存、轮询、错误恢复和 Fake gateway |
| `plc_runtime_tests` | PLC 缓存、完整轮询通知、成对轮询边界、错误恢复和 Fake gateway |

同一规则只在成本最低、失败定位最清楚的层验证:

@@ -43,7 +44,7 @@
pwsh -NoLogo -NoProfile -File .\scripts\run_qt_tests.ps1 -Configuration Release
```

`app/tests/tests.pro` 只聚合上述 11 个功能测试目标。测试构建输出放在 `build/`,不写入 `app/`。
`app/tests/tests.pro` 只聚合上述 12 个功能测试目标。测试构建输出放在 `build/`,不写入 `app/`。

## 性能测试

@@ -87,8 +88,10 @@ pwsh -NoLogo -NoProfile -File .\scripts\run_qt_tests.ps1 -Configuration Release

## 提交前检查

1. 运行 11 个 Release 功能测试目标
1. 运行 12 个 Release 功能测试目标
2. 单独运行 `performance_tests` 并保留基准输出
3. 构建 Qt Release 主程序
4. 按风险决定是否重新执行真实 PLC 实测;未重测时引用已有验收记录并说明原因
5. 运行 `git diff --check`

Float32 真机验收还要确认:写入一次发送两个连续保持寄存器,轮询读回后界面才刷新;测试结束恢复 D 起始字和下一个 D 的原值并再次读回确认。

+ 90
- 0
docs/用户使用/应用配置说明.md Просмотреть файл

@@ -0,0 +1,90 @@
# 应用配置说明

应用配置文件用于调整少量用户体验型数量、新建 HMI 页面的默认尺寸,以及 PLC 配置窗口的串口初值。工程内容仍保存在 JSON 工程文件中,两类文件互不替代。

## 文件位置与生效方式

配置文件固定在程序可执行文件同级目录下:

```text
config/application.ini
```

- 文件统一使用 UTF-8,可带或不带 BOM
- 程序每次启动只读取一次,运行期间不热更新
- 修改后必须完全退出并重新启动程序
- 程序不会覆盖已有配置,也不会把界面里的临时修改自动写回文件
- 字段名和分区名区分大小写,应按示例原样填写

首次运行找不到文件时,程序会自动创建 `config` 目录和默认文件,本次运行直接使用代码默认值,并在主界面底部输出创建结果。创建目录或文件失败时,程序仍会使用默认值启动,同时在底部输出原因并弹出一次警告。

## 默认配置

```ini
[Config]
Version=1

[ProjectLimits]
MaxHmiPages=32
MaxHmiControlsPerPage=128
MaxAlarmDefinitions=256
MaxControlLogics=32
MaxRungsPerLogic=256
MaxOutputMessages=1000

[HmiDefaults]
DefaultPageWidth=800
DefaultPageHeight=400

[PlcDefaults]
PortName=COM3
ServerAddress=1
BaudRate=9600
DataBits=8
Parity=2
StopBits=1
```

## 字段说明

| 分区与字段 | 默认值 | 允许值 | 实际作用 |
| --- | ---: | --- | --- |
| `Config.Version` | 1 | 当前只接受 `1` | 配置格式版本,必填 |
| `ProjectLimits.MaxHmiPages` | 32 | `1~32` | 单工程 HMI 页面上限 |
| `ProjectLimits.MaxHmiControlsPerPage` | 128 | `1~128` | 单页 HMI 控件上限 |
| `ProjectLimits.MaxAlarmDefinitions` | 256 | `0~256` | 单工程报警定义上限,`0` 表示禁止添加报警 |
| `ProjectLimits.MaxControlLogics` | 32 | `1~32` | 单工程控制逻辑组上限 |
| `ProjectLimits.MaxRungsPerLogic` | 256 | `0~256` | 单组逻辑网络上限,`0` 表示逻辑组只能保持空网络 |
| `ProjectLimits.MaxOutputMessages` | 1000 | `1~1000` | 主界面底部输出保留条数 |
| `HmiDefaults.DefaultPageWidth` | 800 | `320~1600` | 新建 HMI 页面的默认宽度,像素 |
| `HmiDefaults.DefaultPageHeight` | 400 | `200~800` | 新建 HMI 页面的默认高度,像素 |
| `PlcDefaults.PortName` | COM3 | 非空,最多 128 个 UTF-8 字节 | 打开 PLC 配置窗口时优先选择的端口 |
| `PlcDefaults.ServerAddress` | 1 | `1~247` | Modbus 从站地址 |
| `PlcDefaults.BaudRate` | 9600 | `9600/19200/38400/57600/115200` | 串口波特率 |
| `PlcDefaults.DataBits` | 8 | `7/8` | 串口数据位 |
| `PlcDefaults.Parity` | 2 | `0/2/3` | `0` 无校验、`2` 偶校验、`3` 奇校验 |
| `PlcDefaults.StopBits` | 1 | `1/2` | 串口停止位 |

页面宽高配置只影响之后新建的页面。已经保存在工程 JSON 中的页面继续使用自身宽高,不会因为修改 INI 被批量改变。

PLC 默认配置只负责填充 PLC 配置窗口。若 `PortName` 对应串口当前不存在,端口列表仍只显示 Windows 实际检测到的可用串口,不会人为加入一个无效端口。用户在窗口里搜索或修改参数后,结果只在本次程序运行期间保留。

## 校验与回退

普通字段缺失时,该字段使用代码默认值,其他合法字段继续生效;未知字段会被忽略。这两种情况都会在主界面底部输出提示,但不会弹警告。

以下情况属于严重错误:

- `Config.Version` 缺失或不是 `1`
- 任意整数包含其他字符、低于最小值或超过代码硬上限
- 波特率、校验位等枚举值不受支持
- 任意字段重复出现,包括在重复分区中再次出现同名字段
- 文件不是有效 UTF-8、INI 行格式错误或文件无法读取

出现任一严重错误时,整份配置都不生效。本次运行全部使用代码默认值,主界面底部会输出字段、原值、允许范围和最终处理结果,并弹出一次警告;程序不会修改原配置文件。

## 调低上限后的工程

程序加载工程时使用与编辑入口相同的启动配置。已有工程超过新上限时会整体拒绝加载,错误信息会给出超限数组、实际数量和当前上限;当前工程不会被部分替换,原 JSON 文件也不会被修改。

全工程 HMI 控件上限 2048、全工程网络上限 2048、M/D 地址范围、16 位数值范围、梯形图结构、表达式安全限制、Modbus 通信限制和工程文件容量仍是代码硬限制,不允许通过 INI 修改。

+ 29
- 13
docs/用户使用/数量边界确认方案.md Просмотреть файл

@@ -1,8 +1,23 @@
# 数量边界确认方案

这份表是本项目最终采用的数量规则。表里的数字是确定值,不是建议值。达到上限还能正常使用,再多一个就会被软件拒绝;只有日志这类临时信息会自动删除最旧内容。
这份表是本项目最终采用的数量规则。表里的最大值是代码绝对硬上限,不允许配置突破;其中 6 个直接影响日常使用体验的数量允许用户通过 `config/application.ini` 进一步调低。达到本次运行的有效上限还能正常使用,再多一个就会被软件拒绝;只有日志这类临时信息会自动删除最旧内容。

## 1. 必须做硬限制的项目
可配置数量如下,缺失字段时使用默认值;详细加载规则见 `docs/用户使用/应用配置说明.md`。

| 配置字段 | 默认值 | 配置最小值 | 代码硬上限 |
| --- | ---: | ---: | ---: |
| `MaxHmiPages` | 32 | 1 | 32 |
| `MaxHmiControlsPerPage` | 128 | 1 | 128 |
| `MaxAlarmDefinitions` | 256 | 0 | 256 |
| `MaxControlLogics` | 32 | 1 | 32 |
| `MaxRungsPerLogic` | 256 | 0 | 256 |
| `MaxOutputMessages` | 1000 | 1 | 1000 |

全工程 HMI 控件数量固定为 2048,全工程网络数量固定为 2048,均不对用户开放。因为可配置的单页控件上限不可能超过 128、单组网络上限不可能超过 256,所以它们与全工程硬上限的关联关系始终成立,不需要再增加可配置字段。

HMI 页面宽度 `320~1600`、高度 `200~800` 仍是结构安全硬范围;`DefaultPageWidth` 和 `DefaultPageHeight` 只允许在该范围内设置新建页面默认尺寸,不改变已有页面,也不改变宽高硬边界。

## 1. 最终数量与安全边界

| 类别 | 项目 | 最小值 | 最大值 | 超出后怎么处理 | 为什么必须限制 |
| --- | --- | ---: | ---: | --- | --- |
@@ -10,8 +25,8 @@
| PLC 软元件 | D 地址 | 0 | 4000 | 拒绝保存、加载或连接使用 | 项目需求已经明确限定 |
| 数值 | D 字、比较值、阈值、普通常量 | -32768 | 32767 | 编辑和加载时拒绝越界值,运算溢出时压到边界值 | D 是有符号 16 位数据 |
| 工程文件 | 单个 JSON 文件大小 | 0 | 16 MiB | 读取 JSON 前直接拒绝 | 防止超大文件一次性占满内存 |
| HMI | 页面数量 | 0 | 32 | 第 33 个页面不能创建或加载 | 控制工程树和运行页面的内存开销 |
| HMI | 单页控件数量 | 0 | 128 | 第 129 个控件不能创建或加载 | 保证编辑画布和运行刷新可控 |
| HMI | 页面数量 | 0 | 32 | 超过配置上限或第 33 个页面不能创建或加载 | 控制工程树和运行页面的内存开销 |
| HMI | 单页控件数量 | 0 | 128 | 超过配置上限或第 129 个控件不能创建或加载 | 保证编辑画布和运行刷新可控 |
| HMI | 全工程控件数量 | 0 | 2048 | 第 2049 个控件不能创建或加载 | 限制工程快照、JSON 和跨页面总刷新工作量 |
| HMI | 页面宽度 | 320 | 1600 | 拒绝保存、加载或编辑 | 当前工具只面向本机 HMI 编辑,限制在可合理查看的桌面页面范围内 |
| HMI | 页面高度 | 200 | 800 | 拒绝保存、加载或编辑 | 当前工具只面向本机 HMI 编辑,限制在可合理查看的桌面页面范围内 |
@@ -19,13 +34,13 @@
| HMI | 单控件扩展属性 | 0 对 | 64 对 | 第 65 对属性不能加载或保存 | 防止属性对象无限增长 |
| HMI | 字号 | 6 | 72 | 拒绝保存、加载或编辑 | 控制文字可读性和控件布局开销 |
| HMI | 控件属性文本输入长度 | 0 字符 | 12 个字符 | 编辑界面限制继续输入 | 保持 HMI 控件属性文本紧凑,避免编辑和运行显示溢出 |
| 报警 | 报警定义数量 | 0 | 256 | 第 257 条报警不能创建或加载 | 报警每个刷新周期都要判断 |
| 报警 | 报警定义数量 | 0 | 256 | 超过配置上限或第 257 条报警不能创建或加载 | 报警每个刷新周期都要判断 |
| 报警 | 报警列表标题输入长度 | 0 字符 | 12 个字符 | 编辑界面限制继续输入 | 标题需要在表头为翻页控件保留空间,显示不下时使用省略号 |
| 报警 | 单条报警文本输入长度 | 1 字符 | 20 个字符 | 编辑界面限制继续输入 | 报警信息按单行显示并在宽度不足时使用省略号 |
| 报警 | 单页同时可见行数 | 1 行 | 5 行 | 超出后通过表头按钮翻页,活动记录不丢失 | 控制默认控件高度并避免少量报警产生大块留白 |
| 注释 | M/D 软元件注释数量 | 0 | 8002 | 超出后拒绝保存或加载 | M0~M4000 和 D0~D4000 每个地址最多一条,共 8002 条 |
| 控制逻辑 | 控制逻辑数量 | 0 | 32 | 第 33 组不能创建或加载 | 控制工程树和扫描工作量 |
| 控制逻辑 | 每组逻辑的网络数量 | 0 | 256 | 第 257 个网络不能创建或加载 | 每次软件扫描都要执行这些网络 |
| 控制逻辑 | 控制逻辑数量 | 0 | 32 | 超过配置上限或第 33 组不能创建或加载 | 控制工程树和扫描工作量 |
| 控制逻辑 | 每组逻辑的网络数量 | 0 | 256 | 超过配置上限或第 257 个网络不能创建或加载 | 每次软件扫描都要执行这些网络 |
| 控制逻辑 | 全工程网络数量 | 0 | 2048 | 第 2049 个网络不能创建或加载 | 限制离线扫描、编辑快照和工程文件总工作量 |
| 单个网络 | 条件表达式节点和叶子总数 | 0 | 1024 | 超出后编辑操作回退,JSON 加载失败 | 防止单个网络过度复杂 |
| 单个网络 | 表达式嵌套深度 | 1 层 | 12 层 | 第 13 层在解析时直接拒绝 | 控制递归求值和画布布局深度 |
@@ -43,7 +58,7 @@
| 字符串 | HMI 属性值 | 0 字节 | 1024 个 UTF-8 字节 | 拒绝保存或加载 | 属性值可能保存显示配置,但不能无限增长 |
| 自由监控 | 去重后的监控地址 | 0 | 64 | 第 65 个监控地址不能加入 | 这是用户临时监控区,不应拖慢 PLC 轮询 |
| 编辑历史 | 可撤销记录 | 0 | 30 | 新操作加入后自动删除最旧记录 | 历史记录会保存工程快照,占用内存 |
| 输出面板 | 日志条数 | 0 | 1000 | 加新日志前自动删除最旧一条 | 日志只用于查看近期状态 |
| 输出面板 | 日志条数 | 0 | 1000 | 达到配置上限后,加新日志前自动删除最旧一条 | 日志只用于查看近期状态 |
| PLC 通信 | 站号 | 1 | 247 | 不允许发起连接 | 248~254 虽然信捷手册可表示,但属于 Modbus 保留地址,本项目不使用 |
| PLC 通信 | 波特率 | 9600 | 115200 | 只接受 9600、19200、38400、57600、115200 | 只开放本项目已经验证的串口档位 |
| PLC 通信 | 数据位 | 7 | 8 | 只接受 7 或 8 | 串口帧格式必须明确 |
@@ -82,19 +97,20 @@
- 同一手册 PDF 第 28 页说明 D 是有符号 16 位寄存器,因此本项目的 D 字、比较值和普通常量统一限制为 `-32768~32767`
- 同一手册 PDF 第 274 页说明 Modbus RTU 数据区最多 252 字节,站号最大可到 254。本项目单读块固定为 120 个,站号固定为 `1~247`
- 同一手册 PDF 第 311 页给出 Modbus 重试次数 `0~5`、回复超时 `0~65535`。本项目采用重试 `0~5`,并把超时进一步收紧为 `100~30000 ms`,避免无限等待
- 页面、控件、报警、逻辑、JSON 大小、日志等数字在信捷手册里没有规定,所以按本软件的内存、绘制、扫描和通信成本给出固定安全预算
- 页面、控件、报警、逻辑、JSON 大小、日志等数字在信捷手册里没有规定,所以按本软件的内存、绘制、扫描和通信成本给出绝对安全预算;其中 6 个体验型数量可由用户在安全预算内调低

## 4. 用户实际会看到什么

- 正常编辑时,达到页面、控件、报警、逻辑或网络上限后,软件会直接提示具体上限,不会先添加再留下坏工程
- 手工改 JSON 绕过界面也没用,加载时会重新检查文件大小、数组数量、字符串长度、页面尺寸和表达式深度
- 正常编辑时,达到页面、控件、报警、逻辑或网络的当前配置上限后,软件会直接提示具体上限,不会先添加再留下坏工程
- 手工改 JSON 绕过界面也没用,加载时会按同一份启动配置重新检查数组数量,并检查文件大小、字符串长度、页面尺寸和表达式深度
- 用户调低上限后,已有工程一旦超限会整体拒绝加载,错误会给出超限项目、实际数量和当前配置上限,不会加载一半或修改原文件
- PLC 参数不只在对话框里限制,真正连接前还会再检查一次
- 超过 1000 条输出日志时,只删除最旧日志,不影响工程和 PLC 数据
- 超过当前 `MaxOutputMessages` 时,只删除最旧日志,不影响工程和 PLC 数据
- PLC 写请求正在等待回复时,新写请求会被拒绝,等前一笔完成后再操作即可

## 5. 本次验收结果

- Release 主程序已经重新构建通过,数量边界相关的 10 组自动化测试全部通过
- 2026-08-25 外部应用配置接入后,12 个 Release 功能测试目标全部通过;覆盖动态领域上限、编辑入口、严格配置加载和调低上限后的 JSON 原子拒绝
- 2026-08-19 使用真实 PLC 在 `COM3、9600、8E1、站号 1` 下完成读写验证:先读取 `D4000=0`,写入 `1` 后读回 `1`,再恢复并读回 `0`
- 真机验证没有下载 PLC 程序,测试结束后 `D4000` 已恢复原值
- 2026-08-21 用户使用 `json/motor_forward_reverse.json` 完成真实 PLC RUN 联动手动验收,确认 HMI 正转启动、反转启动、停止和运行状态反馈正常


Загрузка…
Отмена
Сохранить