4. 设备状态数据采集软件与边缘计算

设备状态监控系统的数据质量,很大程度上在采集端就已经决定。产线上的灌装机、封口机、贴标机、输送电机、杀菌釜等设备,其运行状态往往通过 PLC 寄存器、变频器参数、智能电表、振动传感器和温度变送器暴露出来。采集软件的任务,就是把这些分散、异构、节拍各异的信号,稳定地变成带时间戳、可追溯、可被上层消费的数据流。本节围绕实时采集程序开发、采样频率设定、数据缓存与断线续传、边缘侧预处理四个环节展开,重点讲清楚工程上"怎么做"以及"怎么判断做对了"。

一、实时采集程序的基本结构

一个可用的采集程序通常由采集驱动层、调度层、缓冲层和上传层组成。驱动层负责与具体协议打交道,例如通过 Modbus TCP 读取 PLC 保持寄存器、通过 OPC UA 订阅变频器节点、通过 MQTT 接收无线振动传感器上报。调度层按设定的周期触发读取或维持订阅,并为每条数据打上采集时间戳。缓冲层把数据写入本地队列或环形缓冲区,上传层再按批量或按需发送到边缘网关或云端。

以一条饮料灌装线为例,假设需要采集灌装机的运行状态字、瞬时产量计数、主电机电流,以及一台杀菌釜的温度和压力。用 Python 结合 pymodbus 实现轮询采集的核心逻辑可以写成如下形式(示例参数,非实测):

from pymodbus.client import ModbusTcpClient
import time, json, queue

client = ModbusTcpClient("192.168.10.21", port=502)
buf = queue.Queue(maxsize=20000)

TAGS = [
    {"name": "filler_status", "addr": 100, "type": "u16", "scale": 1},
    {"name": "filler_count",  "addr": 102, "type": "u32", "scale": 1},
    {"name": "motor_current", "addr": 110, "type": "u16", "scale": 0.01},
]

def poll_once():
    ts = time.time()
    for t in TAGS:
        rr = client.read_holding_registers(t["addr"], count=2, slave=1)
        raw = rr.registers[0]
        buf.put({"tag": t["name"], "ts": ts,
                 "value": raw * t["scale"], "quality": "good"})

这里有两个容易被忽略的工程细节。第一,时间戳应在读取动作发起前或完成后立即生成,而不是在上传时补打,否则网络抖动会污染数据的时间属性。第二,读取失败时不能简单丢弃,而应写入一条 quality 为 bad 的记录,保留"该时刻无有效值"这一事实,这对后续判断设备是否真正停机非常关键。

二、采样频率的设定依据

采样频率不是越高越好。频率过高会放大网络与存储压力,也可能把传感器噪声当成真实波动;频率过低则会漏掉关键瞬态。合理的做法是按信号类型分层设定:

  • 状态量(运行/停止/报警/门禁):采用事件触发或 1 Hz 轮询,状态变化时立即上报,避免漏掉短时停机。
  • 工艺量(温度、压力、液位):按工艺时间常数选取,一般取时间常数的 1/5~1/10。例如杀菌釜温度变化较慢,1 s 一次足够;若用于升温曲线分析,可提高到 200 ms。
  • 电气与振动量(电流、振动加速度):电流用于负载与能耗分析,通常 1~5 s;振动若用于轴承故障特征提取,需要 kHz 级高速采样,此时应在传感器或采集卡侧完成,而不是走 PLC 轮询。

设定时还要考虑设备节拍。灌装机若每分钟 300 瓶,单瓶节拍 200 ms,产量计数若按 1 s 轮询,只能得到累计值,无法还原瞬时节拍,因此计数类信号更适合读取累计寄存器并做差分,而不是提高轮询频率去"抓"每一次跳变。

三、数据缓存与断线续传

产线现场网络并不总是可靠,交换机重启、无线 AP 切换、云端维护都会造成短时断连。采集程序必须具备本地缓存能力,常见做法是使用本地消息队列或嵌入式数据库(如 SQLite、LevelDB、Redis Stream)作为落盘缓冲,并设置容量上限与淘汰策略。

断线续传的关键是"可重放"。每条缓存记录应包含唯一序号、采集时间戳、标签名、值和质量码。重连后按序号顺序补传,接收端依据序号去重,避免重复入库。下面是一个简化的续传判断逻辑:

def flush_buffer():
    while not buf.empty():
        batch = [buf.get() for _ in range(min(200, buf.qsize()))]
        try:
            mqtt_publish("line1/telemetry", json.dumps(batch))
            ack_last_seq(batch[-1]["seq"])
        except ConnectionError:
            for item in reversed(batch):
                buf.put(item)   # 放回,等待下次重试
            break

工程上建议缓存容量至少覆盖 2~4 小时的数据量。以 50 个标签、平均 1 s 一次计算,每小时约 18 万条,4 小时约 72 万条,单条按 100 字节估算约 70 MB,普通工控机完全可承受。同时要监控缓存水位,超过 80% 时产生告警,防止磁盘写满导致采集进程崩溃。

四、边缘侧预处理的工程实现

把全部原始数据直接上云既不经济也无必要。边缘侧应完成三类预处理:滤波、去噪和归一化。

滤波用于抑制周期性干扰。对温度、压力这类缓变量,一阶低通滤波简单有效:y(k) = α·x(k) + (1−α)·y(k−1),α 取 0.1~0.3 时可明显平滑毛刺而不过度滞后。对电流信号,若存在工频干扰,可在采集卡侧使用 50 Hz 陷波,或在软件中对固定窗口做滑动平均。

去噪针对随机尖峰。工程上常用中值滤波:取最近 5 个点排序后取中位数,能有效剔除单点跳变,例如传感器偶发的满量程异常值。对于振动信号,则更适合用小波阈值去噪,保留冲击特征的同时压制背景噪声。

归一化用于多源数据融合与模型输入。常见做法是 min-max 归一化到 [0,1],或按设备额定值做量纲统一,例如把电机电流除以额定电流得到负载率。归一化参数应在边缘侧固定保存,避免每次重启后基准漂移。

预处理参数(滤波系数、窗口长度、归一化上下限)应作为配置项管理,并与设备型号绑定。更换设备或调整工艺后必须重新确认,不能沿用旧参数。

下面这张图把本节四个环节串成一条完整的数据通路,标出了各环节的输入、关键处理与输出形态,便于对照实现:

设备状态数据采集与边缘处理通路 现场设备与传感器 PLC 状态字 / 计数 变频器电流 温度 / 压力 / 振动 实时采集程序 Modbus / OPC UA / MQTT 按标签分层采样 打时间戳 + 质量码 本地缓存队列 序号 + 时间戳 + 值 容量上限 / 水位告警 断线落盘,重连续传 边缘侧预处理 低通滤波 / 中值去噪 min-max 归一化 参数与设备型号绑定 上传至边缘网关 / 云端 批量发送 · 按序号去重 断线时暂存,重连后按序补传 采样频率分层:状态量事件触发或 1 Hz;工艺量按时间常数 1/5~1/10; 电流 1~5 s;振动 kHz 级在传感器侧完成。计数类读累计值做差分。

实现完成后,如何判断这套采集链路是否达标?可以从三个可观察点检查。其一,时间连续性:在边缘网关统计每分钟到达的记录数,与理论值偏差应小于 1%,且时间戳单调递增。其二,断线恢复:人为拔掉网线 5 分钟再恢复,检查缓存水位是否回落、补传记录是否完整、接收端有无重复。其三,预处理效果:对比滤波前后的电流曲线,毛刺幅度应明显下降,而设备启停的真实阶跃仍能被识别。若滤波后阶跃被抹平,说明系数过大或窗口过长,需要回调参数。

最后需要强调,采集与预处理不是一次性配置工作。设备增减、工艺调整、网络改造都会改变数据特征,工程上应把采样周期、滤波参数、缓存容量、归一化基准纳入版本化配置,并在每次变更后重新做上述三项检查,才能保证上层状态识别与故障诊断拿到的是可信的输入。