3、信息模型基础:类型定义、对象类型与变量类型、自定义信息模型的构建思路

好,咱们进入第三章。这一章是打地基的活儿。

说实话,OPC UA 最让我佩服的地方,就是它的信息模型。它不是简单地把数据扔给你,而是告诉你:这个数据是什么、从哪里来、跟谁有关系、能干什么。你想想看,这跟咱们做自动化项目时,画电气原理图、做功能块库,是不是一个道理?

3.1 为什么需要信息模型?

我刚开始接触 OPC UA 时,也犯过嘀咕:不就是读写变量吗?搞那么复杂干嘛?

后来在一个汽车焊装项目里,我吃了大亏。现场有 200 多个机器人,每个机器人有几十个温度传感器。我用传统方式,把所有温度值都塞进一个扁平列表里。结果呢?调试时根本分不清哪个温度是哪个关节的。维护人员看着一堆 Tag 名,头都大了。

嗯,从那以后,我彻底理解了信息模型的价值。

说白了,信息模型就是给数据穿上衣服,让它有身份、有结构、有行为。OPC UA 通过对象类型变量类型引用类型这些基础构件,让你能像搭积木一样,构建出跟真实物理世界一一对应的数字模型。

核心思想: 信息模型 = 类型定义 + 实例化 + 关系描述。类型是模板,实例是具体设备,关系是连线。

3.2 类型定义:一切从模板开始

在 OPC UA 里,万物皆节点。但节点不是乱长的,它得有个模板。这个模板就是类型定义

我个人习惯把类型定义理解成「模具」。你做一个注塑件,先得开个模具。模具定了,后面生产出来的零件都一样。OPC UA 里的类型定义就是这个模具。

3.2.1 对象类型(ObjectType)

对象类型描述了一类对象的共同特征。比如「电机」这个对象类型,它定义了:

  • 有哪些属性(额定功率、转速)
  • 有哪些变量(当前电流、温度)
  • 有哪些方法(启动、停止)
  • 能触发哪些事件(过载报警)

我在一个水处理项目中,就自定义了一个「水泵」对象类型。它继承了标准对象类型的一些特性,又加上了我们现场特有的「干转保护」变量和「密封泄漏」报警。这样后面每个水泵实例,都自动拥有了这些能力。

小技巧: 定义对象类型时,尽量复用 OPC UA 规范里已有的类型。比如 BaseObjectType 是所有对象类型的根。别自己从头造轮子,除非你有特殊需求。

3.2.2 变量类型(VariableType)

变量类型定义了变量的数据结构。比如「温度传感器」这个变量类型,它规定了:

  • 数据类型是 Double
  • 值范围是 -40 到 150 摄氏度
  • 工程单位是 °C
  • 精度是 0.1

你想想看,如果没有变量类型,每个温度变量都得单独定义这些属性,那得多累?有了类型,你只需要实例化时指定一下就行。

我记得有一次做半导体设备项目,客户要求所有模拟量变量都必须有「量程上限」和「量程下限」属性。我就在 AnalogItemType 的基础上,扩展了一个自定义变量类型,加上了这两个属性。后面几百个变量,一键搞定。

3.3 自定义信息模型的构建思路

好,理论说完了,咱们聊聊实战。怎么构建一个自定义信息模型?我一般分四步走。

3.3.1 第一步:梳理现实对象

别急着写代码。先拿张纸,把现场的设备、系统、关系画出来。比如一个包装产线:

  • 有 3 台封口机
  • 每台封口机有温度、速度、压力
  • 封口机之间通过传送带连接
  • 整个产线有一个总控系统

这一步,说白了就是做面向对象分析。把物理世界映射到数字世界。

3.3.2 第二步:定义类型层次

根据梳理的结果,定义对象类型和变量类型。我建议遵循这个原则:

  1. 先找共性:所有设备都有「设备名称」、「运行状态」,那就定义一个基础设备类型。
  2. 再分特殊:封口机有温度,传送带有速度,那就从基础类型继承,扩展各自的特性。
  3. 最后定关系:产线包含封口机,封口机包含温度传感器。用 HasComponent 引用表达包含关系。

注意: 类型层次不要太深。我见过有人搞了七八层继承,最后自己都搞不清哪个属性在哪一层。一般 3-4 层就够了。深了反而难维护。

3.3.3 第三步:用 Nodeset 描述

类型定义好了,怎么告诉 OPC UA 服务器?用 Nodeset 文件。这是一个 XML 格式的文件,描述了所有节点和它们的关系。

我一般用 UA Modeler 工具来生成 Nodeset,可视化操作,拖拖拽拽就行。当然,你也可以手写 XML,但说实话,那太折磨人了。

下面是一个简化的 Nodeset 片段,定义了一个「封口机」对象类型:

<UANode NodeId="ns=1;i=1001" NodeClass="ObjectType" BrowseName="1:SealerType">
  <DisplayName>封口机类型</DisplayName>
  <References>
    <Reference ReferenceType="HasSubtype" IsForward="false">
      <NodeId>i=58</NodeId>  <!-- 继承自 BaseObjectType -->
    </Reference>
  </References>
</UANode>

<UANode NodeId="ns=1;i=2001" NodeClass="Variable" BrowseName="1:Temperature">
  <DisplayName>温度</DisplayName>
  <References>
    <Reference ReferenceType="HasComponent" IsForward="false">
      <NodeId>ns=1;i=1001</NodeId>
    </Reference>
    <Reference ReferenceType="HasTypeDefinition">
      <NodeId>i=2368</NodeId>  <!-- AnalogItemType -->
    </Reference>
  </References>
  <Value>
    <uax:Double>25.0</uax:Double>
  </Value>
</UANode>

你看,SealerType 继承自 BaseObjectType,然后通过 HasComponent 引用了 Temperature 变量。这个变量的类型是 AnalogItemType。结构清晰,一目了然。

3.3.4 第四步:实例化与部署

Nodeset 文件写好后,导入到 OPC UA 服务器中。服务器会根据类型定义,自动创建实例。比如你定义了「封口机类型」,然后创建 3 个实例:Sealer_01、Sealer_02、Sealer_03。每个实例都自动拥有 Temperature、Speed、Pressure 这些变量。

我曾经在一个项目中,用这种方式管理了 500 多台设备。每台设备上线时,只需要告诉服务器「我是某某类型的第几号」,所有变量和方法就自动生成了。维护成本大大降低。

总结一下: 自定义信息模型的核心思路就是「先抽象、再定义、后实例」。抽象出现实世界的共性,用类型定义固化下来,最后批量实例化。这套思路,跟面向对象编程如出一辙。

3.4 避坑指南

最后,分享几个我踩过的坑:

  • 别把类型当实例用:类型是模板,不是具体设备。不要在类型定义里写死具体数值。
  • 注意命名空间:自定义的类型和变量,一定要用你自己的命名空间(比如 ns=1),别跟 OPC UA 标准命名空间混在一起。
  • 引用关系别搞反HasComponent 是从父到子,HasProperty 是从对象到属性。方向错了,客户端解析时会出问题。
  • 测试要彻底:Nodeset 文件导入后,用 UA Expert 之类的客户端工具,把整个地址空间遍历一遍。看看类型继承对不对,变量值能不能读写。

嗯,这一章的内容就到这里。信息模型是 OPC UA 的灵魂,搞懂了它,后面的事件、报警、历史数据,都会变得顺理成章。下一章,咱们聊聊如何用这些模型来定义报警条件。