工业现场的数据上云,很多人卡在第一步:PLC 里的数据怎么稳定、低成本地送到 Web 端。传统做法是买一套组态软件加授权,成本高、扩展性差,想接个自定义看板或者推给第三方系统,处处受限。这两年我陆续做了几个从 PLC 采集到 Web SCADA 展示的完整项目,从台达、汇川、西门子 S7-200 SMART 到三菱 FX 系列都碰过,最终沉淀出一套相对通用的方案:工业智能网关负责现场协议采集,MQTT 做消息总线,Node.js 做数据接入与 Web 服务。整套链路跑通之后,加一个采集点、换一个展示页面,成本几乎可以忽略。这篇就把这套方案从选型、搭建到落地的完整过程拆开讲,适合有 PLC 基础但没接触过物联网链路的朋友,也适合想从传统组态转向 Web SCADA 的工程师参考。
1. 为什么放弃传统组态,转向网关加 MQTT 加 Node.js
1.1 传统上位机方案的三个硬伤
先说清楚为什么要换方案,不然很容易陷入"能用就行"的惯性。传统上位机方案通常是 PLC 通过串口或以太网连到一台工控机,工控机上跑组态软件,组态软件再通过 OPC 或者自有协议把数据给到其他系统。这套东西在单机、单车间场景下没问题,但一旦要联网、要多端展示、要对接 MES 或者云平台,问题就集中爆发了。
第一个硬伤是授权成本与点数绑定。组态软件的点数授权是线性涨价的,一个项目几百个点,授权费用可能比硬件还贵。而且很多组态软件的外部接口是单独收费的,想通过 API 把数据推出去,又是一笔钱。
第二个硬伤是协议封闭。组态软件采集上来的数据,往往只能在自己的生态里流转。想接一个自定义的 Web 看板,或者把数据推给第三方的数据分析平台,要么买它的接口模块,要么做二次开发,周期长、依赖厂商。
第三个硬伤是部署与运维重。一台工控机放在现场,Windows 系统要打补丁、要防病毒、要防断电,组态软件升级还可能影响正在跑的项目。现场环境粉尘大、温度高,工控机硬盘故障是家常便饭。
1.2 网关加 MQTT 加 Node.js 的分工逻辑
换成网关加 MQTT 加 Node.js 之后,这三者的分工非常清晰,各干各擅长的事。
工业智能网关负责最底层的协议适配。它向下通过 RS485、RS232、以太网等物理接口连接 PLC、变频器、仪表,向上把采集到的数据统一成一种轻量的消息格式。网关的核心价值在于它内置了大量工业协议驱动,比如 Modbus RTU、Modbus TCP、西门子 S7 协议、三菱 MC 协议、欧姆龙 FINS 等,你不需要自己写协议解析代码,配置一下寄存器地址就能读到数据。
MQTT负责消息的传输与分发。它是一种发布订阅模式的消息协议,特别适合工业场景:带宽占用小、支持断线重连、支持 QoS 等级、天然支持一对多分发。网关把数据发布到某个主题,任何订阅了这个主题的客户端都能收到,Web 服务、手机 App、数据分析程序可以同时订阅,互不干扰。
Node.js负责数据接入与 Web 服务。它订阅 MQTT 主题,把数据存下来(内存、时序数据库或者关系库),同时对外提供 HTTP 接口和 WebSocket 推送,前端页面就能实时刷新。Node.js 的事件驱动模型特别适合这种大量并发连接、低计算量的场景,一台普通服务器扛几千个 MQTT 连接和 WebSocket 连接毫无压力。
1.3 这套方案适合什么样的项目
不是所有项目都适合这套方案,我把它适用的边界说清楚,避免生搬硬套。
适合的场景:需要多端展示(PC、手机、大屏)、需要对接第三方系统、采集点分散在多个车间或厂区、预算有限但要求可扩展、需要做数据留存和分析。
不太适合的场景:对实时性要求极高(毫秒级闭环控制)、现场完全没有网络基础设施且不允许布线、项目规模极小(就十几个点且永远不扩展)。这些场景用传统组态或者本地 HMI 反而更省事。
提示:网关加 MQTT 加 Node.js 这套链路,本质上是把"采集"和"展示"解耦。采集端只管把数据发出来,展示端只管订阅。这个解耦是整套方案灵活性的来源,也是后面所有设计决策的出发点。
2. 工业智能网关的选型与现场接线要点
2.1 网关选型要看哪几个参数
市面上的工业智能网关品牌很多,价格从几百到几千不等。选型的时候不要只看价格,下面这几个参数直接决定项目能不能顺利落地。
| 参数项 | 说明 | 建议 |
|---|---|---|
| 下行协议支持 | 网关能对接的 PLC 和仪表协议 | 至少覆盖 Modbus RTU/TCP,西门子、三菱、台达、汇川等主流品牌驱动要齐全 |
| 上行协议支持 | 网关对外发送数据的协议 | 必须支持 MQTT,最好同时支持 HTTP、Modbus TCP 转发的 |
| 串口数量 | RS485/RS232 接口数量 | 现场仪表多的话,至少 2 路 RS485 |
| 网口数量 | 以太网接口数量 | 至少 1 路,用于连接 PLC 和上行网络 |
| 采集点数 | 支持的最大采集变量数 | 按实际点数的 1.5 倍预留 |
| 边缘计算能力 | 是否支持脚本、公式、报警 | 有的话可以做数据预处理,减轻服务端压力 |
| 断网缓存 | 断网时是否缓存数据 | 工业现场网络不稳定,这个功能很关键 |
我个人的经验是,下行协议驱动的齐全程度比硬件参数更重要。有些网关硬件配置很漂亮,但某个品牌的 PLC 驱动写得不好,读数据经常超时或者读错地址,这种坑非常难排查。选型前最好拿现场实际的 PLC 型号问厂商要一份驱动支持列表,确认能读能写。
2.2 现场接线与网络规划
接线这块看似简单,但现场出问题十有八九是接线和网络规划没做好。
串口接线要注意 RS485 的 A、B 线不要接反,接反了通信不上但不会烧设备,排查起来很费时间。多个从站挂在同一条 RS485 总线上时,要采用手拉手的方式,不要星型分支,分支会导致信号反射。总线两端要接终端电阻,一般 120 欧姆。波特率、数据位、停止位、校验位这几个参数,网关和 PLC 必须完全一致,差一个都通不了。
网络规划方面,如果 PLC 是网口通信,网关和 PLC 要在同一个网段。我习惯给网关单独规划一个管理网段,和办公网隔离,避免办公网的广播风暴影响采集。如果现场有多台网关,每台的 IP 要提前规划好,做好台账,不然后期维护找不到设备。
供电方面,网关一般用 24V 直流供电,要和 PLC 的供电分开走线,避免 PLC 启停时的电压波动影响网关。有条件的话给网关配一个小型 UPS,断电时能撑几分钟,让网关把缓存数据发出去。
2.3 网关侧的数据点配置
网关配置的核心工作是建立数据点表。所谓数据点表,就是把 PLC 里的寄存器地址、数据类型、缩放系数、单位等信息整理成一张表,然后在网关的配置界面里逐个录入。
以读取西门子 S7-200 SMART 的一个温度值为例,假设温度存在 VW100 寄存器里,实际值是放大 10 倍的整数(比如 256 表示 25.6 摄氏度),那么配置大概是这样的:
- 变量名:
temp_01 - 寄存器地址:
VW100 - 数据类型:
int16 - 缩放系数:
0.1 - 单位:
摄氏度 - 采集周期:
1000ms
这里有个容易忽略的点:采集周期不是越短越好。Modbus RTU 是轮询机制,如果一条总线上挂了 10 个从站,每个从站采集周期都设 100ms,那总线根本忙不过来,会出现大量超时。合理的做法是按数据变化频率分组,变化快的(比如电机电流)设 500ms 到 1s,变化慢的(比如温度、液位)设 5s 到 10s。
注意:网关配置完成后,一定要用网关自带的调试工具或者 MQTT 客户端先确认数据能正常发出来,再去搭后面的服务端。现场排查问题的成本远高于在办公室排查。
3. MQTT 服务端的搭建与主题设计
3.1 MQTT 服务端选型与安装
MQTT 服务端(Broker)是整个链路的中枢,所有数据都经过它转发。选型上,开源方案里 Mosquitto 轻量、稳定、资源占用低,适合中小规模项目;EMQX 功能更全,支持集群、规则引擎、Dashboard,适合规模较大或者需要做数据桥接的场景。
以 Mosquitto 为例,在 Linux 服务器上安装非常直接。CentOS 7.9 环境下:
# 安装 mosquitto yum install -y epel-release yum install -y mosquitto mosquitto-clients # 启动并设置开机自启 systemctl start mosquitto systemctl enable mosquitto # 查看状态 systemctl status mosquitto默认配置下 Mosquitto 只监听本地 1883 端口,如果要让网关从外部连进来,需要修改配置文件/etc/mosquitto/mosquitto.conf:
# 监听所有网卡的 1883 端口 listener 1883 0.0.0.0 # 允许匿名连接(内网测试用,生产环境务必关闭) allow_anonymous true生产环境一定要关闭匿名连接,配置用户名密码认证。Mosquitto 的用户管理用mosquitto_passwd命令:
# 创建密码文件并添加用户 mosquitto_passwd -c /etc/mosquitto/passwd gateway_user # 按提示输入密码 # 再添加一个 Web 服务用的用户 mosquitto_passwd /etc/mosquitto/passwd web_user然后在配置文件里指定密码文件并关闭匿名:
allow_anonymous false password_file /etc/mosquitto/passwd改完配置记得重启服务,并且检查防火墙有没有放行 1883 端口。
3.2 主题层级的设计原则
MQTT 的主题设计是很多人忽略但影响深远的一件事。主题设计得好,后期扩展、权限控制、数据分流都很轻松;设计得乱,后面想加个功能就得改所有客户端。
主题用斜杠分层,我一般按"项目/厂区/设备类型/设备编号/数据类型"这样的层级来设计。举个例子:
factory_a/workshop_1/plc/plc_001/realtime factory_a/workshop_1/plc/plc_001/alarm factory_a/workshop_1/meter/meter_005/realtime这样设计的好处是,订阅的时候可以用通配符灵活匹配。+匹配单层,#匹配多层。比如:
factory_a/workshop_1/plc/+/realtime订阅一号车间所有 PLC 的实时数据factory_a/#订阅 A 厂区所有数据factory_a/workshop_1/plc/plc_001/#订阅某台 PLC 的所有类型数据
主题里不要放具体数值,数值放在消息体里。主题只用来标识"这是什么数据",消息体里放"数据是多少、什么时间采集的"。消息体用 JSON 格式,可读性和扩展性都好:
{ "deviceId": "plc_001", "timestamp": 1735689600000, "values": { "temp_01": 25.6, "pressure_01": 0.85, "motor_speed": 1450 } }3.3 QoS 等级与保留消息的取舍
MQTT 有三个 QoS 等级,很多人不知道该选哪个。简单说:
- QoS 0:最多发一次,不保证到达。适合高频、可丢失的数据,比如实时曲线刷新。
- QoS 1:至少发一次,可能重复。适合大多数工业数据采集场景。
- QoS 2:恰好发一次,开销最大。适合计费、报警确认这类不能重复不能丢失的场景。
工业采集我一般用 QoS 1。因为网关和 Broker 之间的网络相对稳定,QoS 1 的开销可以接受,而且能保证数据不丢。QoS 2 的四次握手在高频采集下开销太大,不划算。
保留消息(Retained Message)是个很实用的特性。当网关把一条消息设为保留消息发布后,Broker 会保存这条消息的最后一条,任何新订阅该主题的客户端会立刻收到这条消息。这个特性特别适合 Web 页面首次加载时快速拿到当前值,不用等下一次采集周期。
但保留消息也有坑:如果主题设计得太多太细,每条都设保留消息,Broker 的内存会被大量占用。我的做法是只对"当前值"这类主题设保留消息,历史数据主题不设。
3.4 用 MQTT Explorer 做链路验证
在正式写 Node.js 代码之前,强烈建议先用 MQTT Explorer 这个图形化客户端验证链路。它能连上 Broker,订阅主题,实时看到消息内容,还能看到保留消息和主题树结构。
验证步骤很简单:打开 MQTT Explorer,填入 Broker 地址、端口、用户名密码,连接成功后订阅factory_a/#,然后看网关有没有数据发过来。如果能看到数据,说明网关到 Broker 这一段通了,接下来写 Node.js 就只是订阅和存储的事。如果看不到,问题就在网关侧或者网络侧,先解决这一段。
这个"分段验证"的思路非常重要。整条链路涉及网关、网络、Broker、Node.js、前端多个环节,一次性全搭好再调试,出了问题根本不知道是哪一段。分段验证,每段确认通了再往下走,效率高得多。
4. Node.js 数据接入服务的实现
4.1 Node.js 环境准备与版本选择
Node.js 的安装本身不复杂,但版本选择有讲究。工业项目我建议用 LTS(长期支持)版本,比如 20.x 或者 22.x 的 LTS。不要用最新的实验版本,也不要用太老的版本,老版本可能不支持某些新语法和库。
Windows 上直接去官网下载安装包,一路下一步就行。Linux 上推荐用 NodeSource 的源安装,比系统自带的版本新:
# CentOS 7.9 安装 Node.js 20.x curl -fsSL https://rpm.nodesource.com/setup_20.x | bash - yum install -y nodejs # 验证安装 node -v npm -v如果服务器不能联网,需要离线安装,那就下载对应的二进制包解压,配置环境变量。麒麟 V10 ARM 架构的服务器也是类似思路,下载 ARM64 的包。
验证是否安装成功,除了node -v,还可以跑一句node -e "console.log('ok')",能输出 ok 就说明环境没问题。
4.2 用 mqtt.js 订阅网关数据
Node.js 里操作 MQTT 最常用的库是mqtt(也就是 mqtt.js)。先初始化项目并安装依赖:
mkdir scada-server && cd scada-server npm init -y npm install mqtt然后写一个最简的订阅脚本:
const mqtt = require('mqtt'); const client = mqtt.connect('mqtt://127.0.0.1:1883', { username: 'web_user', password: 'your_password', clientId: 'scada_server_' + Math.random().toString(16).slice(2, 8), clean: true, reconnectPeriod: 5000 }); client.on('connect', () => { console.log('MQTT connected'); client.subscribe('factory_a/#', { qos: 1 }, (err) => { if (err) console.error('subscribe failed', err); else console.log('subscribed'); }); }); client.on('message', (topic, payload) => { try { const data = JSON.parse(payload.toString()); console.log(topic, data); // 这里做数据存储和转发 } catch (e) { console.error('parse error', e.message); } }); client.on('error', (err) => { console.error('MQTT error', err); });几个关键点说明一下。clientId要保证唯一,否则两个客户端用同一个 clientId 会互相踢下线。reconnectPeriod设置自动重连间隔,网络抖动时能自动恢复。clean: true表示不保留会话状态,如果希望断线重连后还能收到离线期间的消息,要设成false并配合固定的 clientId。
4.3 数据落库:内存、时序库还是关系库
数据订阅到之后,存哪里是个关键决策。三种选择各有适用场景。
内存存储最简单,用一个 Map 存每个设备的最新值,Web 页面通过 WebSocket 拿实时数据。优点是快,缺点是重启就丢,且不能查历史。适合只做实时监控、不需要历史的场景。
时序数据库比如 InfluxDB、TDengine,专门为时间序列数据设计,写入快、压缩率高、按时间范围查询效率高。适合需要存历史曲线、做趋势分析的场景。TDengine 对工业场景支持很好,还有免费的社区版。
关系数据库比如 MySQL、PostgreSQL,适合需要和业务数据关联的场景,比如把采集数据和工单、设备台账关联起来。但高频写入对关系库压力较大,一般会做降采样,比如原始数据存时序库,分钟级聚合数据存关系库。
我实际项目里最常见的组合是:内存存最新值 + 时序库存原始数据 + 关系库存聚合和业务数据。三者各司其职,查询的时候按需取。
4.4 对外提供 HTTP 接口与 WebSocket 推送
Node.js 服务订阅到数据后,要对外提供两种接口:一种是 HTTP 接口,用于查询历史数据、设备列表、配置信息;另一种是 WebSocket,用于实时推送。
HTTP 接口用 Express 或者 Fastify 都很方便。WebSocket 用ws库。下面是一个把 MQTT 数据和 WebSocket 打通的最小示例:
const express = require('express'); const http = require('http'); const WebSocket = require('ws'); const mqtt = require('mqtt'); const app = express(); const server = http.createServer(app); const wss = new WebSocket.Server({ server }); // 存最新值 const latestValues = new Map(); // WebSocket 连接管理 wss.on('connection', (ws) => { console.log('ws client connected'); // 连接建立时先把当前所有最新值推一遍 ws.send(JSON.stringify({ type: 'snapshot', data: Object.fromEntries(latestValues) })); }); // MQTT 订阅 const client = mqtt.connect('mqtt://127.0.0.1:1883', { username: 'web_user', password: 'your_password' }); client.on('connect', () => { client.subscribe('factory_a/#', { qos: 1 }); }); client.on('message', (topic, payload) => { const data = JSON.parse(payload.toString()); latestValues.set(topic, data); // 广播给所有 WebSocket 客户端 const msg = JSON.stringify({ type: 'update', topic, data }); wss.clients.forEach((ws) => { if (ws.readyState === WebSocket.OPEN) ws.send(msg); }); }); // HTTP 接口 app.get('/api/latest', (req, res) => { res.json(Object.fromEntries(latestValues)); }); server.listen(3000, () => console.log('server on 3000'));这段代码虽然简单,但已经包含了完整的核心逻辑:订阅、缓存、广播、查询。实际项目里再往上加认证、日志、错误处理、数据落库即可。
提示:WebSocket 广播的时候要注意,如果客户端很多,逐个 send 会有性能问题。生产环境可以用发布订阅模式在服务内部再做一层分发,或者用 Socket.IO 这类封装好的库。
5. 从数据到画面:Web SCADA 前端的实现思路
5.1 实时数据绑定的基本模式
前端拿到 WebSocket 推来的数据后,要把它绑定到画面上。最朴素的做法是每个数据点对应一个 DOM 元素,收到更新就改这个元素的文本或者样式。但工业画面往往有几百个点,逐个操作 DOM 性能很差。
更好的做法是数据驱动视图。用 Vue 或者 React 这类框架,把数据放在响应式对象里,视图自动更新。比如用 Vue:
// 数据存储 const state = reactive({ devices: {} }); // WebSocket 收到消息后更新 ws.onmessage = (e) => { const msg = JSON.parse(e.data); if (msg.type === 'update') { state.devices[msg.topic] = msg.data; } };模板里直接绑定state.devices['factory_a/workshop_1/plc/plc_001/realtime'].values.temp_01,数据一变视图就变,不用手动操作 DOM。
5.2 用 SVG 或 Canvas 绘制工艺画面
Web SCADA 的画面通常包括设备图形、管道、仪表盘、趋势曲线。绘制方式有两种主流选择:SVG 和 Canvas。
SVG是矢量图形,每个图形元素都是 DOM 节点,可以用 CSS 和 JavaScript 直接操作,适合画面元素不多、需要交互(点击、悬停)的场景。工艺流程图、设备状态图用 SVG 很合适。
Canvas是位图绘制,性能好,适合元素非常多或者需要高频重绘的场景,比如实时趋势曲线、大量数据点的散点图。但 Canvas 里的元素不是 DOM,交互要自己算坐标。
实际项目里经常混用:工艺画面用 SVG,趋势曲线用 Canvas(或者用 ECharts 这类图表库,它底层用 Canvas)。
5.3 趋势曲线与报警展示
趋势曲线是 Web SCADA 的刚需。实现方式有两种:一种是前端定时从 HTTP 接口拉历史数据,用 ECharts 画出来;另一种是 WebSocket 推实时数据,前端维护一个滑动窗口,不断往曲线里追加点。
实时曲线要注意数据降采样。如果采集周期是 1 秒,一条曲线显示 1 小时就是 3600 个点,浏览器画起来会卡。通常的做法是前端按像素宽度降采样,比如屏幕宽 800 像素,就只取 800 个点,其余合并。
报警展示要区分实时报警和历史报警。实时报警用 WebSocket 推送,弹窗或者列表高亮;历史报警存数据库,按时间范围查询。报警的触发条件可以在网关侧做(边缘计算),也可以在 Node.js 侧做。我倾向于在 Node.js 侧做,因为规则修改方便,不用重新配置网关。
5.4 移动端适配与多端展示
工业现场越来越多地用平板和手机看数据。前端要做响应式,或者单独做移动端页面。核心是布局要自适应,触摸操作要友好(按钮大一点,避免误触)。
多端展示的关键是后端接口统一。PC、平板、手机、大屏都调同一套 HTTP 和 WebSocket 接口,前端各自渲染。这样后端只维护一套逻辑,前端按设备类型做不同的展示。
大屏展示要特别注意分辨率和刷新频率。大屏一般是 1920x1080 或者更高,浏览器全屏运行,长时间不刷新容易内存泄漏。建议大屏页面定时重载,比如每天凌晨自动刷新一次。
6. 现场落地踩过的坑与排查经验
6.1 网关读不到数据的排查链路
网关读不到 PLC 数据,是现场最高频的问题。我的排查顺序是这样的:
第一步,确认物理连接。串口的话看接线对不对、终端电阻有没有、波特率等参数是否一致;网口的话 ping 一下 PLC 的 IP,看通不通。
第二步,确认协议参数。站号、寄存器地址、数据类型这些,一个都不能错。特别是寄存器地址,不同 PLC 的地址偏移规则不一样,有的从 0 开始,有的从 1 开始,有的地址要加 40001 这种前缀。这个坑我踩过不止一次。
第三步,用第三方工具交叉验证。比如用 Modbus Poll 直接连 PLC 读数据,如果能读到,说明 PLC 和物理链路没问题,问题在网关配置;如果读不到,问题在 PLC 侧或者链路侧。
第四步,看网关日志。网关一般都有通信日志,能看到发送的报文和收到的响应。如果发出去没响应,是链路问题;如果响应是异常码,是地址或参数问题。
6.2 MQTT 连接不稳定与消息丢失
MQTT 连接不稳定,常见原因有几个。一是网络抖动,网关和 Broker 之间的网络质量差,导致频繁断连。这种情况要检查网络设备,或者把reconnectPeriod设短一点,让它快速重连。
二是 clientId 冲突。两个客户端用了同一个 clientId,会互相踢下线,表现为反复断连。排查方法是看 Broker 日志,会记录哪个 clientId 被踢。
三是认证失败。用户名密码错了,或者密码文件没生效,连接会被拒绝。看 Broker 日志能直接看到认证失败的原因。
消息丢失的话,先确认 QoS 等级。QoS 0 本来就不保证到达,如果业务不能丢数据,必须用 QoS 1 或 2。另外要确认订阅的主题有没有写错,通配符用对没有。factory_a/+/realtime和factory_a/#匹配的范围是不一样的。
6.3 Node.js 服务的内存与并发问题
Node.js 服务跑久了内存涨,多半是事件监听器泄漏或者缓存无上限。比如每次 WebSocket 连接都往某个数组里 push,断开时没移除,数组越来越大。或者用 Map 存最新值,但设备删除了 Map 里的条目没清理。
排查内存问题可以用process.memoryUsage()定期打印,或者用 Chrome DevTools 连上去做堆快照。生产环境建议用 PM2 这类进程管理工具,配置内存上限,超了自动重启。
并发方面,Node.js 单进程能扛的连接数是有上限的。如果 WebSocket 客户端超过几千个,要考虑多进程或者多机部署,前面加负载均衡。MQTT 订阅可以用共享订阅(Shared Subscription)让多个 Node.js 实例分担消息处理。
6.4 断网续传与数据补采
工业现场网络不稳定是常态,断网期间的数据不能丢。这要靠网关的断网缓存功能。网关检测到 MQTT 断连后,把数据存到本地,重连后按时间顺序补发。配置的时候要注意缓存容量,太小了断网时间长就存不下,太大了占网关存储。
补发的数据到了 Node.js 侧,要按时间戳入库,不能按接收时间入库,否则历史曲线的时间轴会乱。这一点在写落库逻辑的时候要特别注意,时间戳一定用消息体里的采集时间,不要用服务端的当前时间。
如果网关不支持断网缓存,那就要在 Node.js 侧做补偿逻辑,比如检测到某个设备的数据有断档,主动去网关或者 PLC 补采。这个实现起来复杂,能靠网关解决就靠网关解决。
7. 几个实际项目中的经验补充
7.1 关于 PLC 温度 PID 波动大的处理
有朋友问过 PLC 温度 PID 波动温差大的问题,这个在采集上云的项目里也常遇到。温度控制本身是 PLC 侧的事,但采集上来的曲线能帮你判断问题。如果曲线显示温度在设定值上下大幅振荡,通常是 PID 参数没整定好,比例带太窄或者积分时间太短。如果曲线显示温度响应很慢,是比例带太宽或者积分时间太长。
采集系统能做的,是把温度曲线、加热输出、设定值三条曲线放在一起对比,这样整定 PID 的时候有依据。我一般会在 Web 页面上做一个专门的 PID 调试页面,实时显示这三条曲线,工程师调参数的时候能立刻看到效果。
7.2 多品牌 PLC 混用的地址映射
一个厂区里往往有多个品牌的 PLC,西门子、三菱、汇川、台达都有。每个品牌的寄存器地址规则不一样,如果直接在 Node.js 里处理,代码会非常乱。
我的做法是在网关侧统一映射。不管底层是什么 PLC,网关配置的时候都映射成统一的变量名,比如temp_01、motor_01_speed。这样 Node.js 侧拿到的数据格式是一致的,不用关心底层是什么 PLC。换 PLC 的时候,只改网关配置,Node.js 和前端都不用动。
这个"统一命名"的思路,本质上是做了一层抽象。工业项目里这种抽象非常重要,能大幅降低后期维护成本。
7.3 关于 AI 辅助生成 PLC 代码的现状
最近 AI 生成 PLC 代码的话题挺热,我也试过一些工具。目前的水平是,简单的逻辑(比如电机启停、定时器、计数器)能生成得八九不离十,但复杂的工艺逻辑、安全联锁、异常处理,还是得靠人。AI 生成的代码可以作为初稿,但一定要人工审查,特别是安全相关的部分,不能直接下载到 PLC 里跑。
在采集上云的项目里,AI 能帮上忙的地方是生成数据点表的模板、生成 Node.js 的样板代码、生成前端页面的骨架。这些重复性的工作交给 AI,能省不少时间。但核心的协议配置、网络规划、异常处理,还是得靠经验。
7.4 离线环境下的部署注意事项
很多工业现场是内网环境,服务器不能联网。这种环境下部署 Node.js 服务,要注意几点。
一是依赖包要提前下载好。在能联网的机器上npm install之后,把node_modules整个打包带过去,或者用npm pack把依赖打成离线包。
二是 Node.js 本身要离线安装。下载对应架构的二进制包,解压配置环境变量即可。
三是 MQTT Broker 也要离线安装。Mosquitto 的 RPM 包或者二进制包提前准备好。
四是时间同步。内网环境如果没有 NTP 服务器,各台机器的时间可能不一致,导致数据时间戳混乱。建议在内网搭一个 NTP 服务,所有设备对时。
这套方案我从最早的单个车间试点,到现在几个厂区同时跑,中间迭代了好几版。最开始用组态软件,后来换成网关加 MQTT,再后来把 Node.js 服务拆成采集、存储、接口三个模块。每次迭代都是被实际需求推着走的,没有一步到位的设计。如果你正准备做类似的项目,我的建议是先把最小链路跑通——一台网关、一个 Broker、一个 Node.js 脚本、一个网页,能实时看到一个数据点,然后再往上加功能。这个最小链路跑通了,后面都是量的积累,不会再遇到方向性的问题。