1. 为什么说“不改系统加报警”这件事能成立
做工业现场的都知道,WinCC和PLC这套组合一旦稳定跑起来,就是“祖宗级”待遇——轻易没人敢动。改PLC程序要停机、要重新调试,还得担心逻辑改动后影响原有工艺;改WinCC画面和脚本同样麻烦,版本授权、编译下载、上位机重启,每一步都可能惹出新问题。但现场需求往往不会等你准备好:设备报警要推送到手机、第三方系统要拿数据、现场要加一路声音报警、某个关键参数超限得有人马上知道。
这个“又不想动老系统、又必须加报警”的矛盾,我见到太多了。方案其实就一句话:在PLC和WinCC之间,或者WinCC的上位链路里,旁路一个Node-RED边缘计算网关,通过OPC UA把数据读出来,报警判断和通知分发全部放在网关侧完成。它不写PLC,不改WinCC,甚至WinCC重启、画面操作都跟它没关系。
项目标题里那句“不改WinCC和PLC加报警功能”并不是噱头。Node-RED作为一个开源的低代码流式编程工具,对工控人来说门槛不算高。它跑在一台小主机上,可以是工控机、瘦客户机、树莓派甚至虚拟机,通过OPC UA协议和西门子S7系列PLC通讯(也支持Modbus、三菱、欧姆龙等各类协议),把采集到的数据在本地完成滤波、判断、锁存,然后通过MQTT、HTTP、邮件、企业微信机器人等方式把报警送出去。
这套方案的核心思路是:让“报警”这件事从控制系统里解耦出来。原来的PLC和WinCC继续干它们的老本行——控制逻辑、工艺画面、历史归档;报警检测变成一种“旁挂”能力,由边缘网关独立承载。谁也不会干扰谁,谁也不用停机配合谁。
我最早接触这个思路是接手一个改造项目:现场是S7-300配WinCC 7.4,要加三个模拟量高位报警和一个设备状态监控,但客户的生产计划排得很满,停机窗口只有到一个多月以后才有。当时我就用一台旧工控机跑了Node-RED,从PLC侧把数据读出来做报警推送,一周不到就上线试运行,完全没碰过原系统。这个经历让我确信,这套思路在很多存量项目里都值得复制。
2. 先搞明白:数据从哪来,往哪去
2.1 数据采集链路的确定
要在不动PLC程序的前提下拿到数据,最常见也最稳的路径就是通过OPC UA服务器走西门子S7协议或者通过S7-300/400的以太网口直接读取。实际情况里又有两种接法:
| 方案 | 接法 | 优点 | 缺点 |
|---|---|---|---|
| A | Node-RED通过S7协议直接读PLC | 不依赖额外软件,接法最简 | 节点库较杂,需注意通讯负载;对S7-1200/1500需要设“允许来自远程对象的PUT/GET通讯访问”,可能被安全策略卡住 |
| B | Node-RED通过OPC UA读WinCC或独立OPC UA服务器 | 数据点管理集中,WinCC自带OPC UA服务(从V7.2起支持) | 依赖WinCC服务的稳定性和授权;WinCC侧OPC UA服务需要单独配置,部分版本有连接数限制 |
我在大多数项目里推荐方案B——利用WinCC自带的OPC UA服务器功能。原因很简单:WinCC本来就要连接PLC,数据点、变量名都已经配置好了,直接在WinCC的OPC UA配置里把需要暴露的变量放出来,Node-RED这边只需要按变量节点ID去订阅就行。这样连PLC的变量表都不用翻。
但如果你用的是S7-1200/1500这类自带OPC UA服务器的PLC,那就更简单了。PLC本身就是OPC UA Server,Node-RED直接去连PLC的IP地址加端口4840,不需要WinCC参与。这种方式我一般推荐给“现场没有WinCC、只有触摸屏”的场景——同样能实现“不动HMI、不改PLC”的报警旁挂。
2.2 报警消息最终送到哪
采集侧定了,发送侧也得先想清楚。报警不止是“屏幕上弹一条消息”,不同场景对送达渠道的要求完全不一样:
- 操作工在车间现场:需要现场声光报警器动作,这类可以走Node-RED输出Modbus TCP或直接驱动一个继电器模块;
- 维护人员在办公室:桌面弹窗、浏览器看板最方便,走Node-RED的HTTP接口推送页面或者WebSocket实时刷新;
- 管理层不在现场:需要微信、钉钉、邮件推送,Node-RED自带丰富的通知节点,或者通过HTTP方式调用企业微信机器人、钉钉自定义机器人;
- 系统间集成:把报警写入另一个系统的数据库、通过MQTT转发给上层平台,Node-RED的MQTT节点可以轻松接入EMQX、VerneMQ等消息中间件。
这个“先定出口,再定入口”的顺序很重要。很多人一上来就急着连PLC读数据,结果数据读上来了,发给谁、用什么发还没想好,流写到一半就卡住了。我建议第一步先梳理一张表:报警事件名称、触发条件、恢复条件、通知对象、通知方式、是否记录历史。这张表就是你后面写流的需求说明书。
3. Node-RED边缘网关的落地硬件与运行环境
3.1 硬件的选择逻辑
边缘计算网关听起来高大上,落到硬件上其实就是一个“能联网、能跑Linux、能稳定长时间运行”的小主机。我列一下几个层次的选项,大家按项目预算和对可靠性的要求来选:
| 硬件方案 | 参考配置 | 适用场景 | 注意点 |
|---|---|---|---|
| 工控机/瘦客户机 | 4核CPU, 8GB内存, 2个千兆网口, SSD | 生产现场正式部署 | 选无风扇机型,注意DC供电稳定性 |
| 树莓派4B/5 | 4核ARM, 4~8GB内存, 千兆网口 | 测试验证、非关键场景 | SD卡容易损坏,建议用SSD启动 |
| 软路由盒子/x86小主机 | N100/J4125级别, 8GB内存 | 性价比高的现场方案 | 要选支持长时间运行的工业级电源 |
| Docker部署在现有工控机上 | 复用现场服务器资源 | 不适合单独加硬件的场合 | 注意资源隔离,避免影响WinCC所在服务器性能 |
我做现场项目时,功耗、散热、供电稳定这三个因素往往比绝对性能更重要。Node-RED单体运行所需资源并不高,一般设备跑几十个流,CPU占用率也就百分之几到十几。真正吃资源的是历史数据存储和看板渲染,这一块我通常会把数据转发给独立的时序数据库,主节点只保留队列缓存,避免因为自身故障丢数据。
3.2 运行环境的安装与基础加固
系统层面,我用Debian或者Ubuntu Server的占比最多,也有用Windows宿主机跑Docker的——如果现场IT团队只会管Windows,用Windows也未尝不可。但你要是问我的个人倾向,纯Linux + Docker Compose是最省心的组合:升级、备份、迁移都方便。
安装Node-RED在Linux下只需要一行命令:
# 安装Node-RED(官方脚本,主要用于本地/边缘节点) npm install -g --unsafe-perm node-red如果用Docker部署,我更推荐这样跑:
# 创建持久化目录 mkdir -p /opt/node-red/data # 启动容器,映射1880端口用于编辑器,映射1881端口用于HTTP API接收 docker run -d \ --name node-red \ --restart=always \ -p 1880:1880 \ -p 1881:1881 \ -v /opt/node-red/data:/data \ -e TZ=Asia/Shanghai \ nodered/node-red:latest装完第一件事不是急着连PLC,而是先把两件事做了:
- 打开设置文件 settings.js,修改 adminAuth 配置,为编辑器设置用户名密码。Node-RED默认不设密码,只要网络可达,任何人都能打开1880端口改你的流,这在工厂内网里是高危漏洞。
- 设置
httpNodeRoot和路径安全。如果HTTP接口要对外提供数据,建议放在单独的端口、子路径下,并在前面加一层反向代理做访问控制。
我在现场见过不少工程师在调试机上把Node-RED跑起来以后,就长期不设密码放在那里,后面被不知情的人改乱了流,排查起来非常痛苦。这个坑一定要提前避开。
4. OPC UA接入的几种节点选型与关键配置
4.1 节点库选择
Node-RED连OPC UA,常用的是node-red-contrib-opcua这个节点包。它在工业场景里算是社区活跃度最高、维护比较频繁的了。安装方式:
# 在Node-RED安装目录下执行,或者通过编辑器右上角菜单的“管理面板”搜索安装 npm install node-red-contrib-opcua除了OPC UA,如果你走S7协议直连西门子PLC,也有一个很关键的节点node-red-contrib-s7,是老牌的S7通讯节点,支持S7-200/300/400/1200/1500,但S7-1200/1500需要PLC侧打开“允许来自远程对象的PUT/GET通讯访问”。这个设置对很多安全要求高的项目不开放,所以我在大多数场景下还是优先OPC UA。
4.2 OPC UA Endpoint 的填写细节
说一个我见过最多人填错的地方。在node-red-contrib-opcua的Server节点里,Endpoint URL大多数人直接填:
opc.tcp://192.168.0.10:4840但实际连接时经常报BadCertificateUntrusted或者BadSecurityModeRejected。原因一般是安全策略不匹配。西门子PLC侧的OPC UA服务器默认的安全策略是Basic256Sha256签名加密,而Node-RED端默认会用None来尝试连接。解决办法是在节点的 Security Policy 下拉框里选Basic256Sha256,并设置对应的Security Mode;如果是连接WinCC的OPC UA服务器,还要注意WinCC侧配置的用户名密码认证(如果开了)也要一并填进去。
| 配置项 | 建议取值 | 说明 |
|---|---|---|
| Endpoint URL | opc.tcp://IP:4840 | 端口以实际服务器为准 |
| Security Policy | Basic256Sha256 | 兼容S7-1500默认策略 |
| Security Mode | Sign and Encrypt | WinCC/R uw rz大多支持 |
| Username / Password | 按服务器设置 | 如开启用户认证则必须填 |
| 连接超时 | 10~15秒 | 不要设太短,PLC重启后会恢复重连 |
4.3 变量节点ID的获取方式
这一步是新手最容易卡住的地方:OPC UA的节点ID不是简单填个“DB1.0”就能读,它的格式是类似ns=2;s=DeviceSet.Device1.Temperature或者ns=3;s=“DB1”.TagName这种命名空间加标识符的组合。
我的建议是:先用Node-RED OPC UA节点里的Browse功能去浏览一下服务器地址空间。具体操作是在opcua-client节点的配置界面里,先配置好Server连接,然后点开Browser页签,就能看到服务器上可访问的节点树。从树里找到你要读的量,右键复制NodeId,或者直接用Browse测通以后再填写到订阅列表里。这个方法比翻手册猜ID要快得多。
如果把PLC侧配置了符号名,西门子S7-1500的OPC UA节点结构里往往能直接看到带设备名的变量,非常直观。如果用WinCC,注意WinCC的OPC UA服务里暴露的变量路径跟WinCC内部变量管理器的结构有关,最好在WinCC侧先建一个专门的“报警数据导出”变量组,把需要报警判断的量集中挂进去,后续维护会省很多事。
5. 一步步搭出“报警判断+去抖+锁存+通知”完整流
5.1 流的整体骨架
这是一个我实际项目里用过的报警流结构,思路非常通用。Node-RED里用OpC UA节点订阅过来的数据,会经过下面这几个环节:
OPC UA数据流 ↓ 功能函数处理(状态解析、报警条件判断) ↓ 去抖与锁存逻辑 ↓ 报警事件分发(新报警、恢复、持续) ↓ 通知输出(MQTT / HTTP / 邮件 / 微信)5.2 报警条件判断的Function节点
新建一个Function节点,把每个变量的报警上下限、报警级别做一张配置表,类似下面这样:
| 变量 | 单位 | 报警低限 | 报警高限 | 恢复低限 | 通知级别 |
|---|---|---|---|---|---|
| 空压机温度 | ℃ | 无 | 85 | 75 | 高 |
| 冷却水压力 | MPa | 0.15 | 无 | 0.25 | 高 |
| 料位 | % | 20 | 90 | 25/85 | 中 |
注意“报警低限/高限”和“恢复低限/恢复高限”并不相同,这就是上面提到过的报警回差(死区)。如果报警值和恢复值设成同一个数,现场信号在临界点来回抖动时,系统就会反复触发“报警-恢复-报警”,不仅烦人,还会造成通知轰炸。所以好的报警逻辑一定要带回差。
Function节点里写的核心逻辑大致是这样:
// 假设msg.payload为当前读取到的数据,包含多个属性值 const temp = msg.payload.CompressorTemp; let alarmState = false; let alarmDesc = ""; // 高温报警:85度触发,75度恢复,这里用lastTemp做状态记忆 if (context.get("tempAlarm") === true) { // 处于报警中,检查是否恢复 if (temp <= 75) { context.set("tempAlarm", false); alarmState = false; alarmDesc = "空压机温度已恢复"; } else { alarmState = true; alarmDesc = "空压机温度高报警持续中"; } } else { // 正常状态,检查是否触发 if (temp >= 85) { context.set("tempAlarm", true); alarmState = true; alarmDesc = "空压机温度高报警触发"; } } // 组合成报警事件 if (alarmState || alarmDesc.indexOf("已恢复") >= 0) { msg.payload = { event: alarmState ? "alarm" : "recover", tag: "CompressorTemp", value: temp, desc: alarmDesc, time: new Date().toISOString() }; return msg; } else { return null; // 没有事件产生,丢弃 }这个结构就是报警状态机的最小实现:用context记住每个报警变量当前是否处于报警态,输入一个新值后,先判是“报警中”还是“正常态”,再决定是触发新报警、维持报警还是发出恢复事件。这样比每次拿到数据都粗暴比较大小要可靠得多。
5.3 去抖与防抖的必要性
工业现场的模拟量信号,尤其是温度、压力、液位这些,天然带有噪声。哪怕用OPC UA接到了数据,也经常出现某个扫描周期瞬时值越限、下个周期又恢复正常的情况。如果不做滤波,一天能推几百条假报警出去,没人受得了。
我习惯在OPC UA订阅和报警判断之间加一个滑动窗口均值滤波,或者在报警判断之后加一个持续时间确认。比如:
- 温度连续3个采样周期(每周期1秒)都超过85℃才确认报警;
- 恢复也一样,连续3个周期都低于75℃才发“恢复”事件。
用Function节点实现“连续确认”很简单,记录一个类似以下结构的计数器:
// 初始为0,越限+1,正常则清零 let cnt = context.get("cnt") || 0; if (msg.payload.CompressorTemp >= 85) { cnt = cnt + 1; if (cnt >= 3) { cnt = 0; // 确认报警后,交给状态机处理 msg.payload = { event: "preAlarm", tag: "CompressorTemp" }; return msg; } } return null;这个去抖环节看着不起眼,但对报警质量的影响是决定性的。我在很多项目里把报警推送从“一天几十条无效推送”降到“一天几条有效推送”,靠的就是这两个小改进。
5.4 将报警事件发送到通知渠道
状态判断和去抖做完以后,后面就是纯粹的输出部分了。我用过的组合大致有这几种:
- MQTT转发:Node-RED的
mqtt out节点配置一个本地或云端的Broker地址,上层系统订阅Topic,是一种非常适合系统间异步解耦的方式。Topic建议按层级规划,如factory/site1/line2/alarm。 - 企业微信/钉钉机器人:用HTTP Request节点POST到机器人Webhook地址,注意企业微信有每分钟20条的频率限制,如果报警量大要做合并发送(把多条报警拼成一条消息发)。
- 邮件发送:配置SMTP服务后,用
email节点发出,这个适合不需要实时提醒的日报汇总。 - HTTP API:给第三方系统提供一个POST接口,Node-RED作为HTTP Server,收到报警事件转推给别的平台。
提醒一句:通知渠道的失败要兜底。邮件发失败、机器人Webhook失效、网络抖动导致MQTT断开,这些情况在工业现场一定会遇到。我通常在消息输出前加一个delay节点的重试机制,或者在通知下游加一个失败分支,把失败的消息重新放进一个队列,后续错峰再发,避免报警静默丢失。
6. 历史数据的就地留存与轻量看板
6.1 为什么边缘节点要自带历史
很多人在设计报警系统时只关注“当前的报警推出去”,忽略了“历史报警要可追溯”这个硬需求。尤其是现场出现设备事故后,业主第一个问题一定是:刚才报警了吗?报警信息在哪?当时的工艺参数是多少?
如果把所有历史都只放在WinCC或第三方平台里,那么“旁路报警”方案就失去了意义——既然还是要靠WinCC查历史,那不如直接在WinCC里写报警脚本。所以边缘网关一定要自带一套轻量级的数据留存方案。
常用做法是Node-RED把报警事件和周期采样数据写入SQLite或者InfluxDB。这两个都够轻,跑在边缘主机上毫无压力:
| 数据库 | 类型 | 适合的场景 | Node-RED节点 |
|---|---|---|---|
| SQLite | 关系型 | 报警事件表、操作日志表 | node-red-node-sqlite |
| InfluxDB | 时序型 | 模拟量趋势、长时间历史曲线 | @influxdata/influxdb-client |
我一般把报警事件存SQLite——每条记录有事件时间、Tag名、报警值、描述、是否恢复、恢复时间,这个结构用关系型查起来最顺手;把周期采样的模拟量存InfluxDB——测点、时间、数值的模型天然匹配时序库,查询趋势曲线也简单。
6.2 一张免开发的报警查询看板
数据有了,给人看还得有界面。很多团队一想到看板就以为要开发Web前端,其实Node-RED的HTTP API配合一小段HTML就够用了。最简单的做法是:
- Node-RED里建一个HTTP In节点,监听
GET /alarm/list; - 在Function节点里执行SQLite查询,把最近N条报警查出来;
- 用
template节点拼一个HTML表格页面返回。
这段页面代码不需要复杂框架,纯原生HTML+简单CSS就能实现一个支持按时间筛选、按报警级别筛选的列表页面。如果还想看趋势,ECharts的折线图组件也可以直接在前端页面里引入,后端用Node-RED提供JSON数据接口。
我做过一个项目,现场维护人员就是用这样一个页面,配合手机浏览器直接打开网关地址,随时能看上一天的报警记录和参数曲线,连专门的App都省了。对于很多中小型产线,这就是成本最低的“报警管理系统”。
7. 部署上线时的几个边界问题与排查经验
7.1 网关IP规划与防火墙规则
边缘网关要接入现场控制网络,第一件事就是网络规划。我建议网关至少配两个网口:一个口接PLC/控制网,一个口接办公网或者上层数据网。控制网段和办公网段物理隔离,网关做数据单向或者按需双向转发,这样即使办公网里的电脑中毒,也不至于直接打到PLC上。
如果现场只有一张网,也要在网关上用防火墙规则限制访问来源。Ubuntu上UFW开启后只放行必要端口:
# 开放Node-RED编辑器端口(只给内网管理IP) sudo ufw allow from 192.168.1.100 to any port 1880 proto tcp # 开放OPC UA客户端出站连接(S7-1500默认4840) sudo ufw allow out to 192.168.0.10 port 4840 proto tcp7.2 无线/4G场景下的断线重连
有些项目把边缘网关部署在无固定IP的站点,通过4G/5G上云。这个时候,OPC UA、MQTT这类TCP长连接都需要重点考虑“连接断了能不能自动恢复”。
Node-RED自带的一些节点,如MQTT Out节点,本身有自动重连机制,但要设置好Keep Alive周期和Clean Session。OPC UA的客户端节点也支持断线重连,但我在实际测试中发现,如果网线被拔掉超过几分钟,OPC UA通道可能需要手动重启才能恢复。所以有个技巧:在Node-RED里加一个定时器流,每隔几十秒ping一次PLC地址,一旦ping不通就触发一条通知,同时通过Node-RED的API重新部署OPC UA相关流程,强制重新建立连接。
7.3 网关自身的“看门狗”
边缘网关承担了报警推送职责,它自己反而不能死掉。所以我通常会做三层防护:
- 系统级看门狗:
systemd里设置服务崩溃自动重启; - 容器级守护:Docker的
--restart=always保证容器异常退出后自动拉起; - 业务级心跳:Node-RED里定时发一条心跳消息到MQTT Broker或者企业微信群里,如果上层的监控平台连续几个周期收不到心跳,就说明边缘网关挂了,自动通知IT/OT管理员。
后两点很多人容易忽略。尤其第三点——报警系统的故障要能被感知。网关自己断了,报警全都不发,现场还以为一切正常,这是最危险的情况。
7.4 最麻烦的一类问题:OPC UA连接“时而能连时而不能连”
我在排查这类问题时,最终定位到的原因五花八门,这里把最常遇到的几种列出来:
| 排查方向 | 常见原因 | 解决办法 |
|---|---|---|
| 安全策略 | Endpoint和安全模式不匹配 | 用UA Expert这类工具先连一次,查看服务器支持的安全策略列表 |
| 证书信任 | 客户端证书不被服务器信任 | 在PLC或WinCC侧导入Node-RED生成的证书/指纹 |
| 连接数限制 | WinCC的OPC UA服务有Session数上限 | 检查服务器端配置,限制单Session订阅量 |
| 网络MTU | 大包被交换机分片丢弃 | 把扫描周期调长,单次采集的点数不要太多 |
| 防火墙状态 | 端口被办公网防火墙拦截 | 在网关上tcpdump抓包确认连接建立情况 |
遇到OPC UA连接问题,不要直接猜,先在网关上抓包:
# 抓取到PLC 4840端口的包,确认TLS握手是否完成 sudo tcpdump -i eth0 host 192.168.0.10 and port 4840 -w opcua.pcap再用Wireshark打开pcap文件,看TLS层有没有报错、Server侧的Hello消息有没有返回,基本就能定位是策略还是网络层的问题了。
8. 报警流上线后,我建议补充的几个增强功能
基础报警跑通以后,如果你还有余力,下面这几个增强方向是实际使用效果最好的,按性价比排序:
第一个是报警等级分级与值班规则。把报警分成“提示”“一般”“严重”三级:提示级只在看板里显示,不发通知;一般级推送给当班人员;严重级在一般级之外还要额外电话或短信通知值班经理。实现上不难,报警判断时带上等级信息,输出端根据等级分流到不同通知节点就行。
第二个是报警汇总日报。用一个Cron定时器(比如每天早上8点),查询前一天所有报警事件,生成一张汇总表发到管理群或者邮箱。这个功能上线后,很多管理者反而觉得比实时报警更有价值,它能让管理层对设备运行状态有一个全景式的判断。
第三个是报警与工单系统的对接。如果现场运维已经用了工单系统,Node-RED可以把报警事件作为自动创建工单的触发源,通过HTTP API调用工单系统的创建接口。这样一来,报警不再只是“一条消息”,而是进入了实际处理流转的闭环里。
这三个功能的实现都在Node-RED的流层面完成,不需要动PLC和WinCC。这也是边缘网关方案最有魅力的地方——系统的能力扩展从原来的“改上位机程序”变成了“加流、接API”,工程效率完全不在一个量级。
9. 回看这个方案的得失,说几句实在话
折腾了这么多项目,我对“不改WinCC和PLC加报警功能”这套做法最大的体会是:它把“约束”变成了“设计边界”。因为不能动老系统,你被迫把所有新能力全部外置,这反而让系统边界越来越清晰——控制归控制,信息归信息,后期的维护责任划分也省了很多扯皮。
每个项目能不能用这套方案,我建议先自评三个条件:一是现场有没有可用的OPC UA接口(或者至少是支持S7协议的以太网连接);二是能否接受报警逻辑放在边缘侧而不是PLC里;三是有没有人能维护这台边缘网关。三个条件如果都满足,那这个方案几乎可以闭眼选。
至于那些问我“PLC和WinCC迟早要换代升级,这套网关到时候怎么办”的人,我的回答是:网关本身就是中立的,PLC换成别的品牌、WinCC升级成别的SCADA,只要OPC UA这个标准还在,Node-RED这边的流只需要改一下服务器地址和节点ID就能接着用。这份“可迁移性”是我当初选OPC UA而不是走私有协议的根本原因。
最后再分享一个纯个人经验:真到现场调试那几天,不要盯着Node-RED编辑器里那条绿线发呆,要盯的是PLC扫描周期里那几十毫秒的变化。你把采样周期调到1秒,PLC侧基本无感;但如果把OPC UA的采样周期调到100毫秒以下,又订阅了几百个节点,老款PLC的通讯负载可能会明显升高,这时候手摸PLC的CPU模块面板,温度都能感觉出来。工业现场的“温柔”就在这些细节里——数据能用就行,别把它榨干。