3. 信息模型基础

OPC UA 的信息模型不是“把变量堆进地址空间”,而是用一套统一的建模原语,把现实设备、系统与业务语义表达成可浏览、可查询、可扩展的图结构。理解这套原语,是设计地址空间、定义类型、规划实例化路径的前提。本节围绕节点、引用、属性、类型定义、实例化、继承与组合七个核心概念展开,并用一张关系图把它们串起来。

核心结论:OPC UA 地址空间是一张“有向图”。节点是图的顶点,引用是图的边,属性是节点自带的元数据,类型定义规定“能长成什么样”,实例化是“按类型生成具体对象”,继承与组合决定“类型之间如何复用与嵌套”。
OPC UA 信息模型核心原语关系图 节点 · 引用 · 属性 · 类型定义 · 实例化 · 继承 · 组合 类型定义层(Type) BaseObjectType 根对象类型 MotorType 自定义设备类型 继承 HasSubtype 实例层(Instance) Motor_01 对象实例 Speed / Temperature 变量实例 组合 HasComponent 实例化(Instantiation) 类型定义 → 具体对象 节点(Node)与属性(Attribute) NodeId 唯一标识 ns=2;i=1001 BrowseName 浏览名 2:Motor_01 DisplayName 显示名称 1号电机 Value / DataType 值 / 数据类型 Double / 1450.0 引用(Reference) HasComponent:组合关系,整体-部分 HasProperty:属性关系,节点-属性 HasSubtype:继承关系,父类型-子类型 Organizes / HasTypeDefinition / HasEventSource … 引用

一、节点:地址空间的最小建模单元

节点是 OPC UA 地址空间中的基本对象,任何可被浏览、读取、订阅或调用的东西,最终都表现为一个节点。节点由 NodeId 唯一标识,并携带一组标准属性。最常见的节点类别包括:对象(Object)、变量(Variable)、方法(Method)、对象类型(ObjectType)、变量类型(VariableType)、引用类型(ReferenceType)、数据类型(DataType)和视图(View)。

节点本身不直接“存值”,值由变量节点承载;对象节点用于组织语义;方法节点表示可调用行为;类型节点用于定义模板。设计地址空间时,先判断“这个建模元素应该是什么类别”,再决定它的 NodeId 命名空间与浏览名,这是后续所有工作的基础。

二、属性:节点自带的元数据

每个节点都有一组标准属性,用于描述自身。最常用的包括 NodeId、BrowseName、DisplayName、Description、NodeClass、DataType、ValueRank、AccessLevel、Historizing 等。属性不是“随便挂的字段”,而是由节点类别决定的固定集合。例如变量节点才有 Value、DataType、AccessLevel;对象节点没有 Value 属性。

属性与“属性引用”容易混淆:属性是节点自身的元数据,而 HasProperty 引用用于把某个变量节点声明为另一个节点的属性。前者是节点内部结构,后者是节点之间的边。

三、引用:把节点连成有向图

引用是节点之间的有向连接,决定地址空间的拓扑结构。引用有类型、源节点和目标节点,并且是双向可浏览的:正向引用与反向引用成对出现。常见引用类型如下表所示。

引用类型 语义 典型用法
HasComponent 组合关系,整体包含部分 设备对象包含变量、方法
HasProperty 节点拥有属性 对象拥有工程单位、量程
HasSubtype 类型继承关系 MotorType 继承 BaseObjectType
HasTypeDefinition 实例指向其类型 Motor_01 → MotorType
Organizes 组织关系,弱包含 文件夹组织设备列表

四、类型定义、实例化、继承与组合

类型定义(TypeDefinition)规定了一类节点“应该长什么样”:包含哪些子节点、哪些属性、哪些方法,以及默认的数据类型和访问权限。实例化就是按照类型定义,在地址空间中生成具体节点,并通过 HasTypeDefinition 引用回指类型。实例化不是复制粘贴,而是“类型约束下的具体化”:实例可以覆盖部分默认值,但必须满足类型规定的结构。

继承用于类型之间的复用与扩展。子类型自动拥有父类型的成员,并可增加新成员或细化约束。例如 MotorType 继承 BaseObjectType 后,再定义 Speed、Temperature、Start() 等成员;ServoMotorType 继承 MotorType 后,可增加 Position、Homing() 等成员。继承是“is-a”关系,组合是“has-a”关系。HasComponent 表达组合,HasSubtype 表达继承,两者在地址空间中形成不同的边,不能混用。

判断继承还是组合,可以问一句:ServoMotor 是一种 Motor,还是包含一个 Motor?前者用 HasSubtype,后者用 HasComponent。类型设计阶段把这条边界划清,实例化阶段就不会出现“类型层级混乱、实例结构重复”的问题。

五、案例:从类型到实例的建模路径

假设要为一条产线建模三台电机。推荐路径是:先定义 MotorType,声明 Speed、Temperature、Running 三个变量成员和 Start()、Stop() 两个方法成员;再定义 Motor_01、Motor_02、Motor_03 三个对象实例,通过 HasTypeDefinition 指向 MotorType;最后用 Organizes 或 HasComponent 把它们挂到产线对象下。这样,客户端只需识别 MotorType,就能批量解析所有电机实例,而不必逐台硬编码。

MotorType (ObjectType)
├─ HasProperty: Manufacturer
├─ HasComponent: Speed (Variable, Double)
├─ HasComponent: Temperature (Variable, Double)
├─ HasComponent: Running (Variable, Boolean)
├─ HasComponent: Start (Method)
└─ HasComponent: Stop (Method)

Motor_01 (Object) --HasTypeDefinition--> MotorType
├─ Speed = 1450.0
├─ Temperature = 62.5
└─ Running = true

六、常见误区与复盘

  • 把属性当子节点:属性是节点自带的元数据,不应通过 HasComponent 再挂一个“属性对象”。需要额外语义时,用 HasProperty 挂变量节点。
  • 继承与组合混用:用 HasComponent 表达“是一种”会导致类型系统失效;用 HasSubtype 表达“包含一个”会导致实例结构无法展开。
  • 实例化后随意改结构:实例可以改值,但不应随意增删类型规定的成员,否则客户端按类型解析时会失败。
  • NodeId 命名空间规划缺失:自定义类型和实例应使用独立命名空间,避免与标准节点冲突,也便于后续版本演进。
  • 忽略反向引用:引用是双向的,设计时只考虑正向浏览,可能导致客户端无法从子节点回溯父节点。

复盘时可以按“节点类别 → 属性集合 → 引用类型 → 类型定义 → 实例化路径 → 继承/组合边界”六步检查。只要这六步在建模初期对齐,地址空间就会保持结构清晰、语义一致、可扩展,并为后续的信息模型映射与服务器实现打下稳定基础。