4. OPC UA信息模型核心
OPC UA 之所以能成为工业互操作的事实标准,关键在于它没有把数据简单当作“变量表”,而是把整个现场建模成一张可浏览、可查询、可扩展的语义网络。理解这张网络,必须抓住六个关键词:节点、引用、类型定义、实例声明、地址空间、命名空间。它们共同构成了 OPC UA 信息模型的骨架。
一、节点:地址空间中的最小语义单元
在 OPC UA 中,一切对象都是节点(Node)。节点不是“值”,而是“带身份和属性的实体”。每个节点由 NodeId 唯一标识,并拥有 NodeClass、BrowseName、DisplayName、Description 等公共属性。常见的 NodeClass 包括:
- Object:表示现实世界中的设备、产线、工位等对象。
- Variable:表示可读写的数据点,如温度、转速、状态字。
- Method:表示可被客户端调用的操作,如启动、复位、标定。
- ObjectType / VariableType:类型定义节点,用于描述实例应具备的结构。
- ReferenceType:引用类型节点,定义节点之间关系的语义。
- DataType:数据类型节点,约束变量可承载的数据种类。
- View:视图节点,用于按特定用途组织地址空间子集。
节点本身不表达“它和谁有关系”,关系由引用承担。因此,节点是“名词”,引用是“动词”。
二、引用:把节点连成语义网络
引用(Reference)是节点之间的有向连接,它让地址空间从一堆孤立节点变成一张图。引用有方向、有类型,常见引用类型包括:
Organizes:用于文件夹式组织,如Objects文件夹下挂设备对象。HasComponent:表示整体与组成部分,如电机对象拥有温度变量。HasProperty:表示对象拥有的属性,如序列号、量程。HasTypeDefinition:实例指向其类型定义,是类型系统的关键引用。HasSubtype:类型之间的继承关系,如AnalogItemType继承自DataItemType。
浏览地址空间时,客户端正是沿着这些引用逐层展开,从而发现设备结构。引用类型本身也是节点,因此可以自定义引用语义,例如 HasCalibrationRecord。
三、类型定义与实例声明:从模板到现场
类型定义(TypeDefinition)是 OPC UA 面向对象建模的核心。类型节点描述“这类对象应该长什么样”,包括应包含哪些组件、属性、变量和方法。实例通过 HasTypeDefinition 引用指向类型,从而获得类型语义。
实例声明(InstanceDeclaration)则出现在类型内部,用于声明该类型的实例将拥有哪些成员。例如,一个 MotorType 可以声明 Speed、Current、Start()。当现场创建一台具体电机实例时,这些声明会被实例化为真实节点,并通过 HasComponent 等引用挂接到实例上。
四、地址空间与命名空间:全局图与命名隔离
地址空间(AddressSpace)是服务器中所有节点与引用的集合,是一张全局有向图。客户端通过 Browse、Read、Write、Call 等服务在这张图上操作。地址空间没有固定物理边界,但通常以 Objects、Types、Views 等标准文件夹为入口。
命名空间(Namespace)解决的是“名字冲突”和“来源归属”。每个节点属于一个命名空间,NodeId 中的 ns 索引即命名空间索引。索引 0 是 OPC UA 标准命名空间,索引 1 通常是服务器厂商或项目自定义命名空间。命名空间 URI 是稳定标识,索引只是运行时映射。
NodeId 示例:
ns=0;i=85 // 标准命名空间中的 Objects 文件夹
ns=1;s=Motor01 // 自定义命名空间中的电机实例
ns=2;i=5001 // 另一厂商扩展命名空间中的节点
命名空间表:
Index 0 -> http://opcfoundation.org/UA/
Index 1 -> urn:MyCompany:DigitalTwin:LineA
Index 2 -> urn:VendorX:Extension
下图展示了从类型定义到现场实例,再到地址空间与命名空间归属的完整关系。
五、案例:一条产线的电机建模
假设要为 LineA 产线建模。首先在 ns=1 中定义 MotorType,声明 Speed、Current、Start()。然后在 Objects/LineA 下创建 Motor01 实例,通过 HasTypeDefinition 指向 MotorType,通过 HasComponent 挂接 Speed 变量,通过 HasProperty 挂接 SerialNumber。客户端浏览时,从 Objects 出发,沿 Organizes 找到 LineA,再沿 HasComponent 找到 Motor01,最后读取 Speed 的当前值。
Objects
└─ Organizes → LineA
└─ HasComponent → Motor01
├─ HasTypeDefinition → MotorType
├─ HasComponent → Speed (AnalogItemType)
├─ HasComponent → Current (AnalogItemType)
└─ HasProperty → SerialNumber
六、常见误区与复盘
| 误区 | 问题 | 正确做法 |
|---|---|---|
| 把 NodeId 当唯一业务主键 | NodeId 可随命名空间索引变化,跨服务器不稳定 | 用 BrowseName + 命名空间 URI 或自定义业务标识 |
| 所有变量都挂在 Objects 下 | 地址空间扁平,无法表达设备层级 | 用 Object + HasComponent 建立层次结构 |
| 忽略 HasTypeDefinition | 实例失去类型语义,客户端无法识别设备类别 | 每个实例必须指向明确的类型定义 |
| 命名空间索引硬编码 | 服务器重启或配置变化后索引漂移 | 运行时读取 NamespaceArray,按 URI 解析索引 |
| 类型与实例混在一起 | 模型复用性差,扩展困难 | 类型放 Types,实例放 Objects,职责分离 |
掌握节点、引用、类型定义、实例声明、地址空间与命名空间这六个概念,就掌握了 OPC UA 信息模型的“语法”。后续的数字孪生建模、 companion specification 扩展、跨服务器信息模型映射,都建立在这套语法之上。