综合平台编程器项目的远程存储
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 

15 KiB

综合平台编程器架构

目标与范围

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

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

分层与依赖

app/src/
├── domain/          无 Qt 依赖的模型、值对象、校验和仓库接口
├── services/        编辑、工程、仿真、运行模式和通信契约
├── infrastructure/  JSON、PLC 缓存、Qt Modbus RTU 实现
├── ui/              Qt Widgets 视图、对话框和界面控制器
└── main.cpp         对象创建与依赖注入

生产代码依赖方向固定为:

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

主窗口静态布局由 main_window.ui 管理。UI 协调分为:

组件 职责
ProjectWorkspaceController 工程树、当前页面/逻辑会话选择和编辑对象刷新
PropertyPanelController HMI/梯形图选择、属性表单和编辑服务调用
RuntimePanelController 运行会话、顶层运行监控窗口、运行值刷新和仿真故障反馈
RuntimeMonitorWindow 独立顶层窗口的显示、隐藏、关闭请求和应用退出生命周期
RuntimeMonitorWidget 全应用唯一运行投影,组合可交互 HMI、梯形图轨迹和自由监控
MainWindow 窗口级动作、工程文件操作、控制器组合和模式切换

运行界面统一投影到独立顶层 RuntimeMonitorWindow 中的唯一 RuntimeMonitorWidget。该窗口是编程器的工程师运行监控大屏,不是旧的纯 HMI 操作员窗口:

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

工程模型与持久化

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

工程 JSON 格式固定为严格的 1.0。加载顺序为 JSON 解析、版本检查、字段解析、领域校验,全部成功后才能替换当前工程;保存使用原子提交。当前版本不读取旧 dataPointsstages/branches,也不迁移缺少显式横线、断路或其他必填结构的早期同版本草稿。字段定义见 docs/工程格式说明.md

校验分两级:

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

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

应用启动配置

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

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

结构化梯形图

网络条件保存为规范化表达式树:

ConditionExpression
├── Node       触点、边沿或比较条件
├── Wire       无地址横线,保存条件区列跨度
├── Gap        明确断开的空白网格,保存条件区列跨度
├── Series     子表达式执行 AND
└── Parallel   子表达式执行 OR

输出是网络最右侧的单独节点。LogicEditorService 把串联、并联、旁路、替换和删除转换为表达式树操作,失败时回退整个编辑;LogicEditorWidget 只把树投影成正交网格。竖线由 Parallel 边界派生,网格坐标和自由线段不持久化,因此领域模型不会产生悬空线、环路或多输出路径。

单网络前 10 列为条件区,第 11 列固定为输出位置;存在输出时,前 10 列必须由 Node / Wire / Gap 显式占满。横线和断路只能占条件区。第 11 个条件、未占满或超过 10 列的输出网络、过深表达式或过多并联行会在编辑和加载阶段被拒绝,具体数值以数量边界文档为准。

控制逻辑组可以暂时没有网络。新建工程和新建控制逻辑都保持 rungs=[],用户第一次添加条件、横线或输出时,编辑服务在同一个原子操作中创建网络;删除最后一个网络后也允许回到空逻辑。条件区中每一段可见、可选择和可删除的横线都必须是持久化 Wire;没有横线的位置由 Gap(columnSpan) 明确占位。直接添加输出会创建 Wire(10) + Output,一个触点后添加输出会创建 Node + Wire(9) + Output。删除触点或横线只把对应网格替换为 Gap,不会让左右两侧自动重新接通;在该网格重新添加横线或触点后才恢复连接。并联各支路必须用真实 WireGap 补到相同列宽。左右母线、节点和输出图元自身的端子线、并联竖线仍由结构派生,不保存为自由像素线。连续追加单格横线占满十个条件列后,下一次无选中目标的追加会在同一撤销记录中创建下一个网络。

网络运行语义:

  • 空网络不执行
  • 有条件但没有输出的已启用网络不能运行
  • 直接接通的输出使用 Wire(10) 显式表示恒真路径,每次扫描执行输出
  • Wire 的逻辑值为真,但运行轨迹是否导通仍取决于上游电流
  • Gap 表示明确断路,可以保存编辑草稿,但任何包含 Gap 的启用网络都不能进入运行态

节点配置使用 std::variant 的独立类型。当前条件包括 M 常开/常闭触点、M 上升沿/下降沿触点和 D 与常量比较;输出包括普通/置位/复位 M 线圈、MOVE、ADD 和 SUB。新增或恢复指令必须增加独立配置、校验、JSON、执行、编辑和测试分支,不能向通用节点堆叠无关字段。

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

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

数据源与运行模式

运行界面只访问 RegisterRepositoryActiveRegisterRepository 根据模式切换实际数据源:

编辑:UI -> Editor Services -> ProjectService -> Project
                                  └── ProjectStorage -> JSON

离线:运行监控弹窗 -> ActiveRegisterRepository -> VirtualRegisterRepository
                                                 ^
                            OfflineSimulationService(SoftwareLogicExecutor)

真机:运行监控弹窗 -> ActiveRegisterRepository -> PlcRegisterRepository(cache)
                                                     ^
PLC <-> PlcCommunicationService <--------------------┘

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

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

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

Modbus RTU 通信

PlcCommunicationService 使用 QModbusRtuSerialMaster 异步连接、轮询和单点写入,UI 线程不等待串口响应。Modbus 地址使用从 0 开始的原始地址。

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

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

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

通信状态包括 Disconnected / Connecting / Connected / Recovering / Faulted。连接失败或物理断线会关闭串口会话并进入 Disconnected;超时、协议或读写故障会停止正常轮询、保留最后有效缓存并进入 Faulted。所有通信故障都会撤销首读资格;若当时处于真机运行,运行模式服务立即返回编辑态。可恢复超时探测成功后进入 Recovering 并重新完整首读,但不会自动重新进入真机运行。

扩展规则

  • 新业务先确定所属领域模型和服务用例,再添加 UI;不得从 UI 直接改工程容器或串口状态
  • 编辑操作必须在服务层校验并原子提交,失败不能污染撤销历史或留下半成品
  • 新持久化字段必须同步领域校验、严格 JSON 往返、缺失/非法字段测试和格式说明;是否升级格式版本必须明确决定
  • 新数量边界必须进入 project_limits.h、数量边界文档和上下界测试
  • 新 HMI 类型优先扩展 hmi_control_registry,专用交互留在对应服务和图元
  • 不重新引入数据点表、自由线段、UI 直连串口,也不得把真机本地梯形图的临时输出写入 PLC
  • 自动化测试覆盖领域、服务、JSON 存储和 Fake PLC 通信状态;UI 工作流在交付前人工验收,真实串口、接线和设备 RUN 联动必须现场验证