☰
发那科机床数据转SNMP:从FOCAS到Zabbix的落地实践
2026/10/19 9:16:16 网站建设 项目流程

前阵子接手一个看起来不大、但折腾人的需求:车间里20来台发那科机床,设备部门能通过控制器面板看运行状态,IT部门在监控平台上却什么都看不到。不是没数据,而是机床在说自己的方言——FOCAS、宏变量、PMC地址,监控平台只会听通用语言——SNMP。让两边对上话,本质上就是给机床做一套“翻译器”,也就是这个项目标题说的“发那科机床设备数据转SNMP”。

这类工作现在越来越常见。传统工厂的数控设备数据封闭,管理层想实时看设备状态,就要把设备数据接进统一监控系统。很多监控平台、SCADA、MES系统都支持SNMP,网络设备、服务器也是一套运维体系,如果把机床也变成SNMP网元,就能复用现有的监控体系,不用额外上一套昂贵的工业物联网平台。

我做这个项目时,把重点放在三块:怎么把发那科的数据稳定取出来,怎么翻译成标准SNMP OID,怎么让Zabbix这类监控平台好用。下面把这套落地过程完整拆一遍。

1. 项目背景与需求拆解

1.1 为什么要做数据转换,而不是直接读机床数据

先说一个很多刚接触工业网络的人会掉坑的概念:发那科机床自带的数据接口是FOCAS,这是发那科私有协议,底层是TCP/IP,有固定的端口和报文格式。IT监控平台(Zabbix、Prometheus、Nagios)默认只会SNMP、HTTP、ICMP这一套。让Zabbix直接去解析FOCAS报文,理论上可以,实际上没人这么干:协议栈要自己维护,加密认证要处理,还要处理不同系统版本的差异,工作量巨大。

所以项目思路不是让监控平台去学会FOCAS,而是把数据在源头翻译成SNMP,让机床“伪装”成一台标准网络设备。这样监控平台不需要任何定制开发,只要知道OID就能采集,甚至能把机床当交换机一样管理。

1.2 核心需求拆解:三类数据、两种上报方式

我把项目需求拆成三类数据:

第一类是实时运行数据,包括主轴转速、进给速度、各轴坐标、主轴负载、当前程序号。这类数据变化快,需要周期性获取,对应SNMP的主动轮询(Polling)。

第二类是状态和告警数据,包括机床运行状态(自动运行、手动、暂停、报警)、当前报警号、报警文本。这类数据适合两种方式结合:周期轮询保证完整性,再加SNMP Trap主动推送紧急告警。

第三类是基础信息,包括机床型号、系统版本、控制器序列号、IP地址。这类数据基本不变,定期读一次即可。

两种上报方式也得想清楚。SNMP轮询由监控端发起,能保证数据“一定拿得到”,但延迟高,监控端轮询周期长,报警可能延迟十几秒。SNMP Trap是设备主动推送,延迟低,但UDP不可靠,丢包就丢信息。生产现场的报警必须又快又稳,我的方案是双通道:轮询兜底,Trap秒级推送。

1.3 项目范围与预期效果

我这次项目的范围是20台发那科加工中心,系统版本主要集中在发那科31i和0i-F系列。预期效果有三条:监控平台能实时看到每一台机床的运行状态;机床报警能在5秒内推送到IT人员手机上;历史数据能支撑后续的OEE分析和稼动率统计。

搞清楚需求边界很重要。这个项目不是要做完整的MDC(制造数据采集)系统,不做程序传输、不做刀具管理、不做质量管理,只做“数据上网”。边界划清楚,后续的方案选型才不会被过度设计拖累。

2. 整体架构与方案选型

2.1 两层采集模型:边缘网关加SNMP Agent

第一个要决策的问题,是网关怎么部署。很多项目会把采集程序直接装在一台中央服务器上,让服务器通过局域网同时连所有机床。这样省设备、省维护,但有个隐患:发那科FOCAS的Session连接数量有限,一台网关进程连太多机床,某个连接异常可能拖垮整个程序。

我采用的方案是边缘网关部署,每台机床边上放一台工控机,用工业交换机连接。工控机上运行采集服务,负责通过FOCAS读数据;同时运行SNMP Agent服务,把数据暴露给上层监控平台。网关与监控平台之间是标准SNMP,网关与机床之间是FOCAS,两层逻辑完全隔离。

这样设计的优点在于,FOCAS层连接数受控,每台网关只服务一台或两台机床;SNMP层的问题不影响采集层,即使监控平台把网关查询卡死,机床侧数据采集还能继续缓存。

2.2 方案对比:snmpd扩展脚本与纯Python SNMP Agent

网关上的SNMP Agent有两条技术路线。

第一条是直接用系统自带的snmpd,配合pass_persist机制扩展私有OID。snmpd负责标准SNMP协议栈,收到请求后把OID丢给外部脚本,脚本返回结果。第二条是用PySNMP库自己实现一个纯Python的SNMP Agent,把所有逻辑都写在Python里,灵活性最高,但是要处理好协议栈细节、超时、并发,开发和维护成本高。

我做了个对比:

对比项snmpd + pass_persist纯Python Agent
开发量低,重点写数据查询和OID路由高,需要处理协议细节
稳定性高,snmpd久经考验依赖进程稳定性,需要考虑守护和重启
扩展性中,动态数据需要脚本配合缓存高,可任意扩展协议逻辑
调试难度低,snmpwalk直接测较高,需要协议层排查
Trap发送可用snmptrap命令配合可直接用PySNMP发送

最终我选了第一条,snmpd加脚本扩展。原因是项目刚起步,要以最小代价跑通完整链路,后面要扩展再用PySNMP重构也不迟。

2.3 OID规划:把自己的数据编成一本“电话簿”

SNMP之所以通用,是因为所有数据都有统一编号,这个编号就是OID。全世界所有厂家的私有数据都在enterprises节点下挂着自己的企业号,然后各自往下编排。

项目里还没申请正式企业号,所以我先占用了一个规划号段:1.3.6.1.4.1.99999。以后申请到正式企业号,批量替换前缀即可。OID树规划如下:

  • 1.3.6.1.4.1.99999.1:设备基础信息,包括控制系统型号、系统版本、PMC类型、设备编号
  • 1.3.6.1.4.1.99999.2:运行状态,包括运行模式、主轴转速、各轴坐标、进给速度、主轴负载
  • 1.3.6.1.4.1.99999.3:告警信息,包括当前报警数、最新报警代码、最新报警文本
  • 1.3.6.1.4.1.99999.4:连接状态,包括FOCAS链路状态、最近一次采集时间

OID规划听起来很抽象,其实就是给自己定义数据字典。这里有两条经验:所有OID最好固定下来,不要为了压缩节点数用动态索引让OID飘忽不定,否则监控平台配置会很痛苦;字符串型数据尽量用OCTET STRING类型,对应SNMP里的s类型,数字型数据用INTEGER或Gauge32,不要用Counter32来存会上下波动的主轴负载,Counter32语义上只增不减,会误导监控平台。

3. 数据采集与转换的实现细节

3.1 发那科侧准备:参数、端口与连接方式

先把机床侧打开一个“数据出口”。发那科系统要在网络参数里启用嵌入式以太网功能,我这边启用的是FOCAS1,对应端口8193。系统版本不同,参数项位置不同,大致包括:开启以太网功能、分配IP地址、设置子网掩码、设置端口号、开启FOCAS功能。

这里有个坑,很多老机床虽然控制面板上有网络设置,但根本没有硬件支持FOCAS,需要确认控制器的硬件配置。如果只有RS232串口,就得另外加串口网关,整个方案就要改。项目启动前,先用发那科官方自检功能确认每台机床能否建立FOCAS连接。

连上之后,主要读取的参数大概有这些:

  • 当前程序号,通过读取程序相关数据块拿到
  • 主轴转速、主轴负载,通过读取主轴数据
  • 各进给轴坐标、伺服负载,通过读取轴数据
  • 运行模式、状态信息,通过读取CNC状态
  • 当前报警号、报警文本,通过读取报警数据

发那科的数据接口返回的一定是一串原始值,有些带符号,有些是十六进制BCD码,不能直接拿去用。我写代码时统一在一个模块里做数据清洗,比如BCD码转十进制、二进制位解析、偏移量加工。这个环节不做好,后面SNMP数据永远是错的。

3.2 采集程序的轮询策略与缓存设计

采集程序的核心是一个循环,周期性地做这几件事:重新建立FOCAS连接、读取快数据、读取慢数据、更新本地缓存、按需发送Trap。

快数据包括主轴转速、进给速度、主轴负载、当前运行模式,变化快,我按1秒周期轮询。慢数据包括当前程序号、报警文本、各轴坐标,我按5秒周期轮询。之所以区分周期,是因为FOCAS连接也是资源,读得越频繁,机床控制器负载越高,还可能出现连接被机床侧限制的情况。

采集结果统一写到一个共享缓存里,缓存结构就是一个Python字典,以OID字符串为键,以(数据类型,数值,时间戳)为值。比如这样:

cache = { ".1.3.6.1.4.1.99999.2.1.0": ("i", 1, 1680000000), ".1.3.6.1.4.1.99999.2.2.0": ("i", 12000, 1680000000), }

SNMP查询进来时,Agent只读缓存,不管底层采集是否完成。这样避免一个请求卡住导致整个SNMP请求超时。这也是一条重要经验:采集循环和SNMP响应循环必须分离,别把一次SNMP轮询变成现场实时查询,SNMP的get操作等不起一次FOCAS通信。

3.3 用pass_persist实现SNMP OID的动态响应

我用的方案是snmpd加pass_persist脚本。在snmpd.conf里这样配:

view systemview included .1.3.6.1.4.1.99999 pass_persist .1.3.6.1.4.1.99999 /usr/local/bin/fanuc_cnc_bridge.py

这段配置的意思是:当snmpd收到OID前缀在1.3.6.1.4.1.99999下的请求时,不是从系统MIB查数据,而是把请求转发给fanuc_cnc_bridge.py这个脚本,由脚本返回结果。

pass_persist协议本身很简单,脚本标准输入会不断收到命令:

PING

脚本必须返回:

PING

收到GET请求时,客户端输入:

get .1.3.6.1.4.1.99999.2.1.0

脚本返回:

.1.3.6.1.4.1.99999.2.1.0 INTEGER 1

收到GETNEXT请求时,输入是:

getnext .1.3.6.1.4.1.99999.2.0.0

脚本需要找到比这个OID大的下一个可用OID,并返回它的值。这一步是很多脚本写得不好的地方:如果预先不知道完整的OID列表,GETNEXT会很难处理。所以我在脚本里维护了一个排序好的OID列表,每次查询直接用二分查找定位下一个节点,保证遍历时不会漏项。

3.4 主动推送报警:SNMP Trap的落地姿势

轮询能发现问题,但发现速度不够快。机床报警这种需要秒级响应的场景,必须靠Trap主动推。

我写了一个独立的Trap发送模块,采集程序发现报警号发生变化时,组装一条Trap发到监控服务器。用命令行的方式最直观:

snmptrap -v 2c -c public 192.168.10.50:162 "" .1.3.6.1.4.1.99999.0.1 \ .1.3.6.1.4.1.99999.3.1.0 i 1005 \ .1.3.6.1.4.1.99999.3.2.0 s "SPINDLE OVERLOAD"

这条命令的作用是:发出一条v2c版本的Trap,目标监控服务器是192.168.10.49的162端口,企业OID是.1.3.6.1.4.1.99999.0.1,后面带着两个变量绑定:报警代码和报警文本。

这里的关键点是报警去重。机床报警会在系统里持续挂着,直到操作工复位。如果每10秒就发一次同一条报警Trap,监控平台会被刷爆,告警变成噪音。我在代码里只记录报警状态变化时才触发Trap,如果报警号没变,即使采集到同一条报警,也不重复推送。报警恢复时也发一条Trap,监控平台才能做告警自动恢复。

还要处理网络抖动:Trap是UDP,可能丢包。我的办法是发送失败时重试三次,间隔2秒。重试还不能成功,就写到本地告警日志,等网络恢复后补发。补发逻辑不复杂,但要写,不然丢报警是迟早的事。

4. 监控平台对接与数据可视化

4.1 用Zabbix把机床当作“网络设备”管理

SNMP数据处理完,剩下就是接监控平台。我用Zabbix作为示例,因为它是开源监控里部署率最高的。

在Zabbix里,给每台机床创建一个主机,主机接口选择SNMP,IP填网关地址,版本选SNMPv2c,团体名填自定义值。然后添加监控项,对应关系如下:

  • 机床当前状态,键值监控项类型选择SNMP代理,OID填.1.3.6.1.4.1.99999.2.1.0,数值类型选数字,我给状态做了值映射:0停止、1运行、2报警、3手动
  • 主轴转速,OID填.1.3.6.1.4.1.99999.2.2.0,单位是rpm
  • 主轴负载,OID填.1.3.6.1.4.1.99999.2.5.0,单位是%
  • 当前报警代码,OID填.1.3.6.1.4.1.99999.3.1.0,数值类型选字符

Zabbix支持对SNMP返回的值做预处理。比如发那科返回的主轴负载,有些状态是带小数的百分比,我会在上游统一转成整数百分比,Zabbix里就不用再做除法,这样也避免浮点精度问题。

4.2 编写私有MIB,让监控平台“认”数据

光有OID能用,但运维人员看到一串数字.1.3.6.1.4.1.99999.2.1.0会疯掉。要解决体验问题,需要写一个MIB文件,把这些OID定义成有名字的变量,导入监控平台后自动翻译成“设备运行状态”“主轴负载”这类可读名称。

MIB文件的核心结构大致是这样:

FANUC-CNC-MIB DEFINITIONS ::= BEGIN IMPORTS MODULE-IDENTITY, OBJECT-TYPE, Integer32, enterprises FROM SNMPv2-SMI; fanucCncMIB MODULE-IDENTITY LAST-UPDATED "202401010000Z" ORGANIZATION "Workshop IoT Project" CONTACT-INFO "ops@example.local" DESCRIPTION "Private MIB for FANUC CNC data via SNMP bridge" ::= { enterprises 99999 } cncRunStatus OBJECT-TYPE SYNTAX Integer32 MAX-ACCESS read-only STATUS current DESCRIPTION "CNC run status: stop(0), run(1), alarm(2), manual(3)" ::= { fanucCncMIB 1 } END

MIB写得对不对,用专门的工具可以验证,也可以直接在Zabbix、WhatsUp Gold这些监控平台上导入测试。MIB不是运行必需件,但却是运维文档的一部分,少了它,后续新来的运维同事没法快速搞清OID含义。我强烈建议从一开始就写MIB,哪怕只是把OID清单整理成表格,也比不留文档强。

4.3 数据看板怎么搭才能接地气

数据接到Zabbix后,我搭了一个车间看板,展示四类卡片:机床总数、运行中的机床数、报警中的机床数、最近10条报警记录。每台机床单独一个页面,放当天的主轴负载曲线、设备状态时间分布饼图、报警时长报表。

看板不能只是好看,要真的解决管理问题。比如OEE计算,其实可以直接用SNMP采集到的状态数据折算:机床处于运行状态的时长除以计划生产时长,就是设备稼动率。有了这个,管理层每天打开看板就能看到车间整体的生产状态,不用再跑到现场看控制柜指示灯。

有个细节要注意:Zabbix默认把SNMP数据当离散值存储,绘图时会有台阶状曲线。主轴负载这种高频数据,要用Zabbix的预处理配合趋势存储,或者用Grafana直接查Zabbix数据源做平滑展示。否则看负载曲线会觉得数据一顿一顿的,像卡顿的视频。

5. 常见问题与排查技巧实录

5.1 FOCAS采集层常见的坑

第一个坑是连接数限制。发那科控制器对FOCAS连接数有限制,如果车间里同时有MDC系统、刀具管理系统、以及我这个SNMP网关在连同一台机床,连接数会超限。表现为某个系统偶尔连不上,或者刚连上就被踢掉。解决办法是收敛车间里的采集客户端,所有数据统一走一个采集网关,再由网关分发,不要各系统各自连机床。

第二个坑是端口被防火墙挡。机床所在网段和网关所在网段如果跨了VLAN,8193等端口可能需要防火墙放行。这个坑看起来基础,但最容易卡好几天,原因是机床那边显示连接正常,网关这边就是握手失败。排查时直接用网络工具测试TCP端口连通性,别先怀疑FOCAS协议。

第三个坑是机床不提供稳定的数据刷新周期。发那科的宏变量和PMC数据,有一部分是需要梯形图程序主动写入的,如果机床厂家没写梯形图,读出来的值永远是0或者旧值。这种情况下,单纯改采集程序没用,要跟机床PMC程序或系统参数协商,确认某些数据点是否真实有意义。我以前遇到过一台机床主轴负载数据一直为0,排查到最后发现厂家在PMC里根本没采集这个变量。

5.2 SNMP层常见的坑

第一类问题,snmpwalk能拿到部分OID,但某些OID总报“noSuchName”。这个问题几乎都是因为脚本里没有实现该OID对应的数据分支,或者OID路径写错了。我调试时会在pass_persist脚本里加日志,输出收到的每条请求,这样一眼就能看到snmpd把哪个OID丢进来了,对吧。

第二类问题,监控平台显示“Not supported”。这种一般是数据类型不匹配。例如定义的是Integer32,但脚本返回了一个字符串文本。检查一下OID分支的返回类型是否和MIB定义一致。

第三类问题,频繁snmpwalk会导致采集程序假死。原因就是我先前提到的:脚本处理请求走了同步采集流程,FOCAS通信一慢,多个SNMP请求排队,最后进程卡死。解决方法是彻底分离采集和响应,让脚本永远只查内存缓存。这条我踩过几回,每次改完都清爽很多。

下面是问题速查表:

现象可能原因排查方向
机床连不上端口被封、连接数超限、FOCAS未开启检查TCP端口连通性、检查控制器参数
数据一直为0PMC/宏变量未被梯形图写入确认数据源是否真实有效
Zabbix不支持监控项OID返回类型与定义不匹配用snmpwalk人工确认返回类型
报警重复刷屏无告警去重逻辑增加Trap变化检测与去重
Trap收不到UDP被防火墙丢弃、community不匹配抓包确认UDP报文是否到达监控端

5.3 一次典型的报警Trap排查实录

我记录一个很典型的调试过程,给后来者做个参考。

某天Zabbix突然收不到一台机床的报警Trap,但轮询状态正常。我按顺序排查:先看采集日志,发现采集程序明明读到了报警代码,也调用了Trap发送函数。再看snmptrap命令执行结果,也没有报错。那问题出在传输链路。

我在监控服务器上用抓包工具监听UDP 162端口,发现根本没有报文到达。于是怀疑是网关与监控服务器之间的防火墙拦截了UDP。因为车间网段和办公网段之间有防火墙策略,默认只放行TCP,UDP 162没有单独放开。

防火墙规则加上之后,报文能到,但Zabbix日志还是没展示告警。再查发现是SNMP Trap的community与Zabbix接收配置不一致。我把Zabbix的SNMP Trap配置里community改成一致,告警立马正常。

整个过程看起来简单,但如果一开始就抓包,能省两个小时。所以遇到Trap问题,第一件事永远是抓包,判断是没发出来、没送到、还是被拒收。

5.4 时间同步问题

最后提一个容易被忽略的问题:时间一致性。监控平台显示的报警时间,如果和机床控制器时间不一致,会误导排障。机床操作工在面板上看到的报警时间是机床本地时间,监控平台收到的是来自网关的转发时间,两者对不上,一个隐患就埋下了。

我在每台网关上都配置了NTP同步,统一指向厂区的时间服务器。机床控制器本身有时钟设置,也和网关做了一次校准。时间同步不是功能性问题,但它是数据质量的底层保障。报警时间错了,所有后续的统计报表都会跟着错。

6. 项目落地的体会与后续演进空间

6.1 先跑通最小闭环,再谈规模

这次项目的节奏是阶段式推进的。我先挑了三台不同型号的机床作为试点,把FOCAS采集、SNMP代理、Zabbix监控、Trap告警整条链路跑通。三台机器的范围足够发现数据源差异,也足够验证架构。如果一开始就铺开二十台,调试时现场乱成一锅粥,后续改动成本会成倍增长。

最小闭环跑通后再批量接入,每台机床的接入时间平均只需20分钟,主要工作是配置IP、确认数据点、在Zabbix里克隆主机模板。批量阶段最大的工作量不在技术,而在于设备管理员逐个确认每台机床的数据点映射是否符合预期。

6.2 从SNMP再到数据平台,这个转换层可以一直复用

项目做完之后,我又想明白一件事:SNMP只是“数据上网”的一种方式,不是唯一终点。这套采集网关只要把数据以标准格式输出,既可以转SNMP,也可以转OPC UA,甚至可以通过HTTP接口直接推给MES系统。我们做的真正核心工作,是把发那科私有协议的数据抽象成一套清洁、稳定的数据字典。

后续要扩展的方向很明确:申请正式的SNMP企业号,告别99999临时号段;把报警去重逻辑从网关端移到监控平台,支持更复杂的告警收敛规则;结合机床的电耗数据,做更细颗粒度的设备健康分析。

我个人在实际操作中的体会是,做这类跨协议的数据集成项目,最忌一开始追求“大而全”的物联网平台。先把数据从机床里拿出来,把格式转成IT团队熟悉的SNMP,让监控平台实实在在地用起来,价值就已经落地了。后面每加一个数据点,都是在原有的管道上增加一节,而不是推翻重来。这步棋走对了,项目的路会越走越顺。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询