第3章 数据采集层设计:风机SCADA数据协议解析

数据采集层,说白了就是风电智能运维平台的「耳朵」和「眼睛」。

我做了这么多年风电运维,最深的体会就是:数据采不准,后面一切分析都是白搭。你想想看,如果风速传感器偏差了0.5m/s,那功率曲线分析、故障预警全都会跑偏。

这一章,咱们就聊聊数据采集层那些事儿。我会把SCADA协议解析、高频振动采集、气象数据接入、还有缓存策略,一个一个掰开来讲。

3.1 风机SCADA数据协议解析

SCADA系统是风机的「黑匣子」。它记录了风机所有的运行参数——转速、功率、桨距角、温度、振动……

但问题来了:不同厂家、不同型号的风机,SCADA协议五花八门。我见过最头疼的是某国产风机,Modbus地址表居然和手册对不上,排查了整整两天。

3.1.1 Modbus TCP 协议解析

Modbus TCP是SCADA数据采集的「老黄牛」。它简单、稳定、兼容性好。大部分风机的主控系统都支持这个协议。

协议结构

Modbus TCP的报文结构其实不复杂。我习惯把它拆成两部分看:

  • MBAP头(7字节):事务标识符、协议标识符、长度、单元标识符
  • PDU(变长):功能码 + 数据

举个例子,读取风机有功功率(地址40001):

# 请求报文(十六进制)
00 01 00 00 00 06 01 03 00 00 00 01

# 解析:
# 00 01 - 事务标识符(随便给个编号)
# 00 00 - 协议标识符(固定为0)
# 00 06 - 后面数据长度(6字节)
# 01    - 单元标识符(风机编号)
# 03    - 功能码(读保持寄存器)
# 00 00 - 起始地址(40001对应地址0)
# 00 01 - 读取数量(1个寄存器)

避坑指南

我曾经踩过的坑:

  • 地址偏移问题:有些厂家地址从0开始,有些从1开始。我建议先读一个已知值验证。
  • 字节序问题:大端小端搞反了,功率值直接翻倍。我习惯先读一个固定值(比如风机编号)确认字节序。
  • 超时设置:风场网络环境复杂,我一般设3秒超时,重试3次。

3.1.2 OPC UA 协议解析

OPC UA是新一代的工业通信标准。它比Modbus TCP更强大,但也更复杂。

我个人习惯用OPC UA采集新风机数据。为什么?因为它自带数据模型,你不需要去查地址表。节点ID直接告诉你这个参数是什么。

OPC UA 地址空间结构

节点ID 显示名称 数据类型 描述
ns=2;s=WindTurbine.1.ActivePower 有功功率 Float 当前有功功率(kW)
ns=2;s=WindTurbine.1.WindSpeed 风速 Float 机舱风速(m/s)
ns=2;s=WindTurbine.1.RotorSpeed 叶轮转速 Float 叶轮转速(rpm)
ns=2;s=WindTurbine.1.GenTemp 发电机温度 Float 发电机绕组温度(℃)

Python 采集示例(使用opcua-asyncio库)

import asyncio
from opcua import Client

async def collect_scada_data():
    client = Client("opc.tcp://192.168.1.100:4840")
    
    try:
        await client.connect()
        print("连接成功!")
        
        # 读取多个节点
        nodes = [
            client.get_node("ns=2;s=WindTurbine.1.ActivePower"),
            client.get_node("ns=2;s=WindTurbine.1.WindSpeed"),
            client.get_node("ns=2;s=WindTurbine.1.RotorSpeed")
        ]
        
        values = await client.read_values(nodes)
        
        data = {
            "active_power": values[0],
            "wind_speed": values[1],
            "rotor_speed": values[2]
        }
        
        return data
        
    finally:
        await client.disconnect()

# 运行
result = asyncio.run(collect_scada_data())
print(result)

我的经验:OPC UA的订阅模式比轮询好太多。我建议用订阅方式采集变化数据,能减少网络压力。但注意,订阅的采样间隔别设太短,我一般设100ms。

3.2 高频振动数据采集

振动数据是风机故障诊断的「听诊器」。SCADA数据只能告诉你「风机出问题了」,但振动数据能告诉你「哪里出问题了」。

我记得有一次,某风场连续报齿轮箱故障。SCADA数据显示温度正常、转速正常。但振动数据一看——高频段有异常峰值。后来拆机检查,果然是齿轮点蚀。这就是高频振动采集的价值。

3.2.1 NI DAQ 采集方案

NI DAQ是工业级采集方案。精度高、通道多、抗干扰能力强。适合做整机振动监测。

采集参数配置

参数 推荐值 说明
采样率 10 kHz - 50 kHz 齿轮箱振动建议25.6 kHz
采样时长 1 - 10 秒 建议采集10秒做FFT分析
量程 ±10V 或 ±5V 根据传感器灵敏度调整
耦合方式 AC耦合 滤除直流分量

Python 采集代码(使用nidaqmx库)

import nidaqmx
import numpy as np

def collect_vibration_data(device="Dev1", channel="ai0", rate=25600, duration=10):
    """
    采集高频振动数据
    """
    samples = int(rate * duration)
    
    with nidaqmx.Task() as task:
        # 配置AI通道
        task.ai_channels.add_ai_voltage_chan(
            f"{device}/{channel}",
            min_val=-10.0,
            max_val=10.0
        )
        
        # 配置采样时钟
        task.timing.cfg_samp_clk_timing(
            rate=rate,
            sample_mode=nidaqmx.constants.AcquisitionType.FINITE,
            samps_per_chan=samples
        )
        
        print(f"开始采集,采样率{rate}Hz,时长{duration}秒...")
        data = task.read(number_of_samples_per_channel=samples)
        
        # 转换为numpy数组
        vibration_data = np.array(data)
        
        return vibration_data

# 采集10秒数据
vib = collect_vibration_data()
print(f"采集完成,数据长度:{len(vib)}")

3.2.2 ADXL345 低成本方案

ADXL345是MEMS加速度计。便宜、小巧、功耗低。适合做分布式监测或叶片振动监测。

我曾在某老旧风场用过ADXL345做叶片振动监测。成本低到可以每个叶片装一个。虽然精度不如NI DAQ,但胜在数量多、覆盖广。

关键配置

  • 量程:±16g(风机振动一般不超过±5g)
  • 输出速率:3200 Hz(最高)
  • 分辨率:13位(约3.9 mg/LSB)
  • 接口:I2C或SPI

注意:ADXL345的带宽有限。3200 Hz输出速率下,实际有效带宽约1600 Hz。对于齿轮箱高频振动(可能到10 kHz以上),它是不够的。我建议只用于叶片或塔筒的低频振动监测。

3.3 气象数据接入

气象数据是风机控制的「眼睛」。风速、风向、温度、气压——这些参数直接影响风机的运行策略。

我见过最离谱的事:某风场风速仪结冰了,风机一直报「超风速停机」。结果一查,是传感器冻住了。所以气象数据接入,不仅要考虑协议,还要考虑数据质量。

3.3.1 风速/风向数据

风速仪和风向标是标配。常见的有超声波式和机械式。

数据格式示例

参数 单位 范围 精度要求
风速 m/s 0 - 50 ±0.5 m/s
风向 ° 0 - 360 ±5°
温度 -40 - 60 ±0.5℃
气压 hPa 800 - 1100 ±1 hPa

数据质量校验

我习惯在接入层做三道校验:

  1. 范围校验:风速不在0-50 m/s?直接丢弃。
  2. 变化率校验:1秒内风速从5 m/s跳到30 m/s?大概率是传感器故障。
  3. 一致性校验:相邻两个风速仪数据差异超过20%?可能有一个坏了。

3.4 数据缓存策略:Redis队列

数据采集是高频的。SCADA数据可能每秒一次,振动数据可能每秒上万次。如果直接写入数据库,数据库会崩溃。

我的解决方案是:用Redis做缓冲队列。

3.4.1 为什么用Redis?

  • :基于内存,读写延迟微秒级
  • 支持队列:List结构天然适合做FIFO队列
  • 持久化:RDB/AOF保证数据不丢
  • 分布式:多个采集节点共享一个Redis

3.4.2 队列设计

我一般设计三个队列:

队列名称 数据类型 消费频率 说明
scada:queue JSON字符串 每秒 SCADA数据,批量写入时序库
vibration:queue 二进制数据 每10秒 振动数据,压缩后写入
weather:queue JSON字符串 每5秒 气象数据,实时展示

Python 实现示例

import redis
import json
import numpy as np

class DataCache:
    def __init__(self, host='localhost', port=6379):
        self.r = redis.Redis(host=host, port=port, decode_responses=False)
        
    def push_scada(self, data: dict):
        """推送SCADA数据到队列"""
        self.r.lpush('scada:queue', json.dumps(data))
        
    def push_vibration(self, data: np.ndarray):
        """推送振动数据(二进制压缩)"""
        compressed = data.tobytes()
        self.r.lpush('vibration:queue', compressed)
        
    def pop_batch(self, queue_name: str, batch_size: int = 100):
        """批量取出数据"""
        pipe = self.r.pipeline()
        for _ in range(batch_size):
            pipe.rpop(queue_name)
        results = pipe.execute()
        return [r for r in results if r is not None]

# 使用示例
cache = DataCache()

# 采集线程推送数据
scada_data = {"timestamp": "2024-01-01 12:00:00", "power": 1500.5}
cache.push_scada(scada_data)

# 消费线程批量取出
batch = cache.pop_batch('scada:queue', batch_size=50)
print(f"取出了{len(batch)}条SCADA数据")

我的经验:Redis队列要设置最大长度。我一般设100万条。超过后自动丢弃旧数据。另外,消费端一定要做幂等处理——同一个数据被消费两次,不能影响最终结果。

3.5 本章知识体系

下面这张图,是我梳理的数据采集层整体架构。你可以看到数据从风机到平台的完整流向。

数据采集层架构图 风机SCADA系统 Modbus TCP / OPC UA 振动传感器 NI DAQ / ADXL345 气象站 风速/风向/温度 协议解析层 Modbus TCP解析 | OPC UA客户端 | 振动数据预处理 | 气象数据校验 数据缓存层(Redis队列) scada:queue | vibration:queue | weather:queue 数据存储层(时序数据库 / 对象存储)

这张图展示了数据从风机到存储的完整链路。每一层都有它的职责。数据源负责产生数据,协议解析层负责「翻译」,缓存层负责「缓冲」,存储层负责「持久化」。

我建议你在实际项目中,也按这个分层来设计。每一层独立部署、独立扩展。这样即使某一层出了问题,也不会影响其他层。

核心要点回顾:

  • SCADA数据采集:Modbus TCP适合老风机,OPC UA适合新风机
  • 振动数据:NI DAQ做整机监测,ADXL345做分布式监测
  • 气象数据:一定要做数据质量校验,别信原始数据
  • 缓存策略:Redis队列是标配,注意设置最大长度和幂等消费

专注资料整理