4. OPC UA信息建模实战

OPC UA 信息建模的价值,不在于把变量名从 Tag001 改成中文,而在于把储能站里真实存在的设备、部件、测点和控制关系,映射成一套有类型、有层级、有语义的地址空间。这样上位机、EMS、云平台和第三方系统读取的不再是孤立数据点,而是“某电池簇第 3 号电池包的温度”“1 号 PCS 的直流侧功率”这类可理解、可复用、可扩展的对象模型。

本节以一座典型 10MW/20MWh 储能站为例,建立三层对象模型:站级 EnergyStorageStationType、设备级 BatteryClusterType、PCSType、BMSType,以及部件级 BatteryPackType、BatteryModuleType。示例参数均为教学假设,用于说明建模方法,不代表任何实际工程实测值。

一、先定对象类型,再谈实例化

对象类型是模板,实例是模板在具体设备上的落地。以电池簇为例,一个 1MW/2MWh 电池簇通常包含 8 个电池包,每个电池包由若干电池模组串联。建模时先定义 BatteryClusterType,其核心成员包括:

  • ClusterVoltage:簇总电压,Double,单位 V;
  • ClusterCurrent:簇电流,Double,单位 A,充电为正、放电为负;
  • SOC:荷电状态,Double,单位 %;
  • SOH:健康状态,Double,单位 %;
  • CellVoltageMax、CellVoltageMin:单体最高/最低电压,Double,单位 V;
  • TemperatureMax、TemperatureMin:最高/最低温度,Double,单位 ℃;
  • PackArray:类型为 BatteryPackType[] 的组件数组,承载 8 个电池包实例。

PCS 对象类型 PCSType 则围绕功率变换定义:ActivePower、ReactivePower、DCPower、ACVoltage、ACCurrent、Frequency、PowerFactor、RunState、FaultCode。BMS 对象类型 BMSType 更偏管理与保护:ChargeCurrentLimit、DischargeCurrentLimit、InsulationResistance、BalanceState、AlarmLevel。三者通过站级对象下的组件关系组合,形成完整储能单元。

储能站 OPC UA 对象类型与实例化关系 EnergyStorageStationType 站级实例:ESS_Station_01 BatteryClusterType 实例:Cluster_01 SOC / 电压 / 电流 / 温度 PCSType 实例:PCS_01 有功 / 无功 / 状态 / 故障 BMSType 实例:BMS_01 限流 / 绝缘 / 告警 BatteryPackType[8] Pack_01 … Pack_08 BatteryModuleType[n] 单体电压 / 温度测点 实例化关键规则 1. 类型定义 BrowseName 与 NodeId,实例继承并覆盖 DisplayName。 2. 组件关系用 HasComponent,属性用 HasProperty,数组用 HasOrderedComponent。 3. 实例 NodeId 建议采用 ns=2;s=ESS01.Cluster01.Pack03.TempMax。 4. 工程单位、量程、死区写入 EURange 与 AnalogItem 属性。 5. 类型变更后,所有实例通过引用自动获得新成员,无需逐点重建。 6. 客户端浏览时按 Folder → Device → Component 层级展开。

二、从类型到实例:一次完整的建模操作

假设使用 UaModeler 或 open62541 的 XML 建模工具,操作顺序如下。第一步,在 ns=1 下创建 BatteryClusterType,基类型选 BaseObjectType,添加 SOC 属性,DataType 为 Double,ValueRank 为 Scalar,AccessLevel 为 CurrentRead。第二步,添加 PackArray 组件,TypeDefinition 指向 BatteryPackType,ModellingRule 设为 Mandatory,并设置 ArrayDimensions 为 8。第三步,在 ns=2 下创建实例 Cluster_01,TypeDefinition 指向 BatteryClusterType,此时地址空间自动生成 8 个电池包占位节点。

第四步,为每个电池包实例写入 PackIndex、ModuleCount 等属性,并把实际测点绑定到 CellVoltageMax 等变量。第五步,在站级对象下用 HasComponent 把 Cluster_01、PCS_01、BMS_01 挂到 ESS_Station_01 下。完成后,客户端浏览地址空间应看到如下结构:

ESS_Station_01
├─ Cluster_01 (BatteryClusterType)
│   ├─ SOC = 76.4 %
│   ├─ ClusterVoltage = 1332.5 V
│   ├─ PackArray
│   │   ├─ Pack_01 (BatteryPackType)
│   │   │   ├─ CellVoltageMax = 3.412 V
│   │   │   └─ TemperatureMax = 31.8 ℃
│   │   └─ Pack_08 ...
├─ PCS_01 (PCSType)
│   ├─ ActivePower = 512.0 kW
│   ├─ RunState = 2 (并网运行)
│   └─ FaultCode = 0
└─ BMS_01 (BMSType)
    ├─ ChargeCurrentLimit = 320 A
    └─ InsulationResistance = 2.4 MΩ

三、建模中容易踩的三个坑

第一个坑是把所有测点平铺在站级对象下。这样虽然能读,但失去了设备归属,后续增加第二簇、第三簇时无法复用。正确做法是让每个设备类型自带成员,实例只负责绑定数据源。第二个坑是 NodeId 用随机 GUID。GUID 稳定但不可读,排障时很难定位。建议采用 ns=2;s=ESS01.Cluster01.Pack03.TempMax 这类语义化字符串,既唯一又可读。第三个坑是忽略工程单位。OPC UA 的 EUInformation 属性应写入单位与量程,例如温度写 ℃、量程 -40~85,否则客户端只能靠猜。

建模完成后,用 UaExpert 连接服务器,展开 Objects 节点,逐级检查实例是否出现在正确层级、变量是否可读、单位是否正确。若某个电池包节点缺失,优先检查 ModellingRule 是否设为 Mandatory,以及数组维度是否与实例数量一致。

四、实例化后的数据绑定与验证

类型和实例建好后,真正让模型“活”起来的是数据绑定。以 Cluster_01.SOC 为例,服务器端需要把该节点的值来源指向 BMS 的实时数据区。常见做法是在服务器启动时注册一个回调,周期 500ms 从 Modbus TCP 或 CAN 采集层读取 SOC,写入 Cluster_01.SOC 的 Value 属性,同时更新 SourceTimestamp。客户端订阅该节点后,应能看到数值随电池状态变化,且时间戳与采集周期一致。

验证时重点观察三件事:一是数值是否在合理量程内,例如 SOC 应在 0~100 之间;二是 StatusCode 是否为 Good,若为 BadWaitingForInitialData 说明绑定尚未生效;三是层级浏览是否完整,从站级到电池包再到模组,每一级都能通过引用找到。若这三项都通过,说明对象类型与实例化模型已经可用,后续接入 EMS、云端或第三方平台时,只需按类型语义读取,而不必再关心底层寄存器地址。

最后需要强调,信息建模不是一次性工作。当储能站扩容、增加液冷机组或消防主机时,应优先扩展类型定义,再批量实例化,而不是在站级对象下继续堆变量。保持“类型稳定、实例可增”的原则,地址空间才能长期可维护。