欧姆龙NJ/NX PLC数据采集:FINS协议对接Node-RED实现可视化看板
搞工控的人应该都经历过这种阶段PLC 里数据一堆但想看个趋势曲线还得蹲在现场盯着触摸屏或者靠人工拿本子记。尤其是欧姆龙 NJ/NX 系列这种定位在运动控制和工厂自动化的中型 PLC底层逻辑跑得飞快可一旦牵扯到“把数据拿出来给办公网看”问题就来了——Sysmac Studio 的监控画面只能在编程软件里看上位机想读数据要么上 OPC UA要么就得折腾通信协议。OPC UA 当然好用但要部署 UA 服务器还要授权项目小的时候总觉得不太划算。所以这次我想聊的是另一条轻量级但非常能打的路线直接用 FINS 协议把 NJ/NX 的数据读出来然后喂给 Node-RED 做可视化看板。FINS 是欧姆龙自己的老牌协议说实话很多做欧姆龙的老工程师一听到这三个字母就想到当年用 HostLink 连串口的年代但你别瞧不上它——FINS/TCP 在 NJ/NX 上依然支持得非常好报文结构清晰、实时性可靠而且根本不需要额外授权配置对了直接就能跑。这篇文章面向的是谁呢就是那些手里有 NJ/NX 系列 PLC、想自己做一个小型数据采集看板但又不想一上来就引入 OPC UA、数据库、专业组态软件这类“重武器”的工程师。整个过程我按照自己的实操顺序来写硬件准备、Sysmac Studio 配置、FINS 原理、Node-RED 接入、可视化和排错。看完之后你至少能做出一个“PLC 数值实时曲线 报警列表”的网页看板而且每一层都清楚自己在干什么。1. 项目整体思路为什么选 FINS Node-RED1.1 你要解决的问题是什么先把这个项目要解决的事情说透。我见过不少现场是这么一个状态PLC 控制系统本身已经调试完毕伺服轴、气缸、温度、压力、产量计数都在 PLC 里存着但车间主任想看效率数据老板想在大屏幕上看设备状态这些需求一出来PLC 程序员就得想办法把数据“送出去”。用触摸屏自带的报表功能大部分触摸屏的历史记录能力弱得可怜存不了多少条导出还要插 U 盘。用组态软件WinCC、组态王、力控这些老牌软件没问题但授权费、开发周期、操作门槛全都不低而且很多项目就是“先看看 Trend 曲线”根本没必要上那么重的方案。用 OPC UA 从 PLC 直接读NJ/NX 支持得很好但需要做 OPC UA 服务器配置、客户端证书管理没有专门搞 IT 的人在场容易卡住。Node-RED 的好处在于它是图形化编程、自带仪表盘组件、对 TCP/UDP 这种底层协议支持非常灵活一个树莓派或者一台旧电脑就能当成数据采集网关来用。配上网关之后它既可以往 MySQL 写数据也可以发 MQTT 给物联网平台甚至可以对接自研 ERP 接口。换句话说Node-RED 不是“玩具”只是它长得像玩具真正用起来是一个正经的工业数据前置机。1.2 协议选型FINS 能干什么不能干什么FINS 是 OMRON 开发的一种应用层协议全称 Factory Interface Network Service可以跑在串口、Controller Link、Ethernet 上。我们这里用的是 FINS/TCP也就是把 FINS 报文装进 TCP/IP 包里目标端口默认是 9600。和 EtherNet/IP 相比FINS 的最大优势在于简单直接一条命令就能读一块连续的内存区域没有复杂的 CIP 对象模型用 Wireshark 抓包能一眼看懂。那 FINS 不能干什么它不像 OPC UA 那样有完整的“信息模型”不会告诉上位机“这个变量叫什么名字、单位是什么、上上限是多少”。它只提供最原始的内存读写能力你给我一个区域代码、一个起始地址、一个读取数量我返回给你一堆字节。所以我们需要自己维护一份“地址 ↔ 物理量”的映射表这也在情理之中毕竟 PLC 程序本来就是我写的变量在哪个地址放着我心里最有数。1.3 整体数据链路规划我的最终链路是这样设计的PLCNJ/NX→ 以太网 → Node-RED 所在主机 → Dashboard 网页 / MQTT / 数据库PLC 侧在 Sysmac Studio 里启用 FINS 服务把需要上报的数据整理到一片连续的内存区域比如 D100 开始放温度、D110 开始放温度设定值、D120 开始放设备状态字。Node-RED 侧每隔 500 ms 或 1 s 向 PLC 发送一条 FINS “读内存区”指令拿到二进制数据后按既定的地址映射拆成浮点数、整数、开关量再转成 JSON。展示侧Node-RED Dashboard 做实时曲线和仪表盘同时可以把解析后的数据继续发给 MQTT由 EMQX 这类 Broker 做转发或者直接落库到 IoTDB、InfluxDB 这类时序数据库做长期存储。这套链路中Node-RED 既是协议客户端也是消息路由器。它的运行环境只需要 Node.js跨平台能力很强Windows、Linux、树莓派都没问题。实际项目里我倾向于用一台 Debian 虚拟机或者树莓派专门跑它稳定、省电还能长期开机。2. 硬件与软件准备2.1 PLC 侧NJ/NX 系列与 Sysmac StudioNJ 和 NX 系列在欧姆龙的产品线里定位是“机器自动化控制器”NJ 是插卡式控制器NX 更像是一个可扩展的 PAC 平台不过单纯从通信角度来看两者没有本质区别。你只需要确认你手上的 PLC 配有以太网口并且型号支持 FINSNX1P2、NX102、NX701、NJ501、NJ301、NJ101 这些全都支持。准备工作的第一步是安装 Sysmac Studio。这个软件是欧姆龙 NJ/NX 系列的编程调试环境基本只能从欧姆龙各区域官网下载软件本体带激活锁试用期过了需要授权。版本方面建议用 1.5 以上的版本太老的版本对 NX 系列支持不好。打开软件之后新建设备时注意选择正确的 PLC 型号和单元版本选错了有可能导致功能块不兼容。2.2 上位机软件环境Node-RED 安装Node-RED 的安装方式很多个人最推荐用 npm 全局装npm install -g node-red node-red系统需要预先装好 Node.js 12 以上版本。如果你不想碰命令行也可以直接用 Dockerdocker run -it -p 1880:1880 nodered/node-red启动之后浏览器访问http://本机IP:1880就能进入编辑界面。Dashboard 组件需要额外安装后面我在可视化章节会专门讲。另外建议在 Node-RED 所在主机上提前装好两个网络工具ping和telnet。别看它们简单后面排查“连不上 PLC”的问题时这俩就是第一道照妖镜。Ubuntu 下装 telnet 用apt install telnetWindows 下需要在“启用或关闭 Windows 功能”里勾选 Telnet 客户端。2.3 网络规划与 FINS 节点号分配这是新手最常忽略的地方。FINS/TCP 虽然跑在 TCP/IP 之上但它内部还有一套“FINS 网络号 节点号 单元号”的地址体系。简单理解IP 地址解决的是“我这台电脑怎么找到那台 PLC”FINS 地址解决的是“PLC 的内部 CPU 知道这条报文应该由谁来处理”。对于 NJ/NX 直连的简单场景我们一般把网络号都设成 0单元号设成 0最重要的就是节点号。PLC 的 FINS 节点号通常可以手动指定常见做法是和 IP 地址的最后一段保持一致。比如 PLC 的 IP 是 192.168.1.10那就把 PLC 的 FINS 节点号设为 10上位机 Node-RED 主机的 IP 是 192.168.1.50那我就在客户端侧把本地节点号设为 50。为什么节点号这么重要因为当初 FINS 的设计是允许跨网络寻址的报文里每个字节都得能区分源和目的。如果你上位机声明的节点号和别的设备撞了PLC 端的路由表可能分不清谁是谁轻则时报错重则整个网络通信乱套。所以我规划网络时会单独列一张表把每个上位机节点号固定下来就像给它分配身份证号一样。3. Sysmac Studio 侧关键配置3.1 启用 FINS 服务与端口把 PLC 的 IP 地址设置好了之后需要在 Sysmac Studio 里做 FINS 相关配置。路径大致是项目树 → 控制器设置 → 节点设置 / 内置 EtherNet/IP 端口设置里面有一个 FINS 相关选项区。很多型号默认是启用 FINS 服务的但保险起见你要确认一下“FINS/TCP 服务”和“FINS/UDP 服务”都处于启用状态并且端口是标准的 9600。如果你电脑装了防火墙记得放行 UDP/TCP 9600 端口不然 Node-RED 永远连不上。另外需要在“控制器设置”里确定 FINS 节点号。比如我设成 10保存并传送到 PLC 之后PLC 侧的网络参数就生效了。这里提醒一句修改节点配置之后通常需要重新启动 PLC不像改普通变量那样热切换就能生效。我在现场调试时有一次改完节点号没重启抓包抓了半天才发现 PLC 用的还是旧节点号。3.2 地址映射把变量暴露给 FINS这个环节是整个项目里最容易翻车的点我必须单独拿出来说。NJ/NX 和老的 CS/CJ 系列不一样它程序里的变量默认是“符号变量”只要没手动指定内存地址变量就活在内存池里并没有传统的 CIO/DM 硬地址。而 FINS 指令只认区域代码和地址不认变量名。你说“读变量 Motor_Current”FINS 听不懂它只会说“读 D100”。所以你必须想办法把一个符号变量和一个 FINS 可访问的内存地址关联起来。Sysmac Studio 里有“数据映射”功能可以把变量映射到固定的内存区域但实际用下来最稳的方案还是老办法在 PLC 程序里加一段数据搬运逻辑把需要上报的变量集中搬到连续的一段地址里。比如我在程序里建立一个数组或者结构体放在 D100 开始的区域温度、压力、速度、状态字全都按顺序排好FINS 一次性读 D100 开始的 N 个字就全部拿到手了。用这种“搬数据”的方式有两个好处第一不依赖 Sysmac Studio 的映射配置细节版本差异影响小第二整个数据区是连续的上位机读起来效率高。缺点是要占一点扫描周期但对于几百个字的搬运量来说影响几乎可以忽略。3.3 在线测试先用第三方工具验证连接在对接 Node-RED 之前强烈建议先用一个独立工具验证 FINS 通路是通的。这一步能省下后面大量的排查时间。最简单的验证方式是用 Wireshark 搭配一个开源的 FINS 测试小工具或者直接用 Node-RED 里现成的 FINS 节点先跑一次读操作。如果你不想装任何第三方 GUI也可以在 Linux 命令行下用ncnetcat检查 TCP 9600 端口通不通nc -zv 192.168.1.10 9600只要返回succeeded至少说明物理链路和端口没有问题。接下来再用一条实际的 FINS 命令去读数据如果读到的数据和 Sysmac Studio 监控里看到的一致那就说明通信链路已完全打通可以开始 Node-RED 的可视化部分了。4. FINS 协议原理与报文分析4.1 FINS/TCP 通信模型想要真正用好 FINS我建议你至少理解一次报文结构而不是全程黑盒调用。FINS/TCP 的整体流程是先建立 TCP 连接然后交换“节点地址数据”可以理解成一个握手包节点地址数据告诉 PLC 我这台上位机的 FINS 节点号是多少之后双方开始发送正常的 FINS 请求/响应。FINS 报文的内部结构分两块头部和命令。头部固定 10 个字节包含 ICF、RSV、GCT、DNA、DA1、DA2、SNA、SA1、SA2、SID 这些字段。简单记中间有一对“源地址”和“目标地址”分别是 PLC 的 FINS 节点号和上位机的 FINS 节点号最后一位 SID 是报文序号随手填一个自增的数就行。命令部分则分为命令码和参数。常见的命令码包括0101读内存区、0102写内存区、2301读时钟等。我们这次核心用的就是 0101 读命令。4.2 一条“读 D100 十个字”的报文拆解我用具体的报文例子来说明。假设要读 PLC 的 DM 区 D100 开始的 10 个字那么命令部分应该是这样字节位置数据含义命令码0x01 0x01读内存区区域代码0x82DM 区字单位起始地址高0x00地址高位起始地址低0x64地址低位100 的十六进制是 0x0064数量高0x00读取数量高位数量低0x0A读取 10 个字加上前面 10 字节的 FINS 头报文的 FINS 部分就是80 00 02 00 0A 00 00 32 00 01 01 01 82 00 64 00 0A。其中0A是 PLC 的节点号32是上位机的节点号01是 SID。这里我举例时忽略了 FINS/TCP 最外面的长度头实际通过 Socket 发送之前系统协议栈会把整段数据封装好如果你是自己写 Socket 程序则需要再包一层“FINS”Ascii 头加长度字段。这也是为什么我建议多数场景优先使用现成节点库可以少踩封装层的坑。PLC 收到这条命令后如果成功会返回同样的 10 字节 FINS 头然后跟一个 2 字节响应码。响应码为0000就表示正常后面跟着你要的数据响应码非零就代表错误比如地址不存在、数量超出范围、命令不允许执行等。4.3 地址计算与数据类型对应关系NJ/NX 传统上支持的内存区域包括 CIO 区、WR 区、HR 区、DM 区等。对于我们的应用来说DM 区区域代码 0x82最常用因为 Sysmac Studio 里可以很方便地把变量分配或者搬运到 DM 区地址。读取到的原始数据是大端字节序。什么意思就是返回的每一个“字”占两个字节高字节在前低字节在后。你如果按小端方式去解析读出来的 16 位整数值马上就是错的。所以我在 Node-RED 里解析时一定会用readInt16BE而不是readInt16LE这也是新手最容易踩的坑。对于 Sysmac Studio 里的数据类型和 FINS 读出来的字对应关系大致如下PLC 变量类型占用字数Node-RED 解析方式INT1readInt16BEUINT1readUInt16BEDINT2readInt32BEREAL2readFloatBEBOOL一个位按位取读取对应字后按位与如果你把一个 REAL 类型的数据放在 D100那么 FINS 读回来的 D100 和 D101 两个字拼起来就是一个 32 位浮点数。直接用readFloatBE从 D100 位置开始读就行顺序正好和欧姆龙内存一致这点实测下来很省心。但要注意DINT 和 REAL 在地址连续时前两个字到底谁高谁低不同系列有过一些差异遇到解析结果离谱时先把两个字交换一下再读基本能解决。5. Node-RED 两种接入方式5.1 方式一直接用现成 FINS 节点Node-RED 社区里有现成的欧姆龙 FINS 节点这是最快能跑通的方式。安装方法在 Node-RED 界面右上角的“管理面板”里搜索omron-fins找到st-one-software/omron-fins节点点击安装即可。这个节点支持 FINS/TCP 的读写操作配置项也就那几项PLC 的 IP、端口、PLC 节点号、本地节点号、读写区域和地址。用这个节点读 D100 起始的 10 个字流程非常简单一个 Inject 节点定时触发接到“FINS 读”节点节点里填好区域为 DM、起始地址为 100、数量为 10输出就是一个带payload的 Buffer 对象。后面再接一个 Function 节点用msg.payload.readInt16BE(0)这种办法把二进制拆出来就行。现成节点的好处是省事、封好了 TCP 重连和报文封装。坏处也有一是社区节点的维护质量参差不齐官方 SDK 版本迭代后偶尔会出现无人维护的情况二是出了问题排查起来要翻开节点源码看它怎么封装的反而不如自己写来得心里有底。5.2 方式二手写 FINS 客户端节点我自己更倾向于在 Function 节点里直接手写一个轻量级的 FINS 客户端原理并不复杂。下面是读 D100 起始 10 个字的核心帧构造逻辑你可以把这个代码片段放在一个 Function 节点里配合 TCP Socket 节点使用// 构造 FINS 读命令0101 // 注意这里只构造 FINS frame实际发送时根据你用的 TCP 节点决定是否补 FINS/TCP 头 const finsFrame { icf: 0x80, rsv: 0x00, gct: 0x02, dna: 0x00, da1: 0x0A, // PLC 节点号 da2: 0x00, sna: 0x00, sa1: 0x32, // 上位机节点号 sa2: 0x00, sid: 0x01, cmd: 0x0101, area: 0x82, // DM 区 address: 100, count: 10 }; const buf Buffer.alloc(16); buf[0] finsFrame.icf; buf[1] finsFrame.rsv; buf[2] finsFrame.gct; buf[3] finsFrame.dna; buf[4] finsFrame.da1; buf[5] finsFrame.da2; buf[6] finsFrame.sna; buf[7] finsFrame.sa1; buf[8] finsFrame.sa2; buf[9] finsFrame.sid; buf.writeUInt16BE(0x0101, 10); buf[12] finsFrame.area; buf.writeUInt16BE(finsFrame.address, 13); buf.writeUInt16BE(finsFrame.count, 15); // 发送 context.get(socket).write(buf);按照我的实测10 个字以内的读取需求用一条 UDP 直发也比 TCP 更省事FINS/UDP 的报文结构几乎一模一样少了建立连接的握手包。不过为了保证长时间运行不掉线我还是推荐走 TCP。手写这种方式最大的价值是当现场出现“为什么读出来的数据是乱的”这类问题时你能自己从协议层面去找原因而不是靠猜。5.3 数据解析与格式转换FINS 读回来的是 Buffer解析过程相当于“翻译”。我的习惯是在 Node-RED 里用一个专门的数据转换节点把全项目的解析逻辑集中管理。假设 PLC 侧的数据区布局如下D100温度REAL摄氏度D102压力REALMPaD104线速度REALm/minD106当前产量UINT件D107报警代码UINT0 表示无报警那么一个解析节点里可以这样写const raw msg.payload; // Buffer const data { temperature: raw.readFloatBE(0), pressure: raw.readFloatBE(2), speed: raw.readFloatBE(4), count: raw.readUInt16BE(6), alarm: raw.readUInt16BE(8) }; msg.payload data; return msg;这里每个偏移量为什么是 2因为 REAL 占两个字节读第一个浮点数从偏移 0 开始第二个就从偏移 2 开始。如果你读取的是 20 个字那么真实可用的数据字节数是 40偏移要按这个规律往后排。项目里千万不要凭感觉猜偏移直接用“PLC 数据布局表 脚本注释”的形式维护后续交接也方便。解析完成之后我一般还会补一个函数把数据同时挂到msg.topic和msg.payload上这样后面的 Dashboard 图表和数据库写入节点都能直接消费不需要再做二次转换。6. 可视化面板与运维6.1 用 Dashboard 搭看板Node-RED 的可视化依托node-red-dashboard组件。安装方式cd ~/.node-red npm install node-red-dashboard装完重启 Node-RED右侧节点列表里就会出现dashboard分类里面有 gauge、chart、text、button、ui_control 这些常用组件。我的习惯是搭一个“设备监控总览”页面放这几个元素一个折线图chart显示温度和压力的实时趋势。两个仪表盘gauge显示温度和压力的当前值。一组文本text显示产量、设备状态、报警代码。一个报警列表用 switch 节点判断报警代码非 0 时把报警信息丢给ui_control或者一个自定义 UI 列表。折线图组件ui_chart的输入格式要求是msg.payload为数值msg.topic为这条曲线的名称。所以你可以直接在一个 Inject 节点定时把解析好的 JSON 拆成多条消息分别发到同一个 chart 节点里图表上就会自动出现多条颜色的曲线。6.2 数据长期存储与 IoT 扩展实时看板满足即时监控但要说“一个月之后我要看看设备性能趋势”那就必须把数据落库了。Node-RED 最常见的搭配是写 MQTT解析好的数据通过 MQTT Out 节点发到 EMQX 这类 Broker再由另一个程序订阅 Topic 写入时序数据库比如 IoTDB、InfluxDB、TDengine。为什么不直接让 Node-RED 写数据库也能写但从工程解耦的角度讲MQTT 是个很好的中间层。数据采集只负责采集存储和展示是独立模块以后换数据库、加算法分析都不用动 PLC 侧和 Node-RED 里的采集逻辑。我的经验是小项目直接写 InfluxDB 一点问题没有但如果这个项目后续要接更多 PLC或者要做设备比较提前把 MQTT 这层管道铺好会省很多事。6.3 长时间运行的稳定性优化Node-RED 虽然跑起来稳定但作为数据采集网关还是有几个细节需要专门处理轮询间隔不能太短。PLC 扫描周期可能只有几毫秒但 FINS 通信毕竟要走网络太频繁的请求会给 PLC 增加不必要的负载。我一般把轮询周期设在 500 ms 到 1 s 之间既能看到实时趋势又不会把 PLC 的通信口打满。Socket 断线重连。如果网络抖动导致 TCP 连接断开Node-RED 里的 TCP 节点不会自动重连必须监听close事件延时后重新发起连接。社区 FINS 节点通常已经处理了重连但如果你自己用 TCP 节点这个一定要做。数据上报失败要有日志。不要只在看板上显示“数据未更新”最好在 Node-RED 的 debug 输出里记录失败次数这样远程维护才能第一时间发现问题。7. 踩坑记录与排查技巧7.1 常见 FINS 响应码速查通信过程中最让人头疼的就是返回错误码。下面是我遇到频率比较高的几种响应码含义常见场景0x0101本地节点错误上位机 FINS 节点号未正确声明或不在允许列表中0x0103目标节点错误PLC 节点号与报文中的 DA1 不一致0x1103命令参数错误区域代码、地址或数量超出发送范围0x1101未定义命令发了一个 PLC 不支持的 FINS 命令码排查这类错误的最佳方式是用 Wireshark 抓包把报文展开看几秒钟通常一眼就能定位是地址填错还是节点号填错。不要凭感觉在 Node-RED 里反复试那样只会浪费时间。7.2 连接超时与掉线如果你发现 Node-RED 每次连接 PLC 都超时先别急着怀疑协议配置。第一步先pingPLC 的 IP 地址确认物理链路通不通第二步用telnet 192.168.1.10 9600测一下端口通不通。如果 ping 通但 telnet 不通基本就是防火墙挡了 9600 端口要么在 PLC 端开防火墙规则要么把上位机和 PLC 放在同一网段同一广播域里。如果连接建立成功但每隔一段时间就掉线很有可能是网内存在 IP 冲突也可能是上位机的 TCP Keep-Alive 参数和 PLC 不匹配。我会在 Node-RED 的 TCP 节点配置里手动开启 Keep-Alive并设置较短的探测周期。7.3 数据错位、乱值、负值变正值的排查读出来一堆“看起来不太对”的数是最容易让人崩溃的。我把这类问题分成三类数据类型不匹配PLC 里是 DINT你按 INT 解析数据当然错。先确认“PLC 变量类型 → FINS 占用字数 → Node-RED 解析函数”三者对齐。字节序不对欧姆龙用大端你按小端读了。把readInt16BE换成readInt16LE立刻就能验证。地址偏移对不上D100 起的数据你写了从 D101 读起一位之差全盘错乱。解析前先在 Sysmac Studio 的监控表里确认实际数据分布。我曾经遇到过一次浮点数高位和低位顺序反了的问题最后发现是 PLC 程序里数据搬运用了“字交换”指令。后来我养成了一个习惯任何数据搬到 FINS 区域之前都要在上位机侧先读一遍原始字节和 Sysmac Studio 里的十六进制监控对照一下确认大端顺序没被 PLC 程序“动手脚”再去写解析脚本。7.4 工具推荐Wireshark、节点测试器最后推荐两个有望帮你“救命”的工具。第一个是 Wireshark。抓包的时候过滤条件写tcp.port 9600你就能看到 Node-RED 发出去的 FINS 请求和 PLC 返回的响应。把所有字段展开对照第 4 节里的报文结构表基本能解释所有通信层问题。第二个是 Sysmac Studio 自带的“内存监控”表。在 PLC 在线状态下可以直接看到 D 区里每个地址的十六进制数值。用这个值去和 Node-RED 解析出来的值对比立刻知道到底是 PLC 侧没写好还是上位机读错了。整套链路跑通之后我发现自己在 PLC 程序侧慢慢养成了一个习惯凡是需要上报的数据统一整理成连续的几段区域按固定字长排好再在旁边写清楚备注。上位机侧永远只读固定的几个地址块。别嫌这个办法土在维护阶段它能让你少接无数个半夜打来的电话。你要是从这个项目开始也这么干后面会省心很多。