1. 从一台PLC到浏览器画面这条路到底怎么走车间里一台设备跑得好好的PLC 在电柜里稳定运行但想看数据必须站在触摸屏前面或者抱着笔记本插上线缆连上去。老板在办公室想看产量设备主管在外地想知道机器有没有停机你在家里想确认一下夜班有没有异常报警——这些需求靠传统的组态软件和本地触摸屏根本满足不了。我做了十多年工控项目从最早的 RS232 串口通信到后来的以太网组态再到近几年把数据往 Web 端搬踩过的坑比写过的梯形图还多。今天要聊的这套方案核心思路很简单用一台工业智能网关把 PLC 的数据读出来通过 MQTT 协议把数据推到服务器再用 Node.js 写一个后端服务把数据存下来、转发出去最后在浏览器里用 Web SCADA 的方式呈现出来。整条链路涉及 PLC 通信、工业网关配置、MQTT 消息队列、Node.js 服务端开发和前端可视化看起来环节多但每个环节拆开都不复杂。这套东西适合谁如果你是刚入门的 PLC 工程师想把自己的技能从梯形图扩展到数据采集和 Web 展示这套方案是很好的练手项目。如果你是做设备管理的技术人员想给老设备加一个远程监控功能但预算有限这套方案的成本可以控制得很低。如果你是有一定编程基础的自动化专业学生想做一个完整的毕业设计或者课程项目从 PLC 到 Web 的全链路会让你对工业物联网有立体的认识。甚至如果你是纯软件开发出身想了解工业现场的数据是怎么来的这篇文章也能帮你补上硬件侧的知识盲区。我先把整条链路的关键节点说清楚。PLC 负责采集现场信号和执行控制逻辑它本身不具备直接对外提供 Web 服务的能力。工业智能网关的作用是充当翻译官它支持多种 PLC 协议能把 PLC 内部寄存器、线圈、数据块的值读出来然后转换成 MQTT 消息发出去。MQTT 是一种轻量级的发布订阅协议特别适合工业场景里带宽有限、网络不稳定的情况。Node.js 服务端订阅 MQTT 主题把数据存进数据库同时通过 WebSocket 推送给前端页面。前端页面用 Web SCADA 的思路把数据绑定到组态画面上实现实时刷新。整个链路里工业智能网关是硬件成本的主要部分MQTT 和 Node.js 都是开源免费的所以整体投入可控。注意工业智能网关的选型非常关键不同品牌支持的 PLC 协议不一样买之前一定要确认它是否支持你现场用的 PLC 型号和通信协议。我见过有人买回来发现不支持自家 PLC 的专有协议只能退货换货耽误工期。2. 工业智能网关选型与 PLC 通信配置2.1 网关的核心作用与选型要点工业智能网关在这套方案里扮演的是数据采集器的角色。它向下通过串口、网口或者总线跟 PLC 通信向上通过以太网或者 4G 模块把数据发到服务器。选型的时候我一般看几个硬指标支持的 PLC 品牌和协议数量、采集点位的上限、是否支持边缘计算、通信接口的类型和数量、工作温度范围、供电电压。支持协议这块主流网关基本都覆盖西门子 S7 系列、三菱 FX 和 Q 系列、欧姆龙 CJ 和 CP 系列、台达 DVP 系列、汇川 H 系列等。如果你用的是比较冷门的 PLC 品牌一定要提前查清楚网关的协议兼容列表。采集点位的上限决定了你能同时监控多少数据。小型的网关可能只支持几百个点位中大型的能支持几千甚至上万个。我建议按实际点位的 1.5 倍来选留出扩展余量。边缘计算功能指的是网关能不能在本地做数据处理比如超限报警判断、数据滤波、断线缓存等。这个功能很实用网络断了的时候网关可以把数据先存本地等网络恢复再补传避免数据丢失。通信接口方面至少需要一个网口和一个 RS485 串口网口用来连 PLC 和上行通信串口用来连老式 PLC 或者仪表。工作温度范围经常被忽略。电柜里的温度可能比车间环境高十几度夏天的时候电柜内温度到五十多度很正常。如果网关的工作温度上限只有 55 度那在夏天很容易出问题。我一般选工作温度范围在负 20 度到 70 度的型号宽温设计更稳妥。供电电压方面大部分网关支持 9 到 36 伏直流宽压输入可以直接从电柜的 24 伏开关电源取电很方便。2.2 台达 PLC 程序下载与通信参数设置台达 PLC 在中小型设备里用得非常多性价比高编程软件 WPLSoft 或者 ISPSoft 都很成熟。下载程序的时候首先要用编程线把电脑和 PLC 连起来。台达 DVP 系列一般用 RS232 串口线电脑端如果是 USB 接口就需要一个 USB 转串口模块。装好驱动之后在设备管理器里确认串口号然后在 WPLSoft 的通信设置里选对应的串口和波特率。台达 PLC 默认的通信格式是 7 位数据位、偶校验、1 位停止位波特率 9600。如果你改过通信参数一定要记清楚不然连不上。下载程序之前我习惯先做一次通信测试。在 WPLSoft 里点“通信设置”然后点“测试”如果能读到 PLC 的型号和版本信息说明通信正常。这一步能排除很多低级问题比如串口号选错、线缆接触不良、PLC 没上电等。程序下载完之后记得把 PLC 的运行开关拨到 RUN 位置不然程序不会执行。台达 PLC 的 RUN 和 STOP 开关一般在面板上有些型号是拨码开关有些是按钮具体看型号。通信参数设置这块如果网关要通过 RS485 跟台达 PLC 通信PLC 的通信格式必须和网关一致。我一般把波特率设成 19200 或者 38400数据位 8 位、无校验、1 位停止位这样兼容性更好。台达 PLC 的通信参数在 D 寄存器里设置具体是 D1120 设置通信协议和波特率D1121 设置站号。比如 D1120 设成 H86 表示 9600 波特率、7 位数据位、偶校验、1 位停止位、ASCII 模式。这些参数在台达的编程手册里都有详细说明设置之前翻一下手册确认清楚。2.3 西门子 PLC 与博途的通信配置西门子 S7-1200 和 S7-1500 系列在现在的项目里占比越来越高博途 TIA Portal 是标配的编程软件。用工业智能网关采集西门子 PLC 数据的时候通常走 S7 协议通过网口通信。博途里需要做几个设置首先在硬件组态里确认 PLC 的 IP 地址和子网掩码确保网关和 PLC 在同一个网段。然后在 PLC 的属性里找到“防护与安全”选项勾选“允许来自远程对象的 PUT/GET 通信访问”。这个选项不勾的话网关读不到数据这是很多人第一次配置时容易漏掉的地方。如果你用的是 S7-200 Smart它不支持 S7 协议的直接访问需要用 Modbus TCP 或者西门子的 S7 协议变种。S7-200 Smart 的 Modbus TCP 需要调用 MB_SERVER 指令来启用然后在网关侧配置 Modbus TCP 客户端。这个细节在西门子的系统手册里有说明但很多人不知道以为插上网线就能读数据。我建议在项目开始之前先确认 PLC 型号和支持的通信协议再选网关这样能避免很多返工。博途里还有一个地方要注意就是 PLC 的“连接机制”设置。在硬件组态里双击 PLC找到“防护与安全”下面的“连接机制”勾选“允许来自远程对象的 PUT/GET 通信访问”。这个选项默认是不勾的必须手动打开。另外如果 PLC 有多个网口要确认网关连的是哪个网口以及那个网口的 IP 地址。有些 PLC 的网口是用于编程和 HMI 通信的有些是用于 PROFINET 的功能不一样。2.4 汇川 PLC 的编程与通信要点汇川 PLC 在国内市场增长很快尤其是 AM 系列和 H5U 系列在包装、锂电、光伏设备上用得很多。汇川的编程软件是 AutoShop支持梯形图和 ST 语言。用网关采集汇川 PLC 数据的时候通常走 Modbus TCP 或者汇川的专有协议。汇川 PLC 的 Modbus TCP 默认端口是 502站号默认是 1。在 AutoShop 里需要启用 Modbus TCP 从站功能然后配置寄存器映射。汇川的寄存器地址和 Modbus 地址有一个偏移关系比如 D0 对应 Modbus 地址 0D100 对应 Modbus 地址 100M0 对应线圈地址 0。这个映射关系在汇川的通信手册里有详细表格配置网关的时候要对照着填。汇川 PLC 的网口 MAC 地址可以在 AutoShop 的在线诊断里查看也可以在 PLC 的标签上找到。有些项目需要绑定 MAC 地址来做设备识别这时候就要提前记录好。汇川官网有完整的技术文档和示例程序遇到问题可以去官网下载手册或者联系技术支持。我个人的经验是汇川的 Modbus TCP 通信比较稳定但寄存器地址的映射关系一定要搞清楚不然读上来的数据全是错的。3. MQTT 协议核心机制与服务器搭建3.1 MQTT 发布订阅模型详解MQTT 的全称是 Message Queuing Telemetry Transport翻译过来叫消息队列遥测传输协议。它的核心模型是发布订阅模式跟传统的请求响应模式不一样。在请求响应模式里客户端发一个请求服务器回一个响应客户端和服务器是直接耦合的。在发布订阅模式里发布者把消息发到一个叫“主题”的地方订阅者从主题里收消息发布者和订阅者互相不知道对方的存在通过 MQTT 服务器也叫 Broker来中转。这种解耦的设计让系统扩展变得很容易加一个订阅者不需要改发布者的代码。MQTT 里有几个核心概念需要搞清楚。主题是消息的分类标签用斜杠分隔层级比如factory/line1/plc1/temperature表示工厂一线一号 PLC 的温度数据。通配符有两种加号匹配单层井号#匹配多层。比如订阅factory/line1//temperature能收到一线所有 PLC 的温度数据订阅factory/#能收到工厂所有数据。QoS 是服务质量等级分 0、1、2 三级。QoS 0 是最多发一次发出去就不管了可能丢消息。QoS 1 是至少发一次保证消息到达但可能重复。QoS 2 是恰好发一次保证不丢不重但开销最大。工业场景里一般用 QoS 1兼顾可靠性和性能。保留消息和遗嘱消息是两个很实用的特性。保留消息是指发布者发消息的时候设置 retain 标志Broker 会把这条消息保存下来以后有新的订阅者订阅这个主题立刻就能收到最后一条保留消息。这个特性适合用来发布设备状态新上线的监控页面能马上显示当前状态不用等下一次数据上报。遗嘱消息是指客户端连接 Broker 的时候可以指定一条遗嘱如果客户端异常断线Broker 会自动发布这条遗嘱消息。这个特性适合用来做设备离线告警设备掉线后监控页面能立刻知道。3.2 MQTT 消息不丢失的保障机制工业场景里数据丢失是很严重的问题可能影响生产统计和故障分析。MQTT 保证消息不丢失主要靠几个机制的组合。首先是 QoS 等级QoS 1 和 QoS 2 都有确认和重传机制。QoS 1 的流程是发布者发消息Broker 收到后回一个 PUBACK发布者收到 PUBACK 才认为发送成功。如果发布者没收到 PUBACK会重发消息所以订阅者可能收到重复消息需要在应用层做去重。QoS 2 的流程更复杂有四次握手保证消息恰好到达一次但性能开销大一般只在特别关键的场合用。其次是持久会话。MQTT 客户端连接 Broker 的时候可以设置 clean session 标志。如果设为 falseBroker 会保存这个客户端的订阅关系和未确认的消息客户端断线重连后能继续收到之前订阅的消息。这个机制对网络不稳定的工业现场很重要网关断线重连后不会丢数据。但是持久会话会占用 Broker 的存储资源如果客户端很多要考虑 Broker 的存储容量。还有一点是 Broker 的持久化配置。Mosquitto 默认把消息存在内存里重启后消息就丢了。可以在配置文件里开启持久化把消息存到磁盘上。具体是在 mosquitto.conf 里设置persistence true和persistence_location /var/lib/mosquitto/。这样 Broker 重启后未过期的保留消息和持久会话的消息还能恢复。不过持久化会影响性能如果消息量很大要权衡一下。3.3 Windows 本地搭建 MQTT 服务在 Windows 上搭建 MQTT 服务最常用的 Broker 是 Mosquitto。去 Mosquitto 官网下载 Windows 版的安装包双击安装一路下一步就行。安装完成后Mosquitto 会作为 Windows 服务自动启动。你可以在“服务”管理器里看到 Mosquitto Broker 这个服务确认它的状态是“正在运行”。如果没启动右键点“启动”就行。安装目录一般在C:\Program Files\mosquitto配置文件是mosquitto.conf。默认配置下Mosquitto 只监听本地回环地址也就是 127.0.0.1端口 1883。这意味着只有本机的客户端能连上局域网里的其他设备连不上。如果你想让网关或者别的电脑连上来需要修改配置文件。用记事本打开mosquitto.conf找到listener这一行改成listener 1883 0.0.0.0表示监听所有网络接口。然后找到allow_anonymous这一行改成allow_anonymous true允许匿名连接。改完之后保存重启 Mosquitto 服务。注意允许匿名连接在生产环境里是不安全的测试阶段可以用正式环境一定要配置用户名密码。如果你下载的是 zip 包而不是安装包需要手动把 Mosquitto 注册成 Windows 服务。方法是以管理员身份打开命令提示符进入 Mosquitto 的安装目录执行mosquitto install命令。这个命令会把 Mosquitto 注册成服务然后你可以用net start mosquitto启动服务。如果提示服务已存在先执行mosquitto uninstall卸载再重新安装。手动注册服务的时候配置文件的路径要写对不然服务启动会失败。可以在注册服务的时候指定配置文件路径命令是mosquitto install -c C:\Program Files\mosquitto\mosquitto.conf。3.4 Linux 环境下 MQTT 服务部署在 CentOS 7.9 或者 Ubuntu 上部署 Mosquitto可以用包管理器安装也可以源码编译。用 yum 安装的话先执行yum install epel-release启用 EPEL 源然后yum install mosquitto mosquitto-clients。安装完成后Mosquitto 的服务文件在/usr/lib/systemd/system/mosquitto.service配置文件在/etc/mosquitto/mosquitto.conf。启动服务用systemctl start mosquitto设置开机自启用systemctl enable mosquitto。Linux 下的配置文件格式和 Windows 一样但路径不同。默认配置文件里可能没有 listener 和 allow_anonymous 的配置需要自己加。我一般会在配置文件末尾加上这几行listener 1883 0.0.0.0、allow_anonymous true、persistence true、persistence_location /var/lib/mosquitto/。然后创建持久化目录mkdir -p /var/lib/mosquitto设置权限chown mosquitto:mosquitto /var/lib/mosquitto。重启服务后用systemctl status mosquitto确认状态是 active (running)。防火墙这块要注意CentOS 7 默认用 firewalld需要开放 1883 端口。执行firewall-cmd --permanent --add-port1883/tcp然后firewall-cmd --reload。如果用的是 iptables对应的命令是iptables -I INPUT -p tcp --dport 1883 -j ACCEPT然后保存规则。云服务器的话还要在安全组里开放 1883 端口这个很容易忘。我见过有人本地测试通了部署到云服务器就连不上折腾半天发现是安全组没开端口。3.5 MQTT 客户端工具与调试方法调试 MQTT 通信的时候一个好用的客户端工具能省很多时间。MQTT Explorer 是我最常用的它是一个图形化的 MQTT 客户端能直观地看到主题树和消息内容。去官网下载安装包安装后打开填上 Broker 的地址和端口点连接。连上之后左侧会显示所有主题的树形结构点某个主题就能看到最新的消息。你还可以在发布面板里手动发消息测试订阅端能不能收到。MQTT Explorer 还支持消息历史记录能看到某个主题过去收到的消息方便排查问题。命令行工具 mosquitto_pub 和 mosquitto_sub 也很实用尤其在 Linux 服务器上。发布消息用mosquitto_pub -h localhost -t test/topic -m hello订阅消息用mosquitto_sub -h localhost -t test/topic。如果要测试 QoS 1加-q 1参数。如果要测试保留消息发布的时候加-r参数。这些命令在 Mosquitto 的客户端包里安装 Mosquitto 的时候会一起装上。我经常用这两个命令做快速测试比如网关配置好之后先在服务器上用 mosquitto_sub 订阅主题看能不能收到数据这样能快速判断问题出在网关侧还是服务端侧。4. Node.js 服务端开发与数据处理4.1 Node.js 安装与环境配置Node.js 的安装方式取决于操作系统。Windows 下最简单的是去官网下载 LTS 版本的安装包双击安装。目前比较稳定的 LTS 版本有 18.20.4 和 22.12建议选 18.20.4兼容性最好。安装的时候勾选“Add to PATH”这样在命令行里能直接使用 node 和 npm 命令。安装完成后打开命令提示符输入node -v和npm -v如果能显示版本号说明安装成功。如果提示“不是内部或外部命令”说明 PATH 没配好需要手动把 Node.js 的安装目录加到系统环境变量里。Linux 下安装 Node.js 有几种方式。用包管理器安装最简单CentOS 下yum install nodejs npmUbuntu 下apt install nodejs npm。但包管理器里的版本可能比较老如果你需要新版本可以用 NodeSource 的源。先执行curl -fsSL https://deb.nodesource.com/setup_18.x | bash -Ubuntu或者curl -fsSL https://rpm.nodesource.com/setup_18.x | bash -CentOS然后apt install nodejs或者yum install nodejs。这样装的是 Node.js 18.x 的最新版。安装完成后同样用node -v验证。还有一种方式是用 nvmNode Version Manager来管理多个 Node.js 版本。nvm 的好处是可以在不同项目之间切换 Node.js 版本不会互相干扰。安装 nvm 用curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash然后重新打开终端用nvm install 18.20.4安装指定版本用nvm use 18.20.4切换版本。nvm 在开发机上很方便但生产服务器上一般不需要多版本直接装一个 LTS 版本就行。4.2 用 mqtt.js 订阅 PLC 数据Node.js 里操作 MQTT 最常用的库是 mqtt.js安装命令是npm install mqtt。装好之后写一个简单的订阅脚本。先引入 mqtt 模块然后调用mqtt.connect()连接 Broker传入 Broker 的地址和端口。连接成功后在 connect 事件的回调里调用client.subscribe()订阅主题。收到消息时message 事件的回调会收到主题和消息内容消息内容是 Buffer 类型需要转成字符串再解析。const mqtt require(mqtt); const client mqtt.connect(mqtt://localhost:1883); client.on(connect, () { console.log(已连接到 MQTT Broker); client.subscribe(factory///data, { qos: 1 }, (err) { if (!err) { console.log(订阅成功); } }); }); client.on(message, (topic, message) { const payload JSON.parse(message.toString()); console.log(收到消息:, topic, payload); // 这里可以把数据存数据库或者推送给前端 });这段代码里factory///data用了加号通配符能匹配factory/line1/plc1/data、factory/line2/plc3/data这样的主题。QoS 设为 1保证消息至少到达一次。收到消息后把 Buffer 转成字符串再用 JSON.parse 解析成对象。实际项目里解析之后一般要做数据校验确认字段完整、数值在合理范围内然后再做后续处理。4.3 数据存储方案与数据库选型PLC 数据存哪里取决于你的需求。如果只是做实时监控不需要历史数据那可以只存在内存里用变量保存最新值就行。但如果要做趋势图、报表、故障回溯就需要存数据库。工业场景里常用的数据库有两种关系型数据库如 MySQL、PostgreSQL时序数据库如 InfluxDB、TDengine。关系型数据库适合存设备信息、报警记录、班次统计这类结构化数据。时序数据库专门为时间序列数据优化写入速度快、压缩率高适合存温度、压力、电流这类连续采集的数据。我一般用 MySQL 存设备台账和报警记录用 InfluxDB 存实时采集的模拟量数据。InfluxDB 的写入性能很好单机每秒能写几十万条数据而且自带降采样和保留策略可以自动清理过期数据。安装 InfluxDB 在 Linux 下用包管理器就行Ubuntu 下apt install influxdbCentOS 下需要先加 InfluxData 的源。安装完成后启动服务用influx命令进入命令行创建数据库CREATE DATABASE plc_data。Node.js 里用influxdata/influxdb-client库来写入数据先创建客户端实例然后调用writePoint()写入数据点。如果不想装额外的数据库用 SQLite 也行。SQLite 是一个文件型数据库不需要单独的服务进程Node.js 里用sqlite3库就能操作。适合数据量不大、单机部署的场景。但 SQLite 的并发写入能力有限如果多个进程同时写可能会锁表。所以如果数据量大或者并发高还是用 MySQL 或者 InfluxDB 更稳妥。4.4 WebSocket 实时推送与前端对接前端页面要实时显示 PLC 数据不能用传统的 HTTP 轮询那样延迟高、服务器压力大。正确的做法是用 WebSocket服务器主动把数据推给前端。Node.js 里常用的 WebSocket 库是ws安装命令npm install ws。创建一个 WebSocket 服务器监听某个端口前端用new WebSocket()连接上来。当 MQTT 收到新数据时遍历所有 WebSocket 连接把数据发给前端。const WebSocket require(ws); const wss new WebSocket.Server({ port: 8080 }); wss.on(connection, (ws) { console.log(前端已连接); ws.on(close, () { console.log(前端已断开); }); }); // MQTT 收到消息后调用这个函数 function broadcast(data) { wss.clients.forEach((client) { if (client.readyState WebSocket.OPEN) { client.send(JSON.stringify(data)); } }); }前端页面里用 JavaScript 创建 WebSocket 连接监听 onmessage 事件收到数据后更新页面上的数值。Web SCADA 的画面一般用 SVG 或者 Canvas 绘制把设备图形和数据显示绑定起来。比如一个温度显示可以用一个文本元素显示数值收到 WebSocket 消息后更新文本内容。如果要显示趋势图可以用 ECharts 或者 Chart.js 这类图表库把历史数据传进去渲染成曲线。4.5 服务端异常处理与断线重连工业现场的网络不稳定MQTT 连接断掉是常有的事。Node.js 的 mqtt.js 库自带重连机制连接断开后会自动尝试重连。但默认的重连间隔是 1 秒如果 Broker 长时间不可用会频繁重连消耗资源。可以在连接选项里配置reconnectPeriod比如设成 5000 毫秒5 秒重连一次。还可以监听offline和reconnect事件在断线时记录日志重连成功时也记录日志方便排查问题。const client mqtt.connect(mqtt://localhost:1883, { reconnectPeriod: 5000, connectTimeout: 10000, clean: false, clientId: plc_gateway_server_ Math.random().toString(16).substr(2, 8) }); client.on(offline, () { console.warn(MQTT 连接已断开等待重连...); }); client.on(reconnect, () { console.log(正在尝试重连 MQTT...); }); client.on(error, (err) { console.error(MQTT 连接错误:, err.message); });clean设为 false 表示使用持久会话断线重连后能收到断线期间的消息。clientId要唯一如果两个客户端用同一个 clientId会互相踢下线。我一般用固定前缀加随机字符串来生成 clientId。错误处理这块一定要监听 error 事件不然出错的时候进程可能直接崩掉。生产环境里还会用 pm2 来守护 Node.js 进程进程挂了自动重启保证服务不中断。5. Web SCADA 前端画面与数据绑定5.1 Web SCADA 与传统组态的区别传统组态软件比如 WinCC、组态王、力控都是安装在 Windows 电脑上的桌面程序画面组态、数据绑定、报警配置都在本地完成。Web SCADA 是把组态画面搬到浏览器里用 HTML、CSS、JavaScript 来绘制画面用 WebSocket 来接收实时数据。两者的核心逻辑是一样的都是把设备图形和数据显示关联起来但技术栈完全不同。Web SCADA 的优势是跨平台电脑、平板、手机都能看不需要安装客户端。劣势是开发门槛比传统组态高需要懂前端技术。我刚开始做 Web SCADA 的时候用纯 HTML 加 SVG 来画画面每个设备图形都是手写的 SVG 代码工作量很大。后来发现可以用一些开源的前端组态库比如 GoJS、JointJS、Konva.js它们提供了拖拽式画布和图形元素能省不少时间。但这些库的学习曲线也不低而且有些是收费的。如果画面不复杂用 SVG 手写也能接受毕竟工业画面一般就是泵、阀、罐、管道这些基本图形画一次之后可以复用。5.2 用 SVG 绘制工业画面SVG 是可缩放矢量图形用 XML 描述图形浏览器原生支持。画一个水泵可以用圆形表示泵体用矩形表示底座用线条表示管道。每个图形元素可以绑定一个 idJavaScript 通过 id 找到元素修改它的属性来改变显示。比如温度数值可以用text元素显示收到新数据后修改 textContent。液位可以用rect的高度来表示液位越高矩形越高。svg width800 height600 viewBox0 0 800 600 !-- 储罐 -- rect x100 y200 width120 height200 fill#e0e0e0 stroke#333 stroke-width2/ !-- 液位 -- rect idtank-level x102 y300 width116 height98 fill#4a90d9/ !-- 温度显示 -- text idtemp-value x160 y180 text-anchormiddle font-size20 fill#333-- °C/text !-- 管道 -- line x1220 y1300 x2350 y2300 stroke#666 stroke-width8/ !-- 水泵 -- circle cx400 cy300 r40 fill#f0f0f0 stroke#333 stroke-width2/ text x400 y305 text-anchormiddle font-size14 fill#333泵/text /svg这段 SVG 代码画了一个储罐、液位、温度显示、管道和水泵。液位矩形的高度通过 JavaScript 动态修改温度文本的内容也动态更新。实际项目里我会给每个需要动态更新的元素加一个 id 或者 class然后在 WebSocket 的 onmessage 回调里根据数据更新对应的元素。比如收到{tankLevel: 75, temperature: 42.5}就把液位矩形的高度设成 75% 对应的像素值把温度文本设成42.5 °C。5.3 数据绑定与实时刷新逻辑数据绑定的核心是建立数据点和画面元素之间的映射关系。我一般用一个配置对象来维护这个映射比如{ plc1.temperature: temp-value, plc1.tankLevel: tank-level }表示 PLC1 的温度数据绑定到 id 为 temp-value 的元素液位数据绑定到 id 为 tank-level 的元素。收到 WebSocket 消息后遍历这个映射找到对应的元素并更新。这样加新数据点的时候只需要改配置不用改代码逻辑。const bindingMap { plc1.temperature: { elementId: temp-value, format: (v) v.toFixed(1) °C }, plc1.tankLevel: { elementId: tank-level, format: (v) v } }; const ws new WebSocket(ws://localhost:8080); ws.onmessage (event) { const data JSON.parse(event.data); Object.keys(data).forEach((key) { const binding bindingMap[key]; if (!binding) return; const el document.getElementById(binding.elementId); if (!el) return; if (el.tagName text) { el.textContent binding.format(data[key]); } else if (el.tagName rect) { // 液位矩形根据百分比计算高度 const maxHeight 200; el.setAttribute(height, (data[key] / 100) * maxHeight); el.setAttribute(y, 400 - (data[key] / 100) * maxHeight); } }); };这段代码里bindingMap 定义了数据点到画面元素的映射format 函数负责把原始数值格式化成显示文本。收到消息后遍历数据对象的每个键找到对应的绑定配置更新元素。液位矩形的处理稍微复杂一点需要根据百分比计算高度和 y 坐标保证矩形从底部往上长。实际项目里还会加数据有效性判断比如数值超出量程就不更新避免画面显示异常。5.4 报警与历史趋势的实现思路报警功能是 SCADA 的核心功能之一。实现思路是在服务端或者前端维护一个报警规则表每条规则包含数据点、报警类型高高、高、低、低低、阈值、报警文本。收到数据后遍历报警规则判断是否触发报警。触发报警时在画面上显示报警条同时把报警记录写入数据库。报警恢复时记录恢复时间。报警条一般用红色背景显示按时间倒序排列最新的报警在最上面。历史趋势用图表库来实现ECharts 是常用的选择。服务端把历史数据从数据库查出来通过 HTTP 接口返回给前端前端用 ECharts 渲染成曲线图。数据量大时要做降采样比如每分钟取一个平均值避免图表卡顿。ECharts 支持 dataZoom 组件可以拖动缩放查看不同时间范围的数据。趋势图一般放在单独的页面和实时监控画面分开避免画面太拥挤。6. 常见问题排查与实战避坑经验6.1 PLC 通信失败排查速查表现象可能原因排查方法网关读不到 PLC 数据IP 地址不在同一网段用 ping 命令测试连通性确认 IP 和子网掩码网关读不到 PLC 数据PLC 未启用 PUT/GET 通信在博途里检查“防护与安全”设置网关读不到 PLC 数据寄存器地址映射错误对照 PLC 通信手册确认地址偏移串口通信失败波特率或数据位不匹配确认双方通信参数一致串口通信失败站号冲突或错误检查 PLC 站号和网关配置数据时有时无网线接触不良或干扰更换屏蔽网线检查接地数据值明显错误数据类型解析错误确认是整数还是浮点数字节序是否正确这张表是我这些年踩坑总结出来的覆盖了大部分常见问题。排查的时候按顺序来先确认物理连接再确认网络配置再确认协议参数最后确认数据映射。物理连接是最容易忽略的我见过好几次是网线水晶头没压好时通时断折腾了半天才发现是线的问题。6.2 MQTT 连接与消息异常处理MQTT 连接不上 Broker先检查 Broker 的地址和端口对不对然后确认 Broker 服务有没有启动。在服务器上用netstat -an | grep 1883看端口有没有监听。如果端口没监听说明 Broker 没启动或者配置有问题。如果端口监听了但连不上可能是防火墙或者安全组的问题。Windows 下检查防火墙入站规则Linux 下检查 firewalld 或 iptables云服务器检查安全组。消息收不到的情况先确认订阅的主题和发布的主题是否匹配。MQTT 主题是大小写敏感的Factory/Line1和factory/line1是两个不同的主题。通配符的使用也要注意factory//data能匹配factory/line1/data但匹配不了factory/line1/plc1/data因为加号只匹配一层。如果订阅时用了 QoS 1但发布时用了 QoS 0消息可能丢失。要保证消息不丢发布和订阅都用 QoS 1 或 2。消息重复的问题QoS 1 本身就会导致重复因为它是至少一次投递。解决方法是在消息里加一个唯一 ID订阅端收到消息后先查这个 ID 有没有处理过处理过就丢弃。唯一 ID 可以用时间戳加随机数生成也可以用消息序号。如果对重复零容忍就用 QoS 2但性能会下降。6.3 Node.js 服务稳定性经验Node.js 是单线程的如果某个操作耗时太长会阻塞整个事件循环导致其他请求得不到响应。比如在 message 回调里做复杂的数据库查询或者大量计算就会阻塞。解决方法是用异步操作数据库查询用异步驱动大量计算用 worker_threads 或者拆分成小任务。我一般会在 message 回调里只做简单的解析和转发复杂的处理放到队列里异步执行。内存泄漏是 Node.js 服务的另一个常见问题。如果代码里有全局变量不断累积或者事件监听器没有移除内存会一直涨最后进程崩溃。排查内存泄漏可以用node --inspect启动服务然后用 Chrome 的开发者工具连接上去看内存快照。也可以用process.memoryUsage()定期打印内存使用情况观察趋势。如果内存持续增长不下降基本可以确定有泄漏。生产环境一定要用进程守护工具pm2 是最常用的。npm install -g pm2安装然后用pm2 start app.js --name plc-server启动服务。pm2 会在进程崩溃时自动重启还能设置开机自启。pm2 logs查看日志pm2 monit查看资源占用。我还会配置 pm2 的日志轮转避免日志文件把磁盘写满。pm2 install pm2-logrotate安装日志轮转模块然后配置保留天数和文件大小。6.4 工业现场部署的独家避坑技巧电柜里的电磁干扰是通信不稳定的常见原因。变频器、伺服驱动器、接触器这些设备在工作时会产生很强的电磁干扰如果通信线没有屏蔽或者屏蔽层没接地数据会出错。我一般用双绞屏蔽线屏蔽层在网关侧单端接地不要两端都接否则会形成地环路。通信线要远离动力线至少保持 20 厘米以上的距离能走不同的线槽最好。网关的供电也要注意。电柜里的 24 伏开关电源如果同时给 PLC、继电器、指示灯供电负载变化时电压会有波动。网关对电压波动比较敏感电压太低会重启。我一般给网关单独用一个电源模块或者加一个稳压模块。如果现场电网质量差还要考虑加 UPS防止突然断电导致数据丢失。网络方面如果现场没有有线网络可以用 4G 路由器。但 4G 网络的延迟和稳定性不如有线MQTT 的 keepalive 时间要设长一点比如 60 秒避免频繁断线重连。4G 流量也要注意如果采集频率高、数据量大一个月可能跑几十个 G。可以在网关侧做边缘计算只上传变化的数据或者超限的数据减少流量消耗。最后说一个我踩过的大坑网关的固件版本。有些网关的固件有 bug特定情况下会死机或者数据错乱。买回来之后先升级到最新固件然后做压力测试连续跑几天看稳定性。我遇到过一批网关固件版本旧运行一周左右就会内存溢出重启升级固件后问题解决。所以不要拿到设备就直接上现场先在办公室跑几天测试确认稳定再部署。6.5 从入门到落地的学习路径建议如果你刚接触这套东西建议按这个顺序来先搞定 PLC 编程基础能写简单的梯形图知道什么是输入输出、定时器、计数器。然后学 Modbus 协议理解寄存器、线圈、地址映射这些概念。接着学 MQTT装一个 Mosquitto用 MQTT Explorer 发消息收消息把发布订阅模型搞清楚。然后学 Node.js能写一个简单的 HTTP 服务器和 WebSocket 服务器。最后把这几块串起来用网关读 PLC 数据发到 MQTTNode.js 订阅并推送到前端。每一步都不要贪快把基础打牢。我见过很多人直接拿别人的代码来跑跑通了但不知道为什么出了问题完全不知道怎么排查。工业项目最怕的就是不稳定一个隐藏的 bug 可能导致生产事故。所以宁可慢一点把每个环节的原理搞清楚这样遇到问题才能快速定位和解决。这套方案后续还可以扩展很多功能比如加一个手机端页面用响应式布局适配手机屏幕。或者接入微信通知报警时推送到手机。还可以做数据报表按班次、按天、按月统计产量和能耗。甚至可以用 AI 做异常检测根据历史数据预测设备故障。这些扩展都建立在当前这套基础架构之上把基础打好了往上加功能就是水到渠成的事。