4、数据采集与模拟:真实数据源介绍(SCADA、PMU)、模拟数据生成器设计、时间序列数据格式规范

好,咱们进入第四讲。这一章聊的是数据源头——说白了,就是你的可视化系统到底吃什么样的数据。

我见过不少新手,一上来就急着画界面、调颜色,结果数据一接进来,格式不对、时间戳乱跳、采样率对不上……整个系统直接崩掉。嗯,数据采集这块,其实是整个可视化系统的地基。地基不稳,上面画得再漂亮也没用。

4.1 真实数据源:SCADA 与 PMU

电力系统里,数据源主要分两大类。我习惯把它们比作「体检报告」和「心电图」——一个看整体趋势,一个看实时细节。

4.1.1 SCADA 系统

SCADA,全称是监控与数据采集系统。它在电力系统里已经用了三四十年了,非常成熟。

  • 采样频率:通常是 1 秒到 5 秒一次。别嫌慢,够用了。
  • 数据类型:电压、电流、有功功率、无功功率、开关状态、变压器档位等。
  • 传输方式:通过 RTU(远程终端单元)采集,经通信网络上传到主站。
  • 特点:数据量大但更新慢,适合做历史趋势分析和状态监控。
我的经验:SCADA 数据最大的坑是「死数据」——设备明明离线了,但系统还显示最后那个值。我曾经排查过一个「电压越限」告警,折腾了两小时,结果发现是通信中断,数据卡住了。所以,做可视化时一定要加「数据新鲜度」标识。

4.1.2 PMU 系统

PMU,相量测量单元。这玩意儿是近十几年才大规模部署的。它厉害在哪?

  • 采样频率:每秒 30 到 120 个数据点。注意,是每个周波采多个点。
  • 数据类型:电压相量、电流相量、频率、频率变化率。带角度信息。
  • 同步方式:通过 GPS 授时,所有 PMU 的时间戳是严格同步的。
  • 特点:精度高、速度快,能捕捉到暂态过程。比如电网振荡、低频振荡,SCADA 根本看不到,PMU 看得一清二楚。
注意:PMU 数据量非常大。一个 PMU 每秒产生 60 个数据帧,每个帧包含几十个量测值。如果你做全量存储,一天就能跑出几个 GB。我建议在采集端做「降采样」或「事件触发存储」,别一股脑全存。

4.2 模拟数据生成器设计

做开发时,你不可能天天连真实 SCADA 系统。一是没权限,二是真实数据波动太小,测不出边界情况。所以,我们需要一个模拟数据生成器。

我个人习惯用 Python 写一个轻量级的生成器。核心思路就三个字:像真的

4.2.1 基础波形生成

电力数据不是纯随机数。它有规律:

  • 电压、功率在一天内有「峰谷」变化
  • 频率基本稳定在 50Hz 附近,偶尔有小波动
  • 开关状态是离散的 0/1

我一般用正弦波叠加随机噪声来模拟负荷曲线。举个例子:

import numpy as np
import pandas as pd
from datetime import datetime, timedelta

def generate_load_curve(start_time, hours=24, interval_sec=1):
    """
    生成模拟负荷数据
    """
    n_points = int(hours * 3600 / interval_sec)
    t = np.linspace(0, hours, n_points)
    
    # 基础负荷:100MW 基线 + 50MW 日间波动
    base_load = 100 + 50 * np.sin(2 * np.pi * t / 24 - np.pi/2)
    
    # 叠加随机噪声(±5%)
    noise = np.random.normal(0, 5, n_points)
    
    # 加入尖峰脉冲(模拟大负荷启动)
    spike = np.zeros(n_points)
    spike_indices = np.random.choice(n_points, size=5, replace=False)
    spike[spike_indices] = np.random.uniform(20, 40, 5)
    
    load = base_load + noise + spike
    
    # 生成时间戳
    timestamps = [start_time + timedelta(seconds=i*interval_sec) 
                  for i in range(n_points)]
    
    return pd.DataFrame({
        'timestamp': timestamps,
        'load_mw': load
    })
关键点:模拟数据一定要包含「异常场景」。比如:电压骤降、频率越限、通信中断。否则你的可视化系统在真实环境中会措手不及。

4.2.2 多数据源模拟

真实系统里,SCADA 和 PMU 是同时存在的。我建议模拟器也同时输出两种数据:

数据源 模拟频率 字段示例 备注
SCADA 每 2 秒 1 条 timestamp, substation_id, voltage_kv, current_a, power_mw 带随机延迟 0-500ms
PMU 每秒 50 条 timestamp, pmu_id, voltage_mag, voltage_angle, freq_hz GPS 同步,时间戳精确到微秒
状态量 事件触发 timestamp, breaker_id, status (0/1) 模拟开关变位

嗯,这里要注意:SCADA 和 PMU 的时间戳精度不一样。SCADA 精确到秒就够了,PMU 必须精确到毫秒甚至微秒。你如果混在一起处理,一定要做时间对齐。

4.3 时间序列数据格式规范

数据格式这事,看着简单,但坑最多。我见过有人用 CSV 存 PMU 数据,结果时间戳精度丢了;也有人用 JSON 存 SCADA 数据,结果解析速度慢到崩溃。

我个人推荐两种格式:

4.3.1 面向存储的格式:Parquet

如果你要存大量历史数据,别用 CSV。用 Parquet。

  • 压缩率高:同样数据,Parquet 比 CSV 小 5-10 倍
  • 列式存储:查询某列时极快
  • 支持复杂类型:时间戳、嵌套结构都能原生支持

示例结构:

# Parquet 表结构(SCADA 数据)
- timestamp: datetime64[ns]   # 主键
- substation_id: string
- voltage_kv: float32
- current_a: float32
- power_mw: float32
- quality_flag: int8          # 0=正常, 1=可疑, 2=无效

4.3.2 面向传输的格式:JSON Lines

实时传输时,我习惯用 JSON Lines(每行一个 JSON 对象)。

  • 可读性好:调试时一眼能看出问题
  • 流式处理:可以逐行解析,不占内存
  • 兼容性好:几乎所有语言和工具都支持

示例:

{"timestamp": "2024-01-15T08:30:00.123456Z", "pmu_id": "PMU_001", "voltage_mag": 220.5, "voltage_angle": -12.3, "freq_hz": 50.01}
{"timestamp": "2024-01-15T08:30:00.143456Z", "pmu_id": "PMU_001", "voltage_mag": 220.4, "voltage_angle": -12.4, "freq_hz": 50.00}
{"timestamp": "2024-01-15T08:30:00.163456Z", "pmu_id": "PMU_001", "voltage_mag": 220.6, "voltage_angle": -12.2, "freq_hz": 50.02}
避坑指南:我曾经遇到过一个问题——PMU 的时间戳用了本地时间,没有带时区信息。结果数据传到不同时区的服务器上,时间全乱了。记住:所有时间戳统一用 UTC,带时区标识。这是铁律。

4.3.3 质量标签与数据治理

真实数据不总是干净的。我建议每条数据都带一个质量标签:

质量码 含义 处理建议
0 正常 直接使用
1 可疑(超出正常范围但未越限) 标记后使用,或插值替换
2 无效(通信中断、设备故障) 丢弃,不参与计算
3 人工置数(运维人员手动输入) 特殊标记,谨慎使用

嗯,这块我多说一句。很多可视化系统只画「好看」的数据,把质量码忽略了。但真实场景里,数据质量直接影响决策。你想想看,调度员看到一条电压越限告警,结果是因为数据无效导致的误报——这责任谁担得起?

小结

这一章我们聊了三件事:

  1. 真实数据源:SCADA 看趋势,PMU 看细节,两者互补
  2. 模拟生成器:别只生成理想数据,要包含异常场景
  3. 数据格式:存储用 Parquet,传输用 JSON Lines,时间戳必须带时区

下一章,我们会把这些数据真正「喂」给可视化系统。到时候你会看到,数据从采集到展示,中间要经过多少道工序。嗯,做好准备,那才是真正考验架构能力的地方。