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 第二步:定义类型层次
根据梳理的结果,定义对象类型和变量类型。我建议遵循这个原则:
- 先找共性:所有设备都有「设备名称」、「运行状态」,那就定义一个基础设备类型。
- 再分特殊:封口机有温度,传送带有速度,那就从基础类型继承,扩展各自的特性。
- 最后定关系:产线包含封口机,封口机包含温度传感器。用
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 的灵魂,搞懂了它,后面的事件、报警、历史数据,都会变得顺理成章。下一章,咱们聊聊如何用这些模型来定义报警条件。