3. 信息模型基础:类型系统、对象类型、变量类型、数据类型、自定义类型设计

好,咱们今天聊聊信息模型。说实话,这是OPC UA里最核心、也最容易被忽视的一块。很多人一上来就急着写代码、建连接,结果后面发现模型设计得一塌糊涂,改起来比重新写还痛苦。我当年就吃过这个亏。

3.1 类型系统——OPC UA的骨架

OPC UA的类型系统,说白了就是一套“造物规则”。它定义了你能造出什么样的对象、变量、方法。你想想看,如果每个设备厂商都按自己的路子来,那上层应用怎么统一访问?

类型系统分两大类:

  • 元类型(Meta Type):用来定义类型的类型。比如ObjectType、VariableType、DataType。它们自己也是类型。
  • 实例类型(Instance Type):实际存在的对象、变量、方法。比如一个具体的“温度传感器”对象。

我个人习惯把元类型看作“模具”,实例类型看作“产品”。模具定了,产品才能批量生产。

3.2 对象类型(ObjectType)

对象类型是最常用的。它描述了一类对象的共同特征:包含哪些变量、方法、事件。

举个例子,我要定义一个“电机”对象类型:

// 伪代码,定义MotorType
ObjectType MotorType : BaseObjectType
{
    // 属性
    Variable Speed : Double;        // 转速
    Variable Temperature : Double;  // 温度
    Variable Status : Boolean;      // 运行状态

    // 方法
    Method Start();                 // 启动
    Method Stop();                  // 停止
    Method Reset();                 // 复位
}

嗯,这里要注意:对象类型可以继承。比如我可以定义一个“变频电机”类型,继承自“电机”,再加一个“频率”变量。我在项目中遇到过有人把所有电机都定义成同一个类型,结果变频电机和普通电机混在一起,逻辑判断写得跟蜘蛛网似的。

我的建议:对象类型的继承层次不要太深,3到4层就够了。太深了维护起来很痛苦,你改个基类,下面一堆子类跟着崩。

3.3 变量类型(VariableType)

变量类型定义了变量的“长相”:数据类型、值范围、单位、工程意义。它和对象类型最大的区别是:变量类型没有子节点,它就是个“值容器”。

举个例子,定义一个“温度”变量类型:

// 定义TemperatureVariableType
VariableType TemperatureVariableType : BaseVariableType
{
    DataType : Double;              // 数据类型
    EngineeringUnit : "°C";         // 工程单位
    ValueRange : [-40, 150];        // 值范围
    EURange : [-40, 150];           // 工程单位范围
    InstrumentRange : [-50, 200];   // 仪表量程
}

为什么要定义变量类型?直接用一个Double不行吗?行,但你会丢失语义。你想想看,一个Double变量,上层应用怎么知道它是温度还是压力?有了变量类型,语义就明确了。

我曾经踩过的坑:变量类型的DataType一定要和实际值匹配。我见过有人把Integer类型的数据塞到Double变量里,结果客户端读出来全是NaN。排查了半天,最后发现是类型定义错了。

3.4 数据类型(DataType)

数据类型是最基础的。OPC UA内置了很多:Boolean、SByte、Int16、UInt32、Float、Double、String、DateTime……但实际项目中,这些往往不够用。

比如我要表示一个“三维坐标”:

// 自定义结构体数据类型
DataType Position3D : Structure
{
    X : Double;
    Y : Double;
    Z : Double;
}

再比如我要表示一个“报警记录”:

// 自定义枚举数据类型
DataType AlarmSeverity : Enumeration
{
    Low = 0;
    Medium = 1;
    High = 2;
    Critical = 3;
}

自定义数据类型的好处是:结构清晰、语义明确、序列化高效。坏处是:客户端必须知道你的类型定义才能解析。所以,发布自定义类型时,一定要把类型描述(TypeDictionary)一起发布出去。

3.5 自定义类型设计——实战经验

好了,理论说完了,咱们聊聊实战。自定义类型设计,我总结了几个原则:

  1. 单一职责:一个类型只做一件事。别把温度、压力、流量塞到一个类型里。
  2. 适度抽象:别太具体,也别太抽象。比如“电机”比“西门子V20变频器”更合适。
  3. 考虑继承:公共属性放基类,特殊属性放子类。
  4. 版本控制:类型定义一旦发布,尽量不要改。非要改,就新建一个版本。

我记得有一次,一个客户要求把所有设备都定义成同一个类型,说“统一管理方便”。结果呢?温度设备有“温度”变量,压力设备也有“温度”变量,但实际没有意义。最后我不得不给他们重新设计了一套类型体系,多花了两周时间。

核心要点:类型设计是信息模型的基石。基础打好了,后面的开发事半功倍。基础没打好,后面全是坑。

3.6 类型系统的实际应用

最后,咱们看一个完整的例子。假设我要设计一个“智能电表”的信息模型:

// 1. 定义数据类型
DataType MeterReading : Structure
{
    Timestamp : DateTime;
    Voltage : Double;       // 电压
    Current : Double;       // 电流
    Power : Double;         // 功率
    Energy : Double;        // 电能
}

// 2. 定义变量类型
VariableType MeterReadingVariableType : BaseVariableType
{
    DataType : MeterReading;
    EngineeringUnit : "V/A/W/kWh";
}

// 3. 定义对象类型
ObjectType SmartMeterType : BaseObjectType
{
    // 属性
    Variable MeterID : String;              // 电表编号
    Variable MeterReading : MeterReadingVariableType;  // 读数
    Variable Status : Boolean;              // 状态

    // 方法
    Method ReadMeter();                     // 读取电表
    Method Calibrate();                     // 校准
    Method Reset();                         // 复位
}

这个模型清晰吗?清晰。可扩展吗?可扩展。比如我要加一个“三相电表”,继承SmartMeterType,再加两个读数变量就行了。

好了,这一章就到这里。类型系统是OPC UA的“语法”,掌握了它,你才能写出“好文章”。下一章咱们聊聊地址空间——信息模型怎么在服务器里组织起来。