2. 能源数据采集架构设计

能源管理系统的数据质量,从第一米线缆、第一次寄存器读取就已经决定了。很多项目后期出现“能耗曲线跳变”“峰谷电量对不上”“水表读数回退”等问题,根源往往不在上层算法,而在采集架构与协议选型阶段。本节围绕电表、水表、气表到PLC的完整采集链路,讲清分层结构、协议取舍,以及源码中驱动层如何落地。

一、采集链路的四层结构

一个可维护的能源采集架构,通常分为四层:现场设备层(电表、水表、气表、PLC)、采集接入层(网关/采集器/边缘程序)、协议驱动层(Modbus、IEC 104、OPC UA 等驱动实例)、数据服务层(点位映射、质量码、时序入库)。现场设备层负责产生原始计量数据;采集接入层负责物理连接与轮询调度;协议驱动层负责把字节流翻译成带工程单位的点位值;数据服务层负责统一时标、质量码与存储。

关键设计原则是:协议驱动层与业务逻辑解耦。驱动只负责“读到一个值”,不负责判断这个值是否合理、属于哪个支路、要不要报警。这样更换协议或新增表计时,只需新增驱动实例和点位映射,不必改动上层业务。

现场设备层 采集接入层 协议驱动层 数据服务层 三相电表 水表(脉冲/RS485) 气表(RS485) PLC(S7-1200) 边缘采集网关 / 采集程序 串口轮询 · TCP 长连接 · 采集周期调度 · 断线重连 · 本地缓存 Modbus 驱动 RTU / TCP 功能码 03/04/01 IEC 104 驱动 主站/从站 遥测/遥信/总召 OPC UA 驱动 Client / Server 订阅 / 浏览 / 读写 点位映射 · 质量码 · 时标对齐 · 时序入库 统一 Tag 命名 · 工程单位换算 · 死区过滤 · 断点续传 采集周期 1~5s 超时重试 3 次 质量码 Good/Bad 时标 UTC+8 死区 0.5%

二、协议选型:Modbus、IEC 104、OPC UA 的适用边界

三种协议不是替代关系,而是各有适用场景。选型时先看设备侧支持什么,再看数据实时性、点位数和网络条件。

对比项 Modbus RTU/TCP IEC 60870-5-104 OPC UA
典型设备 电表、水表、气表、小型PLC 电力保护装置、RTU、变电站 中大型PLC、DCS、SCADA服务器
数据模型 寄存器地址,无自描述 信息对象地址(IOA)+ 类型标识 地址空间 + 节点ID,自描述
实时性 轮询,秒级 变化上送,百毫秒级 订阅发布,百毫秒级
点位数 适合数百点以内 适合数千点 适合数万点
配置复杂度 低,需手工映射 中,需点表与总召 高,需建模与安全配置

实际项目中常见的组合是:电表、水表、气表走 Modbus RTU 经串口服务器汇聚;电力回路保护装置走 IEC 104;主控 PLC 走 OPC UA 或 S7 协议。采集程序内部为每种协议实现独立驱动,统一向上输出 TagValue 结构。

三、源码中的驱动实现

驱动层的核心接口应保持极简。以下是一个可扩展的驱动抽象(以 Python 风格示意,实际项目可用 C#/Java 实现):

class ProtocolDriver:
    def connect(self) -> bool: ...
    def read(self, points: list[Point]) -> list[TagValue]: ...
    def disconnect(self) -> None: ...

class TagValue:
    tag: str          # 统一点位名,如 "METER_A_EP"
    value: float      # 工程值
    quality: str      # "Good" / "Bad" / "Uncertain"
    timestamp: float  # UTC 秒
    unit: str         # "kWh" / "m3" / "kPa"

Modbus 驱动的关键实现细节:读取保持寄存器使用功能码 03,读取输入寄存器使用 04。对于 32 位电量,需明确字序。例如某电表有功电能寄存器 0x0000-0x0001,高字在前,则:

raw = client.read_holding_registers(0x0000, 2)
value = (raw[0] << 16) | raw[1]   # 高字在前
kwh = value * 0.01                # 变比 0.01 kWh/字

若字序相反,则交换 raw[0] 与 raw[1]。这个细节必须在点表中标注,否则会出现“电量突然跳变 65536 倍”的典型故障。

IEC 104 驱动需要处理总召(C_IC_NA_1)与变化上送。启动后先发总召,等待激活确认与激活结束,再进入监听。遥测值通常为归一化值或标度化值,需按点表系数还原:

# 标度化值 SVA,16位有符号,系数 0.01
raw_sva = frame.value
engineering = raw_sva * 0.01

OPC UA 驱动推荐使用订阅模式而非轮询。创建 Subscription,设置发布间隔 1000ms,对每个需要采集的 NodeId 建立 MonitoredItem,并设置死区过滤,避免微小波动频繁上报:

sub = client.create_subscription(1000, handler)
for node_id in node_list:
    sub.subscribe_data_change(node_id, deadband=0.5)

四、点位映射与质量码

无论哪种协议,最终都要落到统一点位表。点位表至少包含:Tag 名、协议类型、连接参数、设备地址/IOA/NodeId、寄存器/对象地址、数据类型、字序、变比、工程单位、采集周期、死区。质量码用于标记数据可信度:通信成功且值在合理范围为 Good;超时或校验失败为 Bad;值超出量程但通信正常为 Uncertain。上层能耗计算只使用 Good 数据,Bad 数据进入补采队列。

调试建议:先用 Modbus Poll、IEC 104 主站测试工具或 UaExpert 单独验证每个设备可读,再接入采集程序。这样能把“协议不通”和“程序映射错误”两类问题分开定位。
常见坑:串口服务器多主机轮询时,若采集周期小于设备响应时间,会出现大量超时。建议单条 RS485 总线挂表不超过 16 台,采集周期不低于 2 秒,并在驱动中实现失败重试与总线静默间隔。

采集架构设计完成后,下一步才是数据入库与能耗计算。但请记住:驱动层的稳定性、点位映射的准确性、质量码的完整性,决定了整个能源管理系统能走多远。在源码实现中,把驱动做成可插拔组件,把点位表做成配置驱动,是后续扩展和排障成本最低的做法。