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

下图展示了从类型定义到现场实例,再到地址空间与命名空间归属的完整关系。

OPC UA 信息模型:类型、实例、地址空间与命名空间 类型定义 TypeDefinition MotorType Speed / Current / Start() 实例声明 InstanceDeclaration Speed : AnalogItemType Start() : Method 现场实例 Instance Motor01 (Object) Speed = 1480 rpm 声明 实例化 HasTypeDefinition 地址空间 AddressSpace(全局有向图) ns=0 标准命名空间 Objects / Types / Views http://opcfoundation.org/UA/ ns=1 项目命名空间 Motor01 / LineA urn:MyCompany:DigitalTwin ns=2 厂商扩展 VendorX 诊断节点 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,职责分离
建模时先问三个问题:这个对象属于哪个类型?它应该声明哪些成员?它属于哪个命名空间?回答清楚,地址空间自然清晰。
不要为了省事把所有节点都塞进 ns=0。标准命名空间只应承载 OPC UA 规范定义的内容,项目自定义模型必须使用独立命名空间 URI,否则升级和互操作都会出问题。

掌握节点、引用、类型定义、实例声明、地址空间与命名空间这六个概念,就掌握了 OPC UA 信息模型的“语法”。后续的数字孪生建模、 companion specification 扩展、跨服务器信息模型映射,都建立在这套语法之上。