对象,变量和方法
在OPC UA中,最重要的节点类别是对象,变量和方法。
1. 节点类别为对象
节点类别为对象的节点用于(构成)地址空间结构。
- 对象不包含数据,使用变量为对象公开数值。
- 对象可用于分组管理对象、变量或方法(变量和方法总属于一个对象)。
- 对象也可以是一个事件通知器(设定EventNotifier属性),客户端可以订阅事件通知器来接收事件(事件在地址空间中是不可见的,被绑定到对象)。
2. 节点类别为变量
节点类别为变量的节点代表一个值。
- 值的数据类型取决于变量,类型的种类在BaseDataType中。
- 客户端可以对值进行读取,写入和订阅其变化。
- 变量节点最重要的属性是Value,它由DataType,ValueRank和ArrayDimensions属性定义,通过这三个属性,可以定义各种类型数据。
3. 节点类别为方法
节点类别为方法的节点,代表服务器中一个由客户端调用并返回结果的方法。
- 方法指定客户端使用的输入参数,并返回给客户端输出参数。
- 输入参数和输出参数作为方法的特性存在,是数据方法的变量。
- 客户端使用调用服务(Call)调用方法
4. 一个简单的例子
如图,是一个对象包含对象、变量、方法和生成事件的概念。
- 电机对象Motor包含Status变量,而Status变量使用HasTypeDefinition引用继承自BaseDataVariableType。客户端可以订阅该变量,从而在电机状态变化时得到通知。
- Motor对象有一些配置变量,在Configuration对象下管理着。客户端可以读取或订阅这些变量。其中Torque变量代表电机的转矩,其工程单位使用HasProperty引用,被定义为TorqueUnit的特性。
- Motor对象下有Start和Stop两个方法,客户端可以调用方法来操作电机。
- Motor对象的EventNotifier属性,定义了电机的事件,客户可以订阅事件来掌握电机的相关动态。
当不止存在一个电机时,则需要定义一个复杂的对象类型,通过实例化对象类型来产生具有共同属性的电机对象。
那么上图中的Motor对象,则可使用HasTypeDefinition引用,指向MotorType对象类型,通过实例化,产生多个电机。此时MotorType对象类型变成了一个自定义的类型节点,但MotorType下的对象、方法和变量,仍旧还是继承自基本节点类型。
小结
在地址空间中创建节点,实际就是操作基本节点类,从对象类型节点,变量类型节点继承,并修改属性,得到对象和变量节点。然后指定节点间的引用,来指定各节点间的关系,以及组织节点在地址空间中的结构。
对于复杂的对象,可以在基本对象类型的基础上创建复杂的对象类型。与创建对象类似,复杂基本类型下包含其他基本的或自定义的类型节点。再通过实例化,产生所需的复杂对象。
一个对象节点,是具有实际意义的,它可以映射到现实中的一个设备,而对象类型,并不具有与实物的映射关系,类似于一种概念模型,可以描述设备,但不能代表设备。对象类型中设定的数值,会被继承到对象中去,作为初始值。
5. 实例声明
如第一张图(变量,类型和方法)中所示,Motor对象下的节点,其节点类别都是对象、变量或方法,这些节点都是实例而不是类型。而第二张图(复杂对象类型及其继承)中所示的MotorType对象下的节点,是不具有真正的实际值的实例,被称为实例声明。
实例声明是命名的实体,用来定义复杂的对象类型。通常实例声明被定义为对象类型下的变量,对象和方法。更专业的定义是:实例声明是被对象类型通过正向层次引用,直接引用的节点或者通过另一个实例声明间接地引用的节点。
实例声明的特点是,相对对象类型,实例声明需要被唯一的标识。NodeId不能用于这一目的,因为实例声明通常与其在实例上的对应部分是不同的节点,NodeId也就不同。可以使用浏览名称替代,对间接引用的实例声明,则使用浏览路径。
(客户从对象类型开始,顺着层次化引用前进,检测实例声明的浏览路径。达到目标实例声明之前,添加每一个节点的浏览名称)
简言之,实例是从类型定义节点实例化来的对象,变量和方法,具有实际意义(可认为是映射到具象事物)。实例声明是类型定义节点下的对象,变量和方法,是一种不具有实际值的实例。
6. 建模规则
类型定义(对象类型或变量类型)引用的每一个实例,如果它有建模规则,它就成了实例声明。建模规则指定了关于对象类型的实例会怎么处理该实例声明。
简言之,建模规则就是实例声明到实例过程中的实例化规则。
建模规则有三种选择,也称为建模规则的命名规则:
- 第一种是使实例声明是强制的,这意味着每个实例必须有一个对应此实例声明的部分,具有相同浏览路径。
- 第二种是可选的,每个实例可以有这样一部分,但不强求。
- 第三种是约束,意味着该实例声明定义了一个该类型定义实例的约束。
建模规则使用ModellingRule类型的对象表示,每个ModellingRule有一个变量NamingRule(建模规则的命名规则)。
实例声明通过HasModellingRule引用类型引用一个ModellingRule对象来指定建模规则,如下图所示:
可选的和强制的建模规则并不指定当创建类型定义的新实例时,服务器如何处理实例声明。它可以为该实例创造新的节点,或只是引用现有的节点。类型定义的实例只需要引用一个具有相同浏览路径的相同类型的实例。在服务器运行时,节点可以改变,只要保证对该类型的每一个实例,始终有一个正确的浏览名称和类型的节点存在。
如下图,Address1中的创建了AddressType中全部实例声明的实例;Address2中只创建了引用Mandatory规则的实例声明的实例。在Address3和Address4中,共用了一个City,但是在两个对象中都存在与类型定义中相同的浏览路径,以及相同类型的实例声明的实例(即City),所有都是正确的。
Address1中的两个实例都有建模规则,只要它们不被其他类型定义引用(作为其他类型定义下的实例声明),它们就不是实例声明。Address3和Address4共用的City实例,当删除Address3时,Street必须被删除,但不能删除共用的City,它仍被Address4使用着。
一般建模规则可以改变,但命名规则必须保持不变。唯一例外的是,可选的可以被替换成强制,即允许建模规则被替换成约束更为严格的
一种复杂建模规则的例子
包含两种情况:
- 两个不同引用连接着相同的目标和源。Measurement引用了Temperature两次,如此实例中Measurement对应的部分也必须使用那两个引用来引用到同一个节点,而不能指向两个不同节点。
- 有两种不同的间接路径。EngineeringUnit通过两个不同路径被引用,如此允许一个实例从Configuration引用一个节点,从Temperature引用另一个不同节点。(两次引用的浏览路径不同,是合理的方法)
全继承层次结构如下图:
一般最好使用单一继承来简化地址空间
对于复杂建模,存在多个路径引用,要考虑两方面的问题:
- 覆盖实例声明。在实例声明进行实例化时,子类型要具有父类型的全貌,存在多个浏览路径时要区分父类型的建模规则。
- 定义父级模型。共享的节点(被多个节点引用)如果不定义父级就不能明确被写入的范围,即存在一个引用节点修改导致其他引用节点都变化(如作为特性的单位),所以需要使用HasModelParent引用指定父模型。
作为约束的建模规则
存在另一种实例声明,用来定义对规则的约束,在它们的建模规则里,称有命名规则约束(NameingRuleConstraint)。
OPC UA规范中唯一定义了约束的建模规则叫做暴露内部数组(ExposesItsArray),可以被包含数组数据类型的变量类型使用,语义是数组内的每一项也可以作为一个子变量来暴露。
7. 复杂类型的子类型化
复杂类型被子类型化时,父类型的基本特性需要满足,因此强制的实例声明必须在每个子类型的实例上都可用,父类型的约束在子类型都旅行,并且只能进一步加以限制(即,可选的实例声明可以在子类型变成强制的,但强制的实例声明不能在子类型变成可选的)。
复杂类型的子类型化,一般有两种方式:
- 每个子类型复制所有父类型的实例声明或引用相同的节点(即定义实例节点是通过把类型定义节点的所有内容复制一份)。
- 不复制,但客户端需要请求父类型的实例声明来得到子类型的全貌(即父类型仍旧被子类型继承,但不是复制代码到子类型。子类型不用重复父类型的实例声明,称为完全继承实例声明层次)。如下图所示,AddressType由InternationalAddressType进行子类型化,继承了父类型的全部实例声明,并增加了实例声明Country,在右侧是其全继承的实例声明层次结构。
另外,实例完全继承的实例声明,所有实例声明必须有唯一的浏览路径(即子类型不能为不同实例声明使用同一浏览路径),不过子类型能够覆盖父类型现有的实例声明(如更严格的建模规则)
父级概念 ModelParent
一个节点可能被多个节点共享,例如一个静态类变量包含了所有实例相同的值。客户只是读取该节点数据,不关心节点是不是共享的。但是当客户准备变更节点时,客户端最好知道节点在什么范围内可以被改变。(否则其他所有引用了该节点的节点都会发生改变,而这些改变对其他节点来说是不应发生的)
OPC UA的HasModelParent引用从被包含的节点指向定义其范围的父节点,用于指示客户更改的范围,这样就不会影响其他实例。
上图左边,两个Device实例共享一个特性Icon(指向同一个节点),如果为其中一个实例对象如Device1更改图标,这会影响到类型定义以及其他实例对象如Device2.
为了避免这种问题,可以创建一个新的Icon,如在上图右边为Device2引用了新图标,这样就可以控制变化只发生在Device2中,当修改Device2下的Icon特性时,不会影响Device1.