
2023 年 8 月,我们给一个老车间做设备联网,87 台设备,协议数出来四种。17 台 2008 年前后的老机床只有 RS485 串口,报文是厂家自己定的,手册早丢了;23 台挤压机走 Modbus RTU;31 台新一点的设备走 Modbus TCP;剩下 16 台是近五年买的,支持 OPC UA。
按台数看,三分之二的设备用的是标准协议,接起来不难。麻烦在那 17 台老机床:它们承担了车间四成的工序,产量数据缺了它们,整条线的 OEE 就没意义。厂家的人来了两次,说板卡还能用,但协议文档找不到了,只能抓包看。
结论是:不要为每种协议写一套采集程序,做一个兼容层,把协议差异关在驱动里,上层只认统一的点位模型。这样加一种新协议只改配置和驱动,不动上层。项目从 8 月做到 11 月,87 台全部接上,数据完整率百分之九十六,剩下四个点是老机床本身没有计数信号。
Modbus TCP 和 RTU 差别只在传输层,寄存器地址和含义是一套,接一种就懂另一种。OPC UA 自带信息模型,点位有名字有类型,接起来省事,但握手和会话管理要单独处理。
真正难的是私有报文。老机床的板卡每隔两秒往外吐一帧,帧头两个字节固定,后面跟长度、数据区、校验。数据区里哪两个字节是主轴转速,哪一个是运行状态,全靠抓包对着机床面板一条条试。
反推的过程很土但有效:让人在机床上操作,我盯着抓包工具看哪几个字节变了。开机、停机、调转速、按急停,每次操作记一次帧内容,比对差异字节。
主轴转速花了半天。转速从 800 调到 1200,有两个字节从 03 20 变成 04 B0,正好是 800 和 1200 的十六进制,说明是原始值不缩放,大端。报警码花了三天,因为报警只在特定条件下出现,我们守到第三天下午机床才报了一次刀具磨损。
反推还有个小技巧:先找出不变的部分。一帧里的帧头、长度、校验位是固定的,数据区里常年不变的字节多半是配置或型号,剩下会变的才是状态和参数。这样能把二十几个字节缩到七八个候选,再逐个试。
动手写驱动之前,先定了点位模型。不管什么协议,采上来的数据只有五类:运行状态、产量计数、工艺参数、报警、能耗。
每个点位有固定字段:点位键、设备号、类型、单位、采集频率、有效范围、数据质量。上层应用只认这套,不关心底下是串口还是以太网。这一步要是省了,后面每接一种协议就要改一次上层,改三次之后代码就没法维护了。
兼容层分成两半:驱动负责把字节变成数,映射负责把数变成点位。驱动按协议类型写,一共四个;映射按设备型号写,一共十一个型号。
分开的好处是新增设备型号不用写代码。同一个 Modbus 驱动,挤压机和水泵的寄存器地址不同,只是两份映射配置。车间再进同型号设备,复制一份映射改设备号就行。这套配置后来交给设备科自己维护,我们只负责驱动。
映射文件按型号命名,改一次记一次版本。有一台挤压机换了控制板,寄存器地址整体后移了 8 个,如果没有版本记录,采上来的数据会整体错位而没人发现。现在每次改映射都留一句说明,谁改的、为什么改,出问题能倒查。
采上来的数不对,九成出在三个地方:字节序、字序、缩放系数。字节序是高低字节的顺序,字序是 32 位数据里两个 16 位字的顺序,缩放是原始值乘的系数。
这三样在配置里必须显式写,不能靠猜。我们的规矩是每个点位都写 endian,32 位数据再加 word_order,浮点或小数量级加 scale。写全了啰嗦,但排错快——数不对的时候先看这三项,比翻手册快。
下面这段是一台老机床和一台挤压机的适配配置,点位映射、字节序、缩放、数据质量都在里面。
devices:
- dev_id: CNC-014
driver: serial_frame
link: {port: ttyS2, baud: 9600, databits: 8, parity: none, stopbits: 1}
poll_ms: 2000
frame:
head: "AA55"
len_field: 1
tail: "0D0A"
checksum: sum8
points:
- {key: run_state, offset: 3, type: uint8, map: {0: 停机, 1: 运行, 2: 报警}}
- {key: spindle_rpm, offset: 4, type: uint16, endian: big, scale: 1}
- {key: feed_rate, offset: 6, type: uint16, endian: big, scale: 0.1}
- {key: alarm_code, offset: 8, type: uint8, quality: bitmask}
- dev_id: PRESS-003
driver: modbus_rtu
link: {port: ttyS4, baud: 19200, databits: 8, parity: even, stopbits: 1}
poll_ms: 1000
points:
- {key: run_state, unit_id: 1, func: 1, addr: 0, type: bool}
- {key: stroke_count, unit_id: 1, func: 3, addr: 12, type: uint32, word_order: low_first}
- {key: oil_temp, unit_id: 1, func: 3, addr: 20, type: int16, scale: 0.1}
defaults:
stale_after_ms: 15000
retry: {times: 2, backoff_ms: 300}
clock: devicestale_after_ms 是这份配置里要紧的一项。超过十五秒没采到新值,这条数据就标成过期,不参与 OEE 计算。没有这一项,断线之后界面还显示着上一次的数,看上去一切正常,实际已经停了半小时。这块的补数任务放在搭贝里定时跑,按设备补近一小时的空档。
23 台 Modbus RTU 设备挂在同一条 485 总线上,波特率必须一致。有一台挤压机出厂设的是 9600,其余是 19200,接上去之后整条总线时不时丢包,查了两天才发现是它。
轮询时序也踩过。一开始给每台设备都配 200 毫秒轮询,23 台挤在一起,总线上全是请求,响应超时率百分之十七。改成按设备分组轮询,每组间隔 50 毫秒,超时率降到百分之一以下。
总线空闲时间也要算进去。485 是半双工,转换器收发切换有延迟,我拿示波器量过,从发完一帧到能收下一帧要 8 毫秒左右。轮询间隔如果压到 10 毫秒,请求会叠在一起,表现是偶发校验错。所以 50 毫秒这个数不是拍出来的,是响应时间加切换延迟再加一点余量。
老机床的串口转换器不稳定,一天要断两三次。一开始的重连逻辑是断了立刻重连,结果转换器还没恢复就又被连上,反复几次之后驱动直接卡死。
后来加了退避:失败一次等 300 毫秒,连续三次失败等 5 秒,十次以上等 30 秒并报警。重连期间该设备的所有点位标成无数据,不保留旧值。这个改动之后,驱动连续跑了四个月没重启过。
老设备没有时钟。采上来的数据带不了时间,只能落库时补采集时间。补时间听着简单,但有两处要注意:一是采集端和数据库的时钟要同步,二是补的时间要标明来源。
我们定了个规矩,时间戳带来源字段,device 表示设备自带,collector 表示采集端补的,server 表示服务端补的。报表按采集时间统计时,只用 device 和 collector 两种,server 那种延迟大,会把数据挪到错误的班次里。
87 台设备如果都按一秒采,数据量一天两千多万条,存不下也用不上。按用途分三级:状态和报警两秒一次,工艺参数五秒一次,能耗十五秒一次。
分级之后一天四百万条左右。老机床的私有报文本身两秒一帧,就按两秒采,不额外加快——加快也拿不到新数据,只是把同一帧重复存。
兼容层的一个作用是把"这台设备的数据可不可信"标出来。质量标记有四档:正常、过期、越界、缺失。越界是指数值超出配置的有效范围,比如油温采到 900 度,明显是传感器坏了。
标出来之后,看板上灰色的就是不可信的点。以前的做法是采到什么都往上报,报表里混着错数,分析的人先花半小时判断哪条是真的。现在至少在界面上不骗人。
17 台老机床里,有 5 台的状态信号是从继电器上取的,只能判断"通"和"断",判断不了是运行还是待机。这 5 台的 OEE 只能算可用率,算不了性能开动率。
这件事没有技术解。设备科的计划是三年内换掉 3 台,剩下 2 台作为备用机保留。兼容层的好处是换设备的时候不用改上层:新设备接 OPC UA,加一份映射,点位键沿用老的,历史曲线能接上。
问:私有报文没有文档,反推出来的映射可靠吗?
答:靠抽样验证。反推完字段,让设备在已知状态下跑一段,把采上来的值跟面板显示逐项比。我们对转速比了 40 组,误差都在正负 2 转以内;报警码只验证了 6 种,其余标成"未验证",界面上不显示含义只显示原始码。
问:一条 485 总线上挂多少台设备合适?
答:看轮询周期和响应时间。我们的经验是 19200 波特率下挂 20 台左右,每台轮询间隔不低于 50 毫秒,一轮下来两秒内能转完。再多就要么加总线,要么放慢轮询,硬堆会丢包。
问:采集频率调高是不是数据更准?
答:不一定。设备本身的刷新周期决定了数据上限,采得比它快只是重复存同一个值。另外频率高会挤占总线,影响其他设备。判断方法是先看设备手册的刷新率,按它的一半到一倍设,再根据报表需要往下调。
问:老设备断线时,界面应该显示什么?
答:显示"无数据"加断线时间,不显示上一次的值。这个决定当初争论过,生产那边希望显示旧值,看起来连续。实际运行下来,显示旧值会让人误判设备在运行,宁可留空。真要画曲线,用虚线断开表示缺数据,不用直线连过去。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。