综合平台编程器项目的远程存储
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 

18 KiB

综合平台编程器架构

目标与边界

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

当前目标是完成单工程、单 PLC、多 HMI 页面和多控制逻辑的基础设备控制闭环,不生成、编译或下载 PLC 程序,不实现 XDPPro 的完整 POU/指令集,也不提供任意自由画线。

目录与依赖

app/
├── integrated_platform.pro     qmake 工程入口
├── src/
│   ├── main.cpp                对象创建与依赖注入
│   ├── ui/                     Qt Widgets 视图和界面控制器
│   ├── domain/                 工程、HMI、梯形图和寄存器模型
│   ├── services/               编辑、仿真、运行模式和通信契约
│   └── infrastructure/         JSON、PLC 缓存和 Qt Modbus RTU 实现
└── tests/                      单元测试与集成测试工程

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

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 定义。界面交互按职责由三个 UI 控制器协调:

  • ProjectWorkspaceController 负责工程树、页面/逻辑会话选择和工程对象刷新
  • PropertyPanelController 负责 HMI/梯形图选择状态、属性面板和编辑器信号
  • RuntimePanelController 负责运行监控工作台、运行值刷新和离线执行器反馈

MainWindow 负责组合这些控制器、处理窗口级菜单和跨区域模式切换,不重复实现工程校验或寄存器业务规则。真实 PLC 的串口与轮询参数由独立的 PlcConnectionDialog 收集。

核心领域模型

文件 职责
register_address.* 定义 M0~M4000D0~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 直接保存有序 hmiPagesinitialHmiPageId、有序 alarmDefinitions、工程级 registerComments、有序 controlLogics 和梯形图表达式树,不读取其他结构,也不包含迁移和兼容代码。加载必须先完成 JSON 解析、版本检查和领域校验,成功后才能替换当前工程;保存使用原子提交。完整字段定义见 docs/工程格式说明.md

HmiPage.idControlLogic.id 是稳定内部标识,名称可修改,数组顺序分别决定工程树页面顺序和离线逻辑扫描顺序。MainWindow 保存 current_hmi_page_id_current_logic_id_ 作为编辑会话选择,ProjectWorkspaceController 负责将它们同步到工程树和编辑画布,它们不进入 JSON。Project.initialHmiPageId 是运行态每次启动 HMI 时使用的持久化初始页,与编辑器当前正在查看的页面相互独立。

Project::validate() 允许未绑定控件、待配置节点和未完成网络作为编辑草稿保存。Project::validateForRunning() 额外拒绝未绑定 HMI、待配置节点、空条件和缺少输出的网络,并检查已启用逻辑中的 T 触点必须有唯一的 TON 驱动。

HMI 控件公共描述由无 Qt 依赖的 hmi_control_registry 模块统一提供。当前 7 种控件的工程稳定类型名、
界面名称、默认 ID 前缀、默认文字、默认尺寸、M/D 绑定规则和运行值类型只在注册表中定义一次。
领域校验、编辑服务、运行服务、JSON 存储和属性面板共同查询该描述;控件绘制、按钮写入、
PageJump 导航和 AlarmList 展示等专用行为仍留在各自模块,不进入公共描述表。

HMI 按钮操作作为工程 1.0 的强类型字段保存,支持 置 ON置 OFF取反
瞬时 ON。新建按钮默认使用瞬时 ON;按钮按下和释放事件由 UI 转交运行服务解释,UI
不直接决定写入值。当前版本不迁移缺少按钮操作字段的旧工程文件。

Label 是不绑定 M/D 的固定文本控件。PageJump 是独立控件,使用强类型 targetPageId 引用目标页面稳定 ID,同样不绑定 M/D。编辑态点击 PageJump 只执行选择和拖动;离线或真机运行态点击后由 HmiNavigationService 在本地切换运行页面,不发出 PLC 写请求,因此 PLC 通信写权限不可用时也不影响页面导航。被 PageJump 引用的页面不能删除,初始页必须先切换后才能删除,工程至少保留一个页面。

AlarmList 是不绑定单个寄存器的运行状态投影。项目级 AlarmDefinition 只支持 M 位为 ON、D 值大于等于阈值和 D 值小于等于阈值三种条件,不设置提示、警告、故障等级。AlarmEditorService 负责定义增删改,AlarmService 统一读取活动寄存器仓库并维护当前报警的发生时间和确认状态,所有页面上的 AlarmList 共享这份状态。报警条件恢复后立即删除对应运行记录;运行态没有报警时 AlarmList 隐藏,出现报警时显示并置顶。返回编辑态时清空运行记录,工程 JSON 只保存报警定义。

结构化梯形图

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

ConditionExpression
├── Node       单个触点或比较条件
├── Series     子表达式依次串联,执行 AND
└── Parallel   子表达式并联,执行 OR

例如 A OR (B AND C) 表达为:

Parallel
├── Node(A)
└── Series
    ├── Node(B)
    └── Node(C)

LogicEditorService 支持在当前节点后串联插入,并为单个节点或同一网络中的连续逻辑范围建立并联支路。例如选择 B、C 后建立并联会得到 A AND ((B AND C) OR D)。服务层负责校验范围连续性和子表达式完整性,拒绝跨网络、非连续或截断内部子表达式的选择。删除后会归一化空容器和只有一个子项的容器,防止无效嵌套进入执行器。

LogicEditorWidget 将表达式树投影到固定行列网格,串联节点横向占列,并联分支纵向占行,所有连接只使用水平线和竖线。用户只选择画布中可见的节点:单击定位当前节点,橡皮框或多选连续节点后可直接建立并联支路。内部 Series / Parallel 容器不作为可选 UI 对象暴露。网格是编辑投影,不持久化自由线段,因此不会产生悬空线、环路和多输出路径。

工具栏中的常开、常闭、上升沿、下降沿、T 触点和比较条件在没有选择时追加到当前网络末尾,选中一个条件节点时插入到该节点后。并联支路按钮默认建立常开触点,下拉菜单可选择常闭、边沿、T 触点或比较条件。新增节点保持待配置状态,并自动选中供属性区继续编辑。

条件节点使用 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 读取的外部寄存器。

执行与运行反馈

SoftwareLogicExecutorcontrolLogics 数组顺序扫描所有已启用逻辑,再按网络顺序递归求值 Node / Series / Parallel。禁用逻辑仅保留结构校验,未完成的禁用草稿不会阻止离线运行。普通线圈每周期写入网络结果,置位和复位线圈只在网络成立时写入;前面逻辑或网络的写入对后面逻辑和网络在同一扫描周期立即可见。

上升沿和下降沿是标准的一次扫描脉冲,运行状态按逻辑 ID 和节点 ID 隔离。TON 是正常非保持接通延时:输入接通后记录 steady_clock 起点,ET 使用实际经过的毫秒数,达到 PTQ 为真;输入断开立即清零 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-1contact-1 相互覆盖。运行工作台的逻辑下拉框只切换当前轨迹投影,所有已启用逻辑仍持续执行。逻辑画布以绿色显示导通节点和路径;执行错误携带逻辑、网络和节点上下文,画布选择并以红色突出故障位置。

OfflineSimulationService 以 50 ms 目标周期驱动扫描,启动时复制逻辑快照、清空虚拟 M/D 并重置边沿和 T 运行状态。故障或停止后清理这些运行状态;重新启动不会继承上次边沿历史或 TON 的 ET/Q。该周期只提供调度精度,不提供硬实时保证。

M/D 地址使用

HMI 控件和梯形图节点直接绑定 M0~M4000D0~D4000 地址,不要求预先创建数据点。地址是程序和通信的真实标识,属性面板只编辑区域与地址。

M/D 是外部地址类型:M 保存位输入、复位信号和线圈结果,D 保存字操作数、MOVE/ADD/SUB 目标以及计数器 CV。T/C 是软件逻辑资源类型,只在离线模式维护运行状态,不在工程中映射为 PLC 的 T/C 区寄存器;项目不把梯形图下载到 PLC,因此真机模式只通信访问新指令引用的 M/D 地址。

工程级 registerComments 只保存 M/D 地址和注释文本,作为地址元数据供编辑器和注释对话框使用,不保存当前值、引用位置或缓存状态,也不会因为存在注释而加入 PLC 轮询。LadderRung.comment 保存单个网络的说明文本。两者在 JSON 中分开建模,不能重新引入混合“名称、注释、当前值、引用位置”的数据点表。

自由监控与运行工作台

自由监控是当前调试会话中的寄存器诊断工具,不属于工程模型,也不写入工程 JSON。用户可输入
M0~M4000D0~D4000,并按连续个数批量添加地址。监控表最多保留 64 个去重地址,M
值显示为 ON/OFF,D 值按带符号 16 位十进制显示。

RegisterMonitorService 负责地址解析、范围展开、去重、增删和通过活动寄存器仓库读取值。
FreeMonitorWidget 只投影服务结果,不直接访问虚拟仓库、PLC 缓存或串口。监控地址在离线和
真机运行期间均可增删,因为它们属于调试操作而不是工程结构编辑。

主窗口在进入运行模式后自动打开运行监控工作台:

模式 同时显示 统一数据源
离线运行 可导航 HMI、当前所选逻辑轨迹、自由监控 虚拟 M/D
真机运行 HMI、自由监控 PLC 缓存

真机模式不显示本地梯形图导通轨迹。当前项目不能读取 PLC 内部程序和网络执行轨迹,本地
梯形图也不保证与 PLC 中的程序一致,因此不得把本地条件推演伪装成 PLC 实际运行状态。

寄存器与 Modbus RTU

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

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

PlcCommunicationService 使用 QModbusRtuSerialMaster 异步连接、读取和写入。轮询集合由
HMI 引用、报警定义、梯形图中的 M/D 引用和自由监控地址合并而成,去重后按区域合并相邻地址。
T/C 触点、TON、CTU/CTD 的 C 资源和工程级注释不会进入该集合;只有计数器 CV、PV 的 D 引用、复位 M 以及 MOVE/ADD/SUB 的 M/D 条件和操作数会进入集合。M 使用线圈,D 使用保持寄存器;所有 Modbus 地址均为从 0 开始的原始地址。

PLC 连接后可以动态增删自由监控地址。存在读取请求时,新的轮询集合在当前响应处理完成后
应用,旧响应仍按发出时的读取块解析。连接级首次读取完成后,动态增加地址不会撤销真机
运行资格;新增地址自身在首次读回前显示“等待读取”。如果连接首次读取尚未完成,新的完整
轮询集合会共同参与首次读取判定。

HMI 写入只表示异步请求已受理,不乐观修改 PLC 缓存,实际值由后续读回确认。协议错误或断线会停止轮询、保留最后一次有效值并进入故障状态,错误通过运行模式服务交给 UI 展示。
PLC 未响应或通信超时且本地串口仍然打开时,通信服务停止正常高频轮询,每次探测失败后等待
2 秒,再只读探测一次当前轮询集合的首个地址。探测失败不重复打印同一超时日志;探测成功后进入恢复状态,
重新读取全部轮询地址并重建当前连接的首次读取资格,完整首读完成后才恢复为已连接状态。

通信故障会立即撤销当前连接的首次读取资格。若故障发生在真机运行态,运行模式服务自动返回
编辑态,PLC 最后一次有效缓存仍保留用于日志和故障诊断,但不能作为再次进入真机运行态的依据。
重新进入真机运行必须等待当前串口会话恢复或重新连接 PLC,并重新完成首次读取。

通信超时后的自动探测只恢复通信和缓存,不自动恢复真机运行。接线或 PLC 供电恢复后,应用
完成新的首次读取并继续停留在编辑态,由用户手动决定是否重新进入真机运行。“断开 PLC”
在超时和恢复过程中保持可用,用于关闭并释放仍然打开的本地串口会话。

通信服务根据 Qt Modbus 错误和串口是否曾成功打开区分连接阶段失败与运行中连接中断。连接阶段
无法打开串口时直接保持未连接状态;已经连接后收到 ConnectionError 或意外进入
UnconnectedState 时,统一报告本地串口连接中断,自动清理会话并进入未连接状态。错误日志
独立保留,不会为了显示故障而继续把串口会话表示为已连接。

运行模式边界

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

离线运行与真机运行不得直接互切。进入真机模式不复制或下发离线值;PLC 内部程序是控制源,PC 端软件逻辑执行器必须停止。

测试边界

优先级 测试对象 重点内容
领域模型 地址边界、表达式校验与归一化、工程唯一性
编辑服务 嵌套串并联插入、删除和地址配置
执行与模式服务 递归求值、轨迹、离线闭环、仓库切换和首次读取约束
工程存储 1.0 往返、缺失字段、非法结构和不支持版本
PLC 通信 缓存有效性、地址集合、异步读写和错误反馈
UI 编辑、运行切换、选择和错误定位工作流
冒烟 真机 STOP 状态安全读写 M0/D0,随后 RUN 联动

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