Procházet zdrojové kódy

docs: 更新系统架构与开发进度

main
suyu před 1 měsícem
rodič
revize
1145296aee
4 změnil soubory, kde provedl 183 přidání a 246 odebrání
  1. +2
    -0
      docs/XDH-60T4-E指令与Modbus要点.md
  2. +58
    -137
      docs/ai/handoff.md
  3. +100
    -104
      docs/architecture.md
  4. +23
    -5
      docs/开发顺序.md

+ 2
- 0
docs/XDH-60T4-E指令与Modbus要点.md Zobrazit soubor

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

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

当前软件已实现串口参数配置、异步连接、地址收集、相邻地址合并轮询、PLC 缓存、单点写入、首次读取约束和错误反馈。自动化测试使用替身网关和缓存仓库验证,不等同于真实串口与 PLC 验证。

1. 用官方工具把 PLC 的 COM2 配置为 Modbus RTU 从站,设置并记录站号与串口参数。
2. 使用 USB 转 RS-485 将 PC 与 COM2 的 A/B 正确连接。
3. PC 发送 `0x01` 读取 `M0`,确认能得到合法 RTU 响应。


+ 58
- 137
docs/ai/handoff.md Zobrazit soubor

@@ -1,139 +1,60 @@
# Current Handoff

- Goal: 完成综合平台编程器的工程管理和后续编辑功能。
- Branch: `main`。
- Current status: 已完成开发顺序第 8 步,HMI、虚拟 M/D 和固定周期软件逻辑执行器的离线闭环已完成。
- Changed files:
- `app/integrated_platform.pro`
- `app/src/main.cpp`
- `app/src/ui/main_window.h`
- `app/src/ui/main_window.cpp`
- `app/src/ui/main_window.ui`
- `AGENTS.md`
- `docs/C++代码规范.md`
- `docs/architecture.md`
- `app/src/domain/.gitkeep`
- `app/src/services/.gitkeep`
- `app/src/infrastructure/.gitkeep`
- `app/tests/.gitkeep`
- `scripts/build_and_run_qt.ps1`
- `app/src/domain/register_address.h`
- `app/src/domain/register_address.cpp`
- `app/src/domain/register_repository.h`
- `app/src/domain/register_repository.cpp`
- `app/src/domain/hmi_model.h`
- `app/src/domain/hmi_model.cpp`
- `app/src/domain/control_logic_model.h`
- `app/src/domain/control_logic_model.cpp`
- `app/src/domain/project_model.h`
- `app/src/domain/project_model.cpp`
- `app/src/domain/runtime_state.h`
- `app/src/domain/runtime_state.cpp`
- `app/src/domain/project_storage.h`
- `app/src/services/project_service.h`
- `app/src/services/project_service.cpp`
- `app/src/services/hmi_editor_service.h`
- `app/src/services/hmi_editor_service.cpp`
- `app/src/services/hmi_runtime_service.h`
- `app/src/services/hmi_runtime_service.cpp`
- `app/src/services/runtime_mode_service.h`
- `app/src/services/runtime_mode_service.cpp`
- `app/src/infrastructure/json_project_storage.h`
- `app/src/infrastructure/json_project_storage.cpp`
- `app/tests/domain_tests.pro`
- `app/tests/domain_tests.cpp`
- `app/tests/project_management_tests.pro`
- `app/tests/project_management_tests.cpp`
- `app/tests/hmi_editor_service_tests.pro`
- `app/tests/hmi_editor_service_tests.cpp`
- `app/tests/runtime_mode_service_tests.pro`
- `app/src/services/software_logic_executor.h`
- `app/src/services/software_logic_executor.cpp`
- `app/src/services/offline_simulation_service.h`
- `app/src/services/offline_simulation_service.cpp`
- `app/tests/offline_simulation_service_tests.pro`
- `app/tests/offline_simulation_service_tests.cpp`
- `app/tests/runtime_mode_service_tests.cpp`
- `app/tests/main_window_tests.pro`
- `app/tests/main_window_tests.cpp`
- `app/src/ui/hmi_editor_widget.h`
- `app/src/ui/hmi_editor_widget.cpp`
- `app/src/services/logic_editor_service.h`
- `app/src/services/logic_editor_service.cpp`
- `app/src/ui/logic_editor_widget.h`
- `app/src/ui/logic_editor_widget.cpp`
- `app/tests/logic_editor_service_tests.pro`
- `app/tests/logic_editor_service_tests.cpp`
- `app/tests/runtime_mode_service_tests.pro`
- Decisions made:
- Qt 应用源码位于 `app/`,构建输出位于被忽略的 `build/`。
- 使用 qmake、Qt Widgets、Qt SerialBus 和 MinGW 8.1。
- 主窗口只承载基础界面,不承担 PLC 通信或控制逻辑。
- 简单函数、构造函数和析构函数不添加重复性注释。
- 生产代码按 `UI -> Services -> Domain` 的方向依赖;基础设施实现领域接口。
- 领域模型仅使用 C++17 标准库,不依赖 Qt UI、串口或 Modbus。
- M 地址只承载布尔值,D 地址只承载带符号 16 位值,地址范围统一为 `0~4000`。
- `RegisterRepository` 统一虚拟寄存器和真实 PLC 缓存的访问契约。
- `ProjectStorage` 是领域层的工程文件存储契约,具体 JSON 和 Qt 文件 API 位于基础设施层。
- `ProjectService` 负责工程路径、修改状态和新建保存加载流程;加载成功后才替换当前工程。
- 工程文件格式版本当前为 `1.0`,保存使用 `QSaveFile` 原子提交。
- 控制逻辑节点使用 `std::variant` 组合触点、线圈和数值比较的独立配置类型。
- 当前控制逻辑范围不包含定时器;不得因为设备手册或旧开发计划提前加入未确认功能。
- 新增逻辑节点时增加独立配置类型,不向通用节点结构堆叠无关可选字段。
- 测试按业务风险分级 优先覆盖领域规则 服务闭环 工程存储和通信异常 不为简单访问器堆叠单测。
- 运行态之间不可直接切换;真机运行态必须先完成 PLC 初次读取,且不运行软件逻辑执行器。
- 主窗口采用工程树、中央编辑标签页、属性区、输出区和状态栏组成的可停靠工具布局。
- 编辑、离线运行和真机运行共用一个互斥模式入口,切换规则由服务层调用领域状态机。
- `RuntimeModeService` 直接复用领域模式、策略和结果类型,不维护一比一的服务层副本。
- 运行态禁用工程树和属性编辑区,中央页面保持可见供后续 HMI 运行画面使用。
- 当前尚未接入 PLC 通信,真机运行入口会按领域规则提示先连接并完成首次读取。
- 项目代码不使用自定义命名空间,分层由目录、接口和依赖方向表达。
- `HmiEditorService` 负责页面控件的添加、删除、移动和属性修改,画布项只保存控件标识。
- 控件拖动必须限制在页面内,控件标识在页面内唯一;未绑定控件可作为草稿保存,但不得进入离线运行。
- 新增控件先以未绑定状态进入编辑器,避免自动分配地址造成误绑定。
- 离线运行的按钮采用点击切换 M 位语义,数值输入写入一个带符号 16 位 D 字。
- `HmiRuntimeService` 仅通过 `RegisterRepository` 访问寄存器,不依赖虚拟寄存器、串口或 Modbus。
- 控制逻辑使用梯形图层级:逻辑包含网络,网络包含串联条件级,条件级内部包含并联条件,输出线圈固定在右侧。
- 条件级之间执行 AND,同级并联条件执行 OR;状态保持由常闭停止与“常开启动 OR 常开运行反馈”串联后驱动运行线圈。
- `LogicEditorService` 负责网络、串联条件、并联条件、输出线圈、属性和删除操作。
- `LogicEditorWidget` 自动布局母线、支路和节点,不允许自由拖动或手动画线,不保存节点坐标。
- 控制逻辑编辑器不访问 HMI、寄存器仓库或 PLC,工程格式版本保持 `1.0`。
- 新增 HMI 控件立即进入工程内存但默认未绑定,属性区明确显示“未绑定”,不再以 `M0/D0` 伪装默认绑定。
- 新增梯形图节点立即进入工程内存,但 `configured=false`;画布显示 `< M 地址 >`、`< D 地址 >` 或 `< 常量 >`,点击应用逻辑属性后才成为已配置节点。
- `Project::validate()` 是草稿保存和加载校验,允许未绑定 HMI、待配置节点及未完成网络;`Project::validateForRunning()` 额外校验可运行完整性。
- `RuntimeModeService` 依赖 `ProjectService` 的只读工程,在进入离线运行态前执行完整性校验,拒绝未配置或未完成的工程。
- JSON 节点新增 `configured` 字段;缺失字段的旧版 `1.0` 工程按历史逻辑视为已配置,保持兼容读取。
- 梯形图 UI 已按 LAD 可读性优化:普通线圈采用标准 `( )` 形状,导线连接元件端子而不穿透符号,比较条件显示为带数据类型和上下操作数的指令块。
- 软件逻辑执行器按逻辑、网络顺序扫描,级内 OR、级间 AND,前面网络写入对后面网络同周期可见。
- 普通线圈每周期写结果,置位/复位线圈只在网络成立时写入;同一地址混用不同线圈模式会在启动前被拒绝。
- 离线仿真以 50 ms 为目标扫描周期,启动时复制逻辑快照并清空虚拟 M/D,退出时先停止执行器再返回编辑态。
- 仿真状态使用 `Stopped`、`Running`、`Faulted`,记录扫描次数和逻辑/网络/节点错误上下文;故障后 HMI 保持最终值可见但禁止写入。
- Validation run and results:
- 在 `build/baseline/` 执行 qmake 与 `mingw32-make -j2`,构建成功。
- 已启动 `integrated_platform.exe` 并正常退出。
- 已通过 `scripts/build_and_run_qt.ps1` 完成 Debug 构建和启动验证。
- 新增分层目录后再次完成 Debug 构建和启动验证。
- 已确认构建目录、临时目录和本地草稿文档被 `.gitignore` 忽略。
- 在 `build/core-model/` 重新执行 qmake 和 `mingw32-make -j2`,应用构建成功。
- 在 `build/domain-tests/` 构建并运行领域测试,输出 `domain tests passed`。
- 控制逻辑节点配置重构后再次构建应用和领域测试,均通过。
- 在 `build/project-management-tests/` 构建并运行工程管理测试,输出 `project management tests passed`。
- 工程管理加入应用后重新执行 Qt 应用构建,构建成功。
- 在 `build/main-window/` 构建主窗口和运行模式服务,Qt 5.15.2 Debug 构建成功。
- 运行模式服务测试输出 `runtime mode service tests passed`。
- 主窗口模式工作流测试输出 `main window tests passed`。
- 使用当前工作树重新构建并运行领域测试,输出 `domain tests passed`。
- 启动 Debug 应用完成窗口响应冒烟检查,程序正常启动和退出。
- 移除自定义命名空间后,Release 应用及五个测试工程构建成功,全部测试通过。
- 在 `build/hmi-editor/` 重新构建 Release 应用,构建成功并完成启动冒烟检查。
- 在 `build/hmi-editor-service-tests/` 构建并运行 HMI 服务测试,输出 `HMI editor service tests passed`。
- 重新构建并运行领域、工程管理和主窗口测试,全部通过。
- 控制逻辑编辑器完成后,领域、逻辑编辑服务、工程管理和主窗口测试全部通过。
- Debug 应用构建成功。
- 启动 Debug 应用确认主窗口创建成功,窗口标题为“综合平台编程器”。
- 梯形图结构重构后,领域、逻辑编辑服务、工程管理和主窗口测试重新构建并全部通过。
- 待配置节点改造后,Release 应用构建成功;领域、工程管理、逻辑编辑服务、运行模式和主窗口测试全部通过。
- 离线仿真实现后,Release 应用构建成功;离线仿真、领域、工程管理、HMI 编辑、逻辑编辑、运行模式和主窗口测试全部通过。
- Remaining work: 进入开发顺序第 9 步,实现 Modbus RTU 真机通信。
- Known risks / blockers: 无。
- Suggested next command: 建立异步 Modbus RTU 通信服务,先完成串口配置、连接和 M0/D0 安全读写。
- Goal: 完成综合平台编程器的结构化梯形图、数据点管理、离线仿真和 Modbus RTU 真机链路
- Branch: `main`
- Current status: 第 1~10 步的软件实现与自动化测试已完成,待连接 XDH-60T4-E 进行真机安全验证和整理最终交付资料

## 当前能力

- 梯形图条件使用递归 `Node / Series / Parallel` 表达式树,可表示 `A OR (B AND C)` 和嵌套串并联
- 编辑器支持节点前后串联、节点并联、整支路并联、删除节点或支路、设置输出线圈,并按结构自动布线
- 执行器递归求值并生成节点、表达式、网络轨迹,离线运行时画布显示导通路径和故障位置
- 数据点表统一维护地址、类型、名称、注释、当前值和 HMI/LAD 引用位置
- `ActiveRegisterRepository` 在离线虚拟仓库与 PLC 缓存仓库之间切换
- `PlcCommunicationService` 使用 Qt SerialBus 异步连接、轮询和单点写入,UI 不阻塞
- 真机连接收集数据点、HMI 和梯形图地址,按 M/D 分区并合并相邻地址读取
- 真机模式要求首次读取完成,不复制离线值,不运行 PC 软件逻辑执行器
- 通信错误停止轮询并保留最后一次有效缓存,状态栏和日志显示错误
- 主窗口静态控件已迁入 `main_window.ui`,PLC 参数由独立配置对话框收集后再发起连接

## 关键决策

- 工程格式继续使用 `1.0`,当前文件直接保存表达式树和数据点
- 不读取旧的 `stages/branches` 结构,不添加旧格式迁移和兼容层
- 梯形图编辑结构而不是自由线段,避免悬空线、环路和多个输出路径
- 条件节点继续使用 `std::variant` 的独立配置类型,不向通用节点添加无关字段
- HMI、梯形图和通信共享 `M0~M4000`、`D0~D4000` 数据点范围
- PLC 读写使用从 `0` 开始的原始 Modbus 地址
- HMI 写请求不乐观修改 PLC 缓存,由下一次轮询确认真实值
- 真机运行时 PLC 内部程序是控制源,软件逻辑执行器保持停止

## 自动化验证

以下测试均已完成干净构建并通过:

- `domain_tests`
- `logic_editor_service_tests`
- `offline_simulation_service_tests`
- `project_management_tests`
- `hmi_editor_service_tests`
- `runtime_mode_service_tests`
- `data_point_service_tests`
- `plc_runtime_tests`
- `main_window_tests`

`main_window_tests` 还覆盖 PLC 配置参数往返、独立配置弹窗入口,以及主窗口不再内嵌串口参数控件。

Qt Release 应用已完成干净构建,并执行了启动与正常退出冒烟验证。`git diff --check` 无空白错误。

## 待完成事项

1. 在 PLC STOP 状态确认 COM2 参数和 RS-485 A/B 接线
2. 用安全测试地址读取 `M0`、`D0`
3. 写入 `M0`、`D0` 并由 PLC 监控和下一次轮询确认
4. 验证断线、超时和重新连接流程
5. PLC 切换 RUN 后执行完整 HMI 与设备联动测试
6. 整理测试记录、构建说明和演示工程

## 建议下一步

运行 Release 应用,配置实际 COM 口、站号、波特率和校验方式,在 PLC STOP 状态完成 `M0`、`D0` 首次安全读写。未确认设备程序与负载安全前不要在 RUN 状态写控制地址。

+ 100
- 104
docs/architecture.md Zobrazit soubor

@@ -1,140 +1,136 @@
# 综合平台编程器架构

## 目标
## 目标与边界

项目采用 UI、领域、服务和基础设施分层。HMI 编辑、离线仿真和真机联机使用同一套领域模型,UI 不直接依赖串口或 Modbus 实现。
项目采用 UI、领域、服务和基础设施分层。HMI 编辑、结构化梯形图、离线仿真和真机联机共用工程模型,UI 不直接依赖文件系统、串口或 Modbus 实现。

项目代码不使用自定义命名空间,分层边界由目录、接口和依赖方向表达
当前目标是完成基础设备控制闭环,不生成、编译或下载 PLC 程序,不实现 XDPPro 的完整指令集,也不提供任意自由画线

## 目录职责
## 目录与依赖

```text
app/
├── integrated_platform.pro qmake 工程入口
├── src/
│ ├── main.cpp 应用组合入口
│ ├── ui/ Qt Designer 界面、视图和交互协调
│ ├── domain/ 工程、HMI、逻辑、M/D 地址等纯业务模型
│ ├── services/ 用例编排、运行模式和仿真服务
│ └── infrastructure/ 工程文件、Modbus RTU 等外部实现
│ ├── main.cpp 对象创建与依赖注入
│ ├── ui/ Qt Widgets 视图和交互协调
│ ├── domain/ 工程、HMI、梯形图、数据点和寄存器模型
│ ├── services/ 编辑、仿真、运行模式和通信契约
│ └── infrastructure/ JSON、PLC 缓存和 Qt Modbus RTU 实现
└── tests/ 单元测试与集成测试工程
```

## 依赖方向

生产代码必须保持单向依赖:
生产代码保持以下依赖方向:

```text
UI -> Services -> Domain
Infrastructure -> Domain
Infrastructure -> Services contracts + Domain
main.cpp -> UI + Services + Infrastructure
```

- `domain` 不依赖 UI、串口、文件系统或具体 PLC 库。
- `services` 只通过领域定义的接口使用寄存器、工程存储等能力。
- `infrastructure` 实现外部能力,例如 JSON 工程文件和 Qt Modbus RTU。
- `ui` 只调用服务并展示状态;`MainWindow` 只组织界面,不实现业务规则、寄存器读写或通信流程。
- `main.cpp` 负责创建对象并注入依赖,不承载业务逻辑。
- `domain` 不依赖 Qt、串口和文件系统
- `services` 编排用例,通过接口使用工程存储、寄存器和 PLC 通信
- `infrastructure` 实现 JSON 工程文件、PLC 缓存和异步 Modbus RTU
- `ui` 只提交操作并展示模型、运行轨迹和错误状态
- `main.cpp` 负责组合依赖,不承载业务规则

主窗口的菜单、工具栏、属性表单和数据点面板由 Qt Designer 的
`main_window.ui` 定义。C++ 仅创建 HMI/梯形图自定义画布、填充运行时数据并连接服务层信号。
真实 PLC 的串口与轮询参数由独立的 `PlcConnectionDialog` 收集,主窗口只保留配置连接和断开入口。

## 核心领域模型

| 文件 | 职责 |
| --- | --- |
| `register_address.*` | 定义 `M0~M4000`、`D0~D4000` 地址和值域规则 |
| `register_repository.*` | 定义 M/D 统一读写接口和离线内存实现 |
| `active_register_repository.*` | 在虚拟仓库与 PLC 缓存仓库之间切换当前数据源 |
| `data_point_model.*` | 定义地址、名称和注释统一数据点 |
| `hmi_model.*` | 定义页面、控件、布局和寄存器绑定 |
| `control_logic_model.*` | 定义梯形图表达式树、条件节点和输出线圈 |
| `project_model.*` | 聚合数据点、HMI 页面和控制逻辑并执行工程校验 |
| `runtime_state.*` | 定义编辑、离线运行和真机运行状态机 |

工程文件格式保持 `1.0`。当前版本直接保存表达式树和数据点,不读取旧的 `stages/branches` 结构,也不包含旧格式迁移代码。加载必须先完成 JSON 解析、版本检查和领域校验,成功后才能替换当前工程;保存使用原子提交。

## 核心数据流
`Project::validate()` 允许未绑定控件、待配置节点和未完成网络作为编辑草稿保存。`Project::validateForRunning()` 额外拒绝未绑定 HMI、待配置节点、空条件和缺少输出的网络。

## 结构化梯形图

网络条件采用递归表达式:

```text
用户操作 HMI
-> UI 发起服务请求
-> 服务校验并更新领域模型或寄存器接口
-> 仿真服务 / 真机 Modbus 实现执行
-> 服务发布状态变化
-> UI 刷新控件和状态栏
ConditionExpression
├── Node 单个触点或比较条件
├── Series 子表达式依次串联,执行 AND
└── Parallel 子表达式并联,执行 OR
```

离线模式由服务连接虚拟 M/D 寄存器与软件逻辑执行器;真机模式由服务连接 PLC 寄存器缓存与 Modbus RTU 服务。两种模式共用 HMI 绑定模型,且软件逻辑执行器不得在真机模式运行。
例如 `A OR (B AND C)` 表达为:

## 核心领域模型
```text
Parallel
├── Node(A)
└── Series
├── Node(B)
└── Node(C)
```

领域模型位于 `app/src/domain/`,只使用 C++17 标准库,不包含 Qt 界面、串口或 Modbus
类型:
`LogicEditorService` 支持在节点前后串联插入、与节点并联、与整个支路并联、删除节点或支路,以及设置或替换输出线圈。删除后会归一化空容器和只有一个子项的容器,防止无效嵌套进入执行器。

| 文件 | 职责 |
| --- | --- |
| `register_address.*` | 定义 M/D 地址值对象,统一校验项目地址范围 `0~4000`。 |
| `register_repository.*` | 定义 M 位和 D 字的统一读写接口,并提供离线内存实现。 |
| `project_storage.*` | 定义工程文件保存和加载的领域存储契约,不依赖具体文件格式。 |
| `hmi_model.*` | 定义 HMI 页面、控件、位置、扩展属性和寄存器绑定,并校验控件位于页面边界内。 |
| `control_logic_model.*` | 定义网络、串联级、并联条件和输出线圈,并校验节点类型及地址区域。 |
| `project_model.*` | 聚合工程元数据、HMI 页面和控制逻辑,并校验工程内标识唯一性。 |
| `runtime_state.*` | 定义编辑态、离线运行态、真机运行态及合法切换规则。 |

工程管理由 `services/ProjectService` 编排新建、保存、另存为和加载。JSON 文件转换和原子写入
由 `infrastructure/JsonProjectStorage` 实现。加载必须先完成文件解析、版本检查和领域校验,成功
后才能替换当前工程;保存失败或加载失败不得修改当前工程路径和内存内容。

工程编辑支持保留草稿:HMI 非标签控件可以暂时没有寄存器绑定,梯形图节点可以处于待配置状态,
这些状态仍通过 `Project::validate()` 保存和加载。进入离线运行前由
`Project::validateForRunning()` 额外拒绝未绑定控件、待配置节点和缺少条件/输出的网络;旧版 1.0
工程缺少节点 `configured` 字段时按历史完整节点兼容读取。

`services/HmiEditorService` 负责 HMI 页面的控件添加、删除、移动和属性更新,不依赖 Qt 图元。
`ui/HmiEditorWidget` 使用 `QGraphicsScene` 将模型投影为画布项,画布项只保存控件标识,移动结束后
才通过编辑服务提交新位置。草稿保存时 `Project::validate()` 只拒绝已填写但区域错误或非法的绑定;
进入离线运行前 `Project::validateForRunning()` 要求必需绑定完整。

`RegisterRepository` 是 HMI、逻辑执行器和运行服务唯一可见的寄存器边界。离线运行使用
`VirtualRegisterRepository`;真机运行后续注入由基础设施层实现的 PLC 缓存仓库。仓库读取的是
最近一次有效值,写入成功只表示请求被仓库接受;真实 PLC 的异步确认由服务层负责反馈。

`services/HmiRuntimeService` 仅持有 `RegisterRepository` 接口:按钮切换 M 位,指示灯读取 M 位,
数值显示读取 D 字,数值输入写入 D 字。它不依赖虚拟寄存器实现、串口或 Modbus,因此离线和真机
运行可替换数据源而无需改变 HMI 模型或画布代码。

`services/SoftwareLogicExecutor` 负责一次确定性梯形图扫描。执行顺序为控制逻辑、网络、串联级,
同级并联条件执行 OR,不同串联级执行 AND;普通线圈每周期写入网络结果,置位和复位线圈只在
网络成立时写入。前面网络的写入对后面网络在同一扫描周期立即可见。启动前拒绝同一 M 地址混用
不同线圈模式,避免运行结果依赖隐藏顺序。

`services/OfflineSimulationService` 使用 `Qt::PreciseTimer` 以 50 ms 目标周期驱动扫描器。服务启动时
先校验并复制控制逻辑快照,再清空虚拟 M/D 并开始扫描;退出离线态时先停止扫描再开放工程编辑。
该周期不提供硬实时保证。服务记录 `Stopped`、`Running`、`Faulted` 实际状态、成功扫描次数和最近
错误上下文。发生仓库读写或执行错误时停止定时器、保留最终寄存器值和逻辑/网络/节点定位;HMI
仍可读取最终值,但按钮和数值输入进入只读状态。

控制逻辑节点使用 `std::variant` 组合独立配置类型。初版只包含 M 区触点、M 区线圈和 D 值与
常量比较。新增节点时应增加独立配置类型及其校验和执行处理,不得向通用 `LogicNode` 持续
添加只对单一节点有效的可选字段。定时器不是当前原始需求范围,不提前建立模型或执行逻辑。

控制逻辑按标准梯形图的受限结构表达:一个逻辑包含多个网络,一个网络包含按顺序串联的多个
条件级,每个条件级包含一个或多个并联条件,输出线圈固定在网络最右侧。条件级之间执行逻辑与,
同级并联条件执行逻辑或。状态保持通过启动触点和运行反馈触点并联,并与停止触点串联后驱动运行
线圈表达。该结构是后续软件逻辑执行器的确定性求值契约,不需要通用图遍历或连接表。

`services/LogicEditorService` 负责新增和删除网络、追加串联条件、增加并联条件、设置输出线圈、
修改属性和删除节点。空网络和未完成网络均可作为编辑草稿保存;进入离线运行前,非空网络必须同时
具备条件和输出线圈,且所有节点均已配置。
`ui/LogicEditorWidget` 自动排列左右母线、串联级、并联支路和右侧线圈,不允许自由拖动或手动画线,
也不保存像素坐标。编辑器通过服务提交修改,不访问 HMI、寄存器仓库或 PLC。
`LogicEditorWidget` 根据表达式树计算布局并自动生成横线、竖线和支路汇合线。画布可选择节点或表达式范围,并显示选中范围和插入位置;用户编辑结构而不是直接保存线段,因此不会产生悬空线、环路和多输出路径。

条件节点使用 `std::variant` 保存独立配置类型,当前包含 M 常开/常闭触点和 D 值与常量比较。输出支持普通、置位和复位 M 线圈。新增节点类型时应增加独立配置类型及校验、存储和执行分支,不能向通用节点堆叠无关字段。

## 执行与运行反馈

`SoftwareLogicExecutor` 按控制逻辑、网络顺序扫描,并递归求值 `Node / Series / Parallel`。普通线圈每周期写入网络结果,置位和复位线圈只在网络成立时写入;前面网络的写入对后面网络同一扫描周期立即可见。

每次扫描生成 `LogicTraceSnapshot`,记录节点、表达式、网络和线圈的导通状态。逻辑画布在运行时以绿色显示导通节点和路径;执行错误携带逻辑、网络和节点上下文,画布选择并以红色突出故障位置。

`OfflineSimulationService` 以 50 ms 目标周期驱动扫描,启动时复制逻辑快照并清空虚拟 M/D。故障后停止扫描并保留最终寄存器值,HMI 可继续显示但禁止写入。该周期不提供硬实时保证。

## 数据点管理

`DataPointService` 维护统一数据点表:

```text
地址 | 类型 | 名称 | 注释 | 当前值 | 引用位置
```

地址和名称在工程内唯一。引用位置从 HMI 绑定和梯形图节点动态计算;仍被引用的数据点不能删除或修改地址,但可以修改名称和注释。HMI 和梯形图属性区从同一数据点集合选择地址,运行时当前值由活动寄存器仓库读取。

## 寄存器与 Modbus RTU

`HmiRuntimeService` 和运行界面只访问 `RegisterRepository`。`ActiveRegisterRepository` 在两种数据源之间切换:

- 离线运行使用 `VirtualRegisterRepository`
- 真机运行使用 `PlcRegisterRepository` 的最近一次有效缓存

`PlcCommunicationService` 使用 `QModbusRtuSerialMaster` 异步连接、读取和写入。连接时从数据点、HMI 和梯形图收集 M/D 地址,去重后按区域合并相邻地址进行轮询。M 使用线圈,D 使用保持寄存器;所有 Modbus 地址均为从 `0` 开始的原始地址。

HMI 写入只表示异步请求已受理,不乐观修改 PLC 缓存,实际值由后续读回确认。超时、协议错误或断线会停止轮询、保留最后一次有效值并进入故障状态,错误通过运行模式服务交给 UI 展示。

## 运行模式边界

| 模式 | 允许编辑工程 | 寄存器来源 | 软件逻辑执行器 | 进入约束 |
| 模式 | 允许编辑 | 寄存器来源 | 软件逻辑执行器 | 进入约束 |
| --- | --- | --- | --- | --- |
| 编辑态 | 是 | 无运行仓库 | 停止 | 运行态必须先回到编辑态。 |
| 离线运行态 | 否 | 虚拟 M/D | 运行 | 仅可从编辑态进入。 |
| 真机运行态 | 否 | PLC 读回缓存 | 停止 | 仅可从编辑态进入,且必须先完成 PLC 初次读取。 |
| 编辑态 | 是 | 无运行数据源 | 停止 | 运行态必须先退出 |
| 离线运行 | 否 | 虚拟 M/D | 运行 | 工程通过运行校验 |
| 真机运行 | 否 | PLC 读回缓存 | 停止 | PLC 已连接且首次读取完成 |

离线运行态与真机运行态不得直接互切。进入真机运行态时不复制或写入离线仓库值,避免误写
PLC;退出任一运行态后才能重新编辑工程。
离线运行与真机运行不得直接互切。进入真机模式不复制或下发离线值;PLC 内部程序是控制源,PC 端软件逻辑执行器必须停止。

## 测试边界

测试按业务风险和模块边界安排,不要求为每个简单访问器或转发函数单独编写测试:

| 优先级 | 测试对象 | 重点验证内容 |
| 优先级 | 测试对象 | 重点内容 |
| --- | --- | --- |
| 高 | `domain` | 地址边界、M/D 类型约束、模型校验、工程唯一性和运行模式切换 |
| 高 | `services` | HMI、寄存器仓库和逻辑执行器之间的离线闭环 |
| 高 | 工程存储 | 保存加载往返、版本字段、缺失字段和非法文件 |
| 中 | `infrastructure` | Modbus 帧解析、超时、CRC、断线和错误反馈 |
| 中 | `ui` | 新建工程、编辑、运行切换和错误提示等关键工作流 |
| 冒烟 | 真机联调 | 少量安全地址的读写和 RUN 联动验证 |
`domain` 和 `services` 的自动化测试不依赖 Qt 界面或真实 PLC。基础设施优先使用模拟串口
和测试文件,真机测试只覆盖无法在模拟环境确认的通信和联动行为
| 高 | 领域模型 | 地址边界、表达式校验与归一化、工程唯一性 |
| 高 | 编辑服务 | 嵌套串并联插入、删除、数据点引用保护 |
| 高 | 执行与模式服务 | 递归求值、轨迹、离线闭环、仓库切换和首次读取约束 |
| 高 | 工程存储 | `1.0` 往返、缺失字段、非法结构和不支持版本 |
| 中 | PLC 通信 | 缓存有效性、地址集合、异步读写和错误反馈 |
| 中 | UI | 编辑、运行切换、选择和错误定位工作流 |
| 冒烟 | 真机 | STOP 状态安全读写 M0/D0,随后 RUN 联动 |

自动化测试不依赖真实 PLC。串口接线、PLC 参数和设备 RUN 行为只能通过现场真机测试确认。

+ 23
- 5
docs/开发顺序.md Zobrazit soubor

@@ -2,6 +2,10 @@

> 本文规定项目的推荐开发顺序。原则是先建立稳定的数据模型和模块边界,再完成编辑功能,最后接入仿真与真实 PLC。每个阶段完成后都应保证项目可以构建、运行并提交一次 Git 记录。

## 当前进度

第 1~10 步的软件实现已完成,工程格式继续使用 `1.0`,不保留旧梯形图结构的兼容代码。当前重点是第 11 步中的真机安全验证和第 12 步交付资料。

## 1. 建立开发基线

- 初始化 Git 仓库,确认 `.gitignore`、编码规则和提交规范。
@@ -20,7 +24,7 @@

## 3. 定义核心数据模型与接口

- 定义工程、HMI 页面、HMI 控件、控制逻辑和 M/D 地址模型。
- 定义工程、HMI 页面、HMI 控件、结构化控制逻辑、统一数据点和 M/D 地址模型。
- 定义统一的寄存器仓库接口,隔离虚拟寄存器与真实 PLC 寄存器。
- 定义编辑态、离线运行态和真机运行态的状态边界。

@@ -53,20 +57,30 @@

## 7. 实现控制逻辑编辑器

- 建立梯形图逻辑模型,支持网络、串联条件、并联支路、输出线圈、删除和属性配置。
- 使用 `Node / Series / Parallel` 表达式树建立梯形图逻辑模型,支持嵌套串并联关系。
- 支持节点前后串联插入、与节点并联、与整个支路并联、删除和输出线圈配置。
- 初版实现常开、常闭、普通线圈、置位/复位以及 D 值与常量比较。
- 定时器不属于当前原始需求范围,只有在后续需求明确时才新增对应配置和执行逻辑。
- 新增节点类型时使用独立配置类型,不向通用节点结构持续堆叠无关字段。
- 编辑器只生成逻辑模型,不直接修改 HMI 或 PLC。
- 画布按梯形图结构自动布局,不允许节点自由拖动,也不要求用户手动画线。
- 画布按表达式结构自动布线,显示节点或支路选择范围,不允许节点自由拖动,也不保存自由线段。

完成标准:能够表达 `A OR (B AND C)` 等嵌套逻辑,并配置启动、停止和状态保持逻辑。

完成标准:能够配置一套简单的启动、停止和状态保持逻辑。
## 7.1 建立统一数据点表

- 统一维护地址、类型、名称、注释、当前值和引用位置。
- HMI 与梯形图从同一数据点集合选择 M/D 地址。
- 防止删除或修改仍被引用的数据点地址。

完成标准:地址只需定义一次,能够查看 HMI 和梯形图中的全部引用位置。

## 8. 实现离线仿真运行

- 实现虚拟 M/D 寄存器和固定周期的软件逻辑执行器。
- 将 HMI、虚拟寄存器和控制逻辑形成完整闭环。
- 实现启动、停止、异常和当前运行状态反馈。
- 记录节点、支路和网络导通轨迹,在画布突出导通路径和故障节点。

完成标准:不连接 PLC 时,可以通过 HMI 操作并观察逻辑运行结果。

@@ -75,8 +89,10 @@
- 使用 Qt SerialBus 的 `QModbusRtuSerialMaster` 封装独立通信服务。
- 实现串口配置、连接、断开、M/D 读写、轮询、超时和错误反馈。
- 使用从 `0` 开始的原始 Modbus 地址,通信过程不得阻塞 UI。
- 收集数据点、HMI 和梯形图地址,按 M/D 分区并合并相邻地址轮询。
- 写请求不乐观更新缓存,等待下一次读回确认 PLC 实际值。

完成标准:先在 PLC STOP 状态下稳定读取和写入 `M0`、`D0`,再扩展到批量轮询。
完成标准:软件链路和自动化测试完成后,先在 PLC STOP 状态下稳定读取和写入 `M0`、`D0`,再验证批量轮询。

## 10. 完成运行模式集成

@@ -96,6 +112,8 @@

完成标准:核心流程可重复执行,异常有反馈,程序不会崩溃或误写 PLC。

当前待办:自动化回归已覆盖核心领域、编辑、存储、仿真、运行模式、PLC 缓存和主界面;真实串口、接线、STOP 安全写入和 RUN 联动仍需连接 XDH-60T4-E 验证。

## 12. 完成交付与演示

- 整理规格书、方案书、测试大纲、构建运行说明和演示工程。


Načítá se…
Zrušit
Uložit