3、HMI软件架构:前端显示层、中间逻辑层、后端驱动层、数据流走向
聊到HMI的软件架构,很多人第一反应就是“画界面”。其实不然。一个真正能稳定运行在工业现场的HMI,它的内部结构远比表面看到的复杂。我个人习惯把HMI软件拆成三个层次来看:前端显示层、中间逻辑层、后端驱动层。这三层各司其职,又通过数据流紧密咬合。
说白了,这就是一个“怎么把按钮按下去的动作,变成电机转起来的指令”的过程。你想想看,如果这三层不分清楚,代码写到最后就是一锅粥。我在项目中遇到过好几次,就是因为层与层之间的职责没划清,后期维护简直要命。
3.1 三层架构总览
我们先从整体上看看这三层分别干什么。下面这张图是我每次给团队做架构评审时必画的,它清晰地展示了数据从用户手指到物理设备的完整路径。
核心原则:每一层只做自己的事。前端不直接写PLC寄存器,后端不渲染按钮。这个原则一旦打破,代码就会变得难以测试和移植。
3.2 前端显示层:不只是“画皮”
前端显示层是用户直接看到、摸到的那一层。很多人觉得这层就是美工活,其实不然。一个优秀的HMI前端,要处理的事情不少:
- 画面渲染:无论是Qt QML还是Web HMI,都要保证画面刷新率不低于30fps。工业现场如果画面卡顿,操作员会疯掉。
- 用户输入捕获:触摸点击、键盘快捷键、鼠标滚轮。这些事件要第一时间捕获并打包成标准指令。
- 本地动画与反馈:按钮按下要有视觉反馈,报警弹窗要有闪烁效果。这些不能依赖后端,必须前端本地完成。
我的经验:我曾经在一个项目中,前端直接去轮询PLC的寄存器来刷新画面。结果画面卡得一塌糊涂。后来改成前端只显示中间层推送过来的缓存数据,流畅度瞬间提升。记住,前端不要直接跟硬件通信。
3.3 中间逻辑层:HMI的“大脑”
中间逻辑层是整个架构的核心。它像是一个翻译官,把前端那些“花里胡哨”的用户操作,翻译成后端能理解的“冷冰冰”的协议指令。反过来,它又把后端采集上来的原始数据,加工成前端能展示的有意义信息。
这一层通常包含以下关键模块:
| 模块 | 职责 | 常见问题 |
|---|---|---|
| 变量管理 | 维护所有HMI变量的实时值、类型、地址映射 | 变量名混乱,地址映射写死,导致换PLC型号时改到崩溃 |
| 报警引擎 | 根据变量值判断是否触发报警,记录报警历史 | 报警频率过高导致日志爆满,没有做报警死区处理 |
| 配方管理 | 存储和切换不同产品的工艺参数组 | 配方文件格式不统一,导入导出容易出错 |
| 权限校验 | 区分操作员、工程师、管理员的不同操作权限 | 权限校验放在前端,容易被绕过 |
| 日志记录 | 记录操作事件、系统事件、数据变更 | 日志没有分级,排查问题时大海捞针 |
避坑指南:我曾经见过一个项目,把配方管理逻辑写在了前端JavaScript里。结果换了一个浏览器版本,脚本跑不起来了。所有业务逻辑必须放在中间层,前端只负责展示和交互。
3.4 后端驱动层:与硬件“对话”的翻译官
后端驱动层是HMI与物理世界之间的桥梁。它负责把中间层发来的“我想让电机转速设为1500转”这个意图,转换成Modbus RTU报文或者Profinet数据帧,通过串口或以太网发给变频器。
这一层有几个关键点:
- 协议解析:不同的PLC和驱动器使用不同的协议。Modbus、Profinet、EtherCAT、OPC UA……一个HMI往往要同时支持好几种。
- 通信管理:处理连接建立、心跳维持、断线重连、超时重试。工业现场通信环境复杂,线缆松动、电磁干扰都是家常便饭。
- 数据采集:按照设定的周期(比如100ms)轮询设备寄存器,或者订阅OPC UA的变量变化通知。
一个常见的误解:很多人觉得驱动层就是“调库”。其实不然。驱动层要处理字节序(大端/小端)、数据类型转换(Int16转Float32)、位操作(一个寄存器里不同位代表不同状态)。这些细节搞错了,数据就是错的。
3.5 数据流走向:从手指到电机,再回到屏幕
理解了每一层的职责,我们来看看一次完整的交互,数据是怎么流动的。以“操作员点击启动按钮”为例:
- 前端显示层:捕获到按钮点击事件,打包成一条指令:“用户请求启动设备#1”。通过内部接口发送给中间逻辑层。
- 中间逻辑层:收到指令后,先做权限校验(当前用户是否有启动权限?),再做逻辑判断(设备当前是否处于待机状态?)。校验通过后,生成一条写指令:“将PLC寄存器%MW100.0 写为1”。发送给后端驱动层。
- 后端驱动层:将写指令封装成Modbus TCP报文,通过Socket发送给PLC。同时启动一个超时定时器,等待PLC响应。
- PLC执行:PLC收到报文,解析后置位启动线圈,电机开始运转。PLC将状态寄存器更新为“运行中”。
- 后端驱动层:轮询到PLC的状态寄存器变化,读取到“运行中”状态。将原始数据(比如寄存器值0x0001)上报给中间逻辑层。
- 中间逻辑层:将原始数据映射为HMI变量“设备1_状态 = 运行中”。推送给前端显示层。
- 前端显示层:收到状态更新,将启动按钮变为绿色常亮,并在状态栏显示“设备1 运行中”。
你看,一次简单的点击,背后是七步数据流转。任何一个环节出问题,操作员看到的可能就是“点了没反应”。
我建议:在设计数据流时,一定要考虑异常路径。比如超时了怎么办?通信断了怎么办?设备报错了怎么办?这些异常处理代码,往往比正常流程的代码还要多。
3.6 架构设计的几个关键原则
最后,我总结几条我在实际项目中反复验证过的原则,供你参考:
| 原则 | 说明 | 违反的后果 |
|---|---|---|
| 单向依赖 | 前端依赖中间层,中间层依赖后端。不能反向依赖。 | 代码耦合严重,改一处动全身 |
| 接口隔离 | 层与层之间通过定义好的接口通信,不直接访问内部实现。 | 单元测试无法做,换驱动层得重写前端 |
| 数据缓存 | 中间层维护一份实时数据缓存,前端和驱动都不直接持有数据。 | 前端频繁读驱动,性能灾难 |
| 异步通信 | 层与层之间的调用尽量异步,避免阻塞UI线程。 | 操作时界面卡死,用户体验极差 |
嗯,这三层架构说起来简单,真正落地的时候,细节非常多。但只要你把每一层的边界划清楚,数据流的方向理明白,后面的开发就会顺畅很多。我见过太多项目,前期架构没想清楚,后期天天加班改bug。希望你能从一开始就把这个基础打好。