# 综合平台编程器架构 ## 目标 项目采用 UI、领域、服务和基础设施分层。HMI 编辑、离线仿真和真机联机使用同一套领域模型,UI 不直接依赖串口或 Modbus 实现。 项目代码不使用自定义命名空间,分层边界由目录、接口和依赖方向表达。 ## 目录职责 ```text app/ ├── integrated_platform.pro qmake 工程入口 ├── src/ │ ├── main.cpp 应用组合入口 │ ├── ui/ Qt Designer 界面、视图和交互协调 │ ├── domain/ 工程、HMI、逻辑、M/D 地址等纯业务模型 │ ├── services/ 用例编排、运行模式和仿真服务 │ └── infrastructure/ 工程文件、Modbus RTU 等外部实现 └── tests/ 单元测试与集成测试工程 ``` ## 依赖方向 生产代码必须保持单向依赖: ```text UI -> Services -> Domain Infrastructure -> Domain main.cpp -> UI + Services + Infrastructure ``` - `domain` 不依赖 UI、串口、文件系统或具体 PLC 库。 - `services` 只通过领域定义的接口使用寄存器、工程存储等能力。 - `infrastructure` 实现外部能力,例如 JSON 工程文件和 Qt Modbus RTU。 - `ui` 只调用服务并展示状态;`MainWindow` 只组织界面,不实现业务规则、寄存器读写或通信流程。 - `main.cpp` 负责创建对象并注入依赖,不承载业务逻辑。 ## 核心数据流 ```text 用户操作 HMI -> UI 发起服务请求 -> 服务校验并更新领域模型或寄存器接口 -> 仿真服务 / 真机 Modbus 实现执行 -> 服务发布状态变化 -> UI 刷新控件和状态栏 ``` 离线模式由服务连接虚拟 M/D 寄存器与软件逻辑执行器;真机模式由服务连接 PLC 寄存器缓存与 Modbus RTU 服务。两种模式共用 HMI 绑定模型,且软件逻辑执行器不得在真机模式运行。 ## 核心领域模型 领域模型位于 `app/src/domain/`,只使用 C++17 标准库,不包含 Qt 界面、串口或 Modbus 类型: | 文件 | 职责 | | --- | --- | | `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` 实现。加载必须先完成文件解析、版本检查和领域校验,成功 后才能替换当前工程;保存失败或加载失败不得修改当前工程路径和内存内容。 `services/HmiEditorService` 负责 HMI 页面的控件添加、删除、移动和属性更新,不依赖 Qt 图元。 `ui/HmiEditorWidget` 使用 `QGraphicsScene` 将模型投影为画布项,画布项只保存控件标识,移动结束后 才通过编辑服务提交新位置。保存时由 `Project::validate()` 拒绝缺失必需绑定或绑定区域错误的控件。 `RegisterRepository` 是 HMI、逻辑执行器和运行服务唯一可见的寄存器边界。离线运行使用 `VirtualRegisterRepository`;真机运行后续注入由基础设施层实现的 PLC 缓存仓库。仓库读取的是 最近一次有效值,写入成功只表示请求被仓库接受;真实 PLC 的异步确认由服务层负责反馈。 `services/HmiRuntimeService` 仅持有 `RegisterRepository` 接口:按钮切换 M 位,指示灯读取 M 位, 数值显示读取 D 字,数值输入写入 D 字。它不依赖虚拟寄存器实现、串口或 Modbus,因此离线和真机 运行可替换数据源而无需改变 HMI 模型或画布代码。 控制逻辑节点使用 `std::variant` 组合独立配置类型。初版只包含 M 区触点、M 区线圈和 D 值与 常量比较。新增节点时应增加独立配置类型及其校验和执行处理,不得向通用 `LogicNode` 持续 添加只对单一节点有效的可选字段。定时器不是当前原始需求范围,不提前建立模型或执行逻辑。 控制逻辑按标准梯形图的受限结构表达:一个逻辑包含多个网络,一个网络包含按顺序串联的多个 条件级,每个条件级包含一个或多个并联条件,输出线圈固定在网络最右侧。条件级之间执行逻辑与, 同级并联条件执行逻辑或。状态保持通过启动触点和运行反馈触点并联,并与停止触点串联后驱动运行 线圈表达。该结构是后续软件逻辑执行器的确定性求值契约,不需要通用图遍历或连接表。 `services/LogicEditorService` 负责新增和删除网络、追加串联条件、增加并联条件、设置输出线圈、 修改属性和删除节点。空网络可作为编辑占位;非空网络保存时必须同时具备条件和输出线圈。 `ui/LogicEditorWidget` 自动排列左右母线、串联级、并联支路和右侧线圈,不允许自由拖动或手动画线, 也不保存像素坐标。编辑器通过服务提交修改,不访问 HMI、寄存器仓库或 PLC。 ## 运行模式边界 | 模式 | 允许编辑工程 | 寄存器来源 | 软件逻辑执行器 | 进入约束 | | --- | --- | --- | --- | --- | | 编辑态 | 是 | 无运行仓库 | 停止 | 运行态必须先回到编辑态。 | | 离线运行态 | 否 | 虚拟 M/D | 运行 | 仅可从编辑态进入。 | | 真机运行态 | 否 | PLC 读回缓存 | 停止 | 仅可从编辑态进入,且必须先完成 PLC 初次读取。 | 离线运行态与真机运行态不得直接互切。进入真机运行态时不复制或写入离线仓库值,避免误写 PLC;退出任一运行态后才能重新编辑工程。 ## 测试边界 测试按业务风险和模块边界安排,不要求为每个简单访问器或转发函数单独编写测试: | 优先级 | 测试对象 | 重点验证内容 | | --- | --- | --- | | 高 | `domain` | 地址边界、M/D 类型约束、模型校验、工程唯一性和运行模式切换 | | 高 | `services` | HMI、寄存器仓库和逻辑执行器之间的离线闭环 | | 高 | 工程存储 | 保存加载往返、版本字段、缺失字段和非法文件 | | 中 | `infrastructure` | Modbus 帧解析、超时、CRC、断线和错误反馈 | | 中 | `ui` | 新建工程、编辑、运行切换和错误提示等关键工作流 | | 冒烟 | 真机联调 | 少量安全地址的读写和 RUN 联动验证 | `domain` 和 `services` 的自动化测试不依赖 Qt 界面或真实 PLC。基础设施优先使用模拟串口 和测试文件,真机测试只覆盖无法在模拟环境确认的通信和联动行为。