# 综合平台编程器架构 ## 目标与边界 项目采用 UI、领域、服务和基础设施分层。HMI 编辑、结构化梯形图、离线仿真和真机联机共用工程模型,UI 不直接依赖文件系统、串口或 Modbus 实现。 当前目标是完成基础设备控制闭环,不生成、编译或下载 PLC 程序,不实现 XDPPro 的完整指令集,也不提供任意自由画线。 ## 目录与依赖 ```text app/ ├── integrated_platform.pro qmake 工程入口 ├── src/ │ ├── main.cpp 对象创建与依赖注入 │ ├── ui/ Qt Widgets 视图和交互协调 │ ├── domain/ 工程、HMI、梯形图和寄存器模型 │ ├── services/ 编辑、仿真、运行模式和通信契约 │ └── infrastructure/ JSON、PLC 缓存和 Qt Modbus RTU 实现 └── tests/ 单元测试与集成测试工程 ``` 生产代码保持以下依赖方向: ```text UI -> Services -> Domain Infrastructure -> Services contracts + Domain main.cpp -> UI + Services + Infrastructure ``` - `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 缓存仓库之间切换当前数据源 | | `register_monitor_model.*` | 保存当前调试会话的自由监控 M/D 地址集合 | | `hmi_model.*` | 定义页面、控件、布局和寄存器绑定 | | `control_logic_model.*` | 定义梯形图表达式树、条件节点和输出线圈 | | `project_model.*` | 聚合 HMI 页面和控制逻辑并执行工程校验 | | `runtime_state.*` | 定义编辑、离线运行和真机运行状态机 | 工程文件格式保持 `1.0`。当前版本直接保存 HMI 和梯形图表达式树,不读取旧的 `stages/branches` 和 `dataPoints` 结构,也不包含旧格式迁移和兼容代码。加载必须先完成 JSON 解析、版本检查和领域校验,成功后才能替换当前工程;保存使用原子提交。 `Project::validate()` 允许未绑定控件、待配置节点和未完成网络作为编辑草稿保存。`Project::validateForRunning()` 额外拒绝未绑定 HMI、待配置节点、空条件和缺少输出的网络。 HMI 按钮操作作为工程 `1.0` 的强类型字段保存,支持 `置 ON`、`置 OFF`、`取反` 和 `瞬时 ON`。新建按钮默认使用瞬时 ON;按钮按下和释放事件由 UI 转交运行服务解释,UI 不直接决定写入值。当前版本不迁移缺少按钮操作字段的旧工程文件。 ## 结构化梯形图 网络条件采用递归表达式: ```text ConditionExpression ├── Node 单个触点或比较条件 ├── 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)`。服务层负责校验范围连续性和子表达式完整性,拒绝跨网络、非连续或截断内部子表达式的选择。删除后会归一化空容器和只有一个子项的容器,防止无效嵌套进入执行器。 `LogicEditorWidget` 将表达式树投影到固定行列网格,串联节点横向占列,并联分支纵向占行,所有连接只使用水平线和竖线。用户只选择画布中可见的节点:单击定位当前节点,橡皮框或多选连续节点后可直接建立并联支路。内部 `Series / Parallel` 容器不作为可选 UI 对象暴露。网格是编辑投影,不持久化自由线段,因此不会产生悬空线、环路和多输出路径。 工具栏中的常开、常闭和比较条件在没有选择时追加到当前网络末尾,选中一个条件节点时插入到该节点后。并联支路按钮默认建立常开触点,下拉菜单可选择常闭触点或比较条件。新增节点保持待配置状态,并自动选中供属性区继续编辑。 条件节点使用 `std::variant` 保存独立配置类型,当前包含 M 常开/常闭触点和 D 值与常量比较。输出支持普通、置位和复位 M 线圈。新增节点类型时应增加独立配置类型及校验、存储和执行分支,不能向通用节点堆叠无关字段。 ## 执行与运行反馈 `SoftwareLogicExecutor` 按控制逻辑、网络顺序扫描,并递归求值 `Node / Series / Parallel`。普通线圈每周期写入网络结果,置位和复位线圈只在网络成立时写入;前面网络的写入对后面网络同一扫描周期立即可见。 每次扫描生成 `LogicTraceSnapshot`,记录节点、表达式、网络和线圈的导通状态。逻辑画布在运行时以绿色显示导通节点和路径;执行错误携带逻辑、网络和节点上下文,画布选择并以红色突出故障位置。 `OfflineSimulationService` 以 50 ms 目标周期驱动扫描,启动时复制逻辑快照并清空虚拟 M/D。故障后停止扫描并保留最终寄存器值,HMI 可继续显示但禁止写入。该周期不提供硬实时保证。 ## M/D 地址使用 HMI 控件和梯形图节点直接绑定 `M0~M4000`、`D0~D4000` 地址,不要求预先创建数据点。地址是程序和通信的真实标识,属性面板只编辑区域与地址。 软元件注释、网络注释和命令语视图不属于当前实现。后续如增加注释,应把地址级软元件注释和网络级说明分开建模,不能重新引入“名称、注释、当前值、引用位置”混合的数据点表。 ## 自由监控与运行工作台 自由监控是当前调试会话中的寄存器诊断工具,不属于工程模型,也不写入工程 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` 的最近一次有效缓存 `PlcCommunicationService` 使用 `QModbusRtuSerialMaster` 异步连接、读取和写入。轮询集合由 HMI 引用、梯形图引用和自由监控地址合并而成,去重后按区域合并相邻地址。M 使用线圈,D 使用保持寄存器;所有 Modbus 地址均为从 `0` 开始的原始地址。 PLC 连接后可以动态增删自由监控地址。存在读取请求时,新的轮询集合在当前响应处理完成后 应用,旧响应仍按发出时的读取块解析。连接级首次读取完成后,动态增加地址不会撤销真机 运行资格;新增地址自身在首次读回前显示“等待读取”。如果连接首次读取尚未完成,新的完整 轮询集合会共同参与首次读取判定。 HMI 写入只表示异步请求已受理,不乐观修改 PLC 缓存,实际值由后续读回确认。超时、协议错误或断线会停止轮询、保留最后一次有效值并进入故障状态,错误通过运行模式服务交给 UI 展示。 通信故障会立即撤销当前连接的首次读取资格。若故障发生在真机运行态,运行模式服务自动返回 编辑态,PLC 最后一次有效缓存仍保留用于日志和故障诊断,但不能作为再次进入真机运行态的依据。 重新进入真机运行必须重新连接 PLC,并在当前连接中重新完成首次读取。 通信服务根据 Qt Modbus 错误和串口是否曾成功打开区分连接阶段失败与运行中连接中断。连接阶段 无法打开串口时直接保持未连接状态;已经连接后收到 `ConnectionError` 或意外进入 `UnconnectedState` 时,统一报告本地串口连接中断,自动清理会话并进入未连接状态。错误日志 独立保留,不会为了显示故障而继续把串口会话表示为已连接。 ## 运行模式边界 | 模式 | 允许编辑 | 寄存器来源 | 软件逻辑执行器 | 进入约束 | | --- | --- | --- | --- | --- | | 编辑态 | 是 | 无运行数据源 | 停止 | 运行态必须先退出 | | 离线运行 | 否 | 虚拟 M/D | 运行 | 工程通过运行校验 | | 真机运行 | 否 | PLC 读回缓存 | 停止 | PLC 已连接且首次读取完成 | 离线运行与真机运行不得直接互切。进入真机模式不复制或下发离线值;PLC 内部程序是控制源,PC 端软件逻辑执行器必须停止。 ## 测试边界 | 优先级 | 测试对象 | 重点内容 | | --- | --- | --- | | 高 | 领域模型 | 地址边界、表达式校验与归一化、工程唯一性 | | 高 | 编辑服务 | 嵌套串并联插入、删除和地址配置 | | 高 | 执行与模式服务 | 递归求值、轨迹、离线闭环、仓库切换和首次读取约束 | | 高 | 工程存储 | `1.0` 往返、缺失字段、非法结构和不支持版本 | | 中 | PLC 通信 | 缓存有效性、地址集合、异步读写和错误反馈 | | 中 | UI | 编辑、运行切换、选择和错误定位工作流 | | 冒烟 | 真机 | STOP 状态安全读写 M0/D0,随后 RUN 联动 | 自动化测试不依赖真实 PLC。串口接线、PLC 参数和设备 RUN 行为只能通过现场真机测试确认。