OPC是什么?
OPC是自动化行业及其他行业用于数据安全交换时的互操作性标准。它独立于平台,并确保来自多个厂商的设备之间信息的无缝传输,OPC基金会负责该标准的开发和维护。
OPC标准是由行业供应商,终端用户和软件开发者共同制定的一系列规范。这些规范定义了客户端与服务器之间以及服务器与服务器之间的接口,比如访问实时数据、监控报警和事件、访问历史数据和其他应用程序等,都需要OPC标准的协调。
OPC标准于1996年首次发布,其目的是把PLC特定的协议(如Modbus,Profibus等)抽象成为标准化的接口,作为“中间人”的角色把其通用的“读写”要求转换成具体的设备协议,反之亦然,以便HMI/SCADA系统可以对接。这也因此造就了整个行业内手工作坊的蓬勃兴起,通过使用OPC协议,终端用户就可以毫无障碍地使用最好的产品来进行系统操作。
最初,OPC标准仅限于Windows操作系统。因此,OPC是OLE for Process Control的缩写(中文意思:用于过程控制的OLE)。我们所熟知的OPC规范一般是指OPC Classic,被广泛应用于各个行业,包括制造业,楼宇自动化,石油和天然气,可再生能源和公用事业等领域。
随着在制造系统内以服务为导向的架构的引入,给OPC带来了新的挑战,如何重新定义架构来确保数据的安全性?这促使OPC基金会创立了新的架构----OPC UA,用以满足这些需求。与此同时,OPC UA也为将来的开发和拓展提供了一个功能丰富的开放式技术平台。
这就是为什么许多OPC会员以及与之合作的相关技术组织转而开始使用OPC UA的原因。
OPC有两种架构经典架构
和统一架构
经典架构(Classic Architecture)
OPC Classic规范基于Microsoft Windows技术,使用COM / DCOM(分布式组件对象模型)在软件组件之间交换数据。 规范为访问过程数据、报警和历史数据提供了单独的定义。
OPC Data Access (OPC DA)
OPC DA规范定义了数据交换,包括值、时间和质量信息。
OPC Alarms & Events (OPC AE)
OPC A&E规范定义了报警和事件类型消息信息的交换,以及变量状态和状态管理。
OPC Historical Data Access (OPC HDA)
OPC HDA规范定义了可应用于历史数据、时间数据的查询和分析的方法。
OPC Classic规范很好地服务于工业企业。 然而随着技术的发展,企业对OPC规范的需求也在增长。2008年,OPC基金会发布了OPC统一架构(OPC UA),这是一个独立于平台的面向服务的架构,集成了现有OPC Classic规范的所有功能,并且兼容OPC Classic。 几个因素影响了创建OPC UA的决策:
- Microsoft已经不再强化COM(组件对象模型)和DCOM(分布式COM)用以支持跨平台SOA(面向服务的体系结构)。
- OPC供应商想要一组服务器来公开OPC数据模型,例如数据访问、报警和事件、历史数据访问等。
- 为了保持竞争力,OPC供应商需要在非Microsoft系统上实施OPC,包括嵌入式设备。
- 其他合作组织需要一种可靠、高效的方式来传输高级结构化数据。
- 用户需要能够通过防火墙以安全的方式访问OPC服务器。
统一架构(Unified Architecture)
2008年发布的OPC统一架构(UA)将各个OPC Classic规范的所有功能集成到一个可扩展的框架中,独立于平台并且面向服务。
这种多层方法实现了最初设计UA规范时的目标:
- 功能对等性:所有COM OPC Classic规范都映射到UA
- 平台独立性:从嵌入式微控制器到基于云的基础设施
- 安全性:信息加密、身份验证和审核
- 可扩展性:添加新功能而不影响现有应用程序的能力
- 综合信息建模:用于定义复杂信息
功能对等性
基于OPC Classic的成功,OPC基金会推出了新的技术标准OPC UA。 OPC UA实现了所有OPC Classic的功能,并有以下的增强和超越:
- 发现:在本地PC和/或网络上查找可用的OPC服务器
- 地址空间:所有数据都是分层表示的(例如文件和文件夹),允许OPC客户端发现、利用简单和复杂的数据结构
- 按需:基于访问权限读取和写入数据/信息
- 订阅:监视数据/信息,并且当值变化超出客户端的设定时报告异常
- 事件:基于客户端的设定通知重要信息
- 方法:客户端可以基于在服务器上定义的方法来执行程序等
OPC UA产品和OPC Classic产品之间的集成可以通过COM/Proxy Wrappers轻松实现。
平台独立性
鉴于市场上有各种各样的硬件平台和操作系统,平台独立性就显得至关重要。OPC UA包含但不限于以下平台及系统:
- 硬件平台:传统PC硬件、云服务器、PLC、微控制器(ARM等)
- 操作系统:Microsoft Windows、Apple OSX、Android或任何Linux发行版本等
OPC UA为企业之间的互操作性提供必要的M2M、M2E及两者之间的基础架构。
安全性
企业在选择技术标准时最重要的考虑之一是安全性。OPC UA在通过防火墙时通过提供一套控制方案来解决安全问题:
- 传输:定义了许多协议,提供了诸如超快OPC二进制传输或更通用的SOAP-HTTPS等选项
- 会话加密:信息以128位或256位加密级别安全地传输
- 信息签名:信息接收时的签名与发送时必须完全相同
- 测序数据包:通过排序消除了已发现的信息重放攻击
- 认证:每个UA的客户端和服务器都要通过OpenSSL证书标识,提供控制应用程序和系统彼此连接的功能
- 用户控制:应用程序可以要求用户进行身份验证(登录凭据,证书等),并且可以进一步限制或增强用户访问权限和地址空间“视图”的能力
- 审计:记录用户和/或系统的活动,提供访问审计跟踪
可扩展性
OPC UA的多层架构提供了一个“面向未来”的框架。诸如新的传输协议、安全算法、编码标准或应用服务等创新技术和方法可以并入OPC UA,同时保持现有产品的兼容性。 今天的UA产品能够与未来的UA产品互联互通。
综合信息建模
OPC UA信息建模框架将数据转换为信息。通过完全面向对象的功能,即使是最复杂的多级结构也可以建模和扩展。数据类型和结构在配置文件中定义。 例如,现有的OPC Classic规范被建模为UA配置文件,也可以由其他组织扩展:
OPC UA系列标准
- 核心规范
- IEC 62541-1 概念和概述:给出OPC UA的概念和概述
- IEC 62541-2 安全模型:描述OPC UA客户端和服务器之间安全交互模型。
- IEC 62541-3 地址空间模型:描述了服务器地址空间的内容和结构。
- IEC 62541-4 服务:规定了OPC UA服务器提供的服务。
- IEC 62541-5 信息模型:规定了OPC UA服务器的类型及其关系。
- IEC 62541-6 映射:规定了OPC UA支持的传输映射和数据编码。
- IEC 62541-7 规约:规定了可用于OPC客户端和服务器的行规。
- 访问类型规范
- IEC 62541-8 数据访问:规定了使用OPC UA进行数据访问。
- IEC 62541-9 报警和条件:规定了使用OPC UA支持用于访问报警和条件。
- IEC 62541-10 程序:规定了支持对程序进行访问的OPC UA。
- IEC 62541-11 历史访问:规定了使用OPC UA进行历史访问,包括历史数据和历史事件。
- 应用规范
- IEC 62541-12 发现:规定了服务器在不同情况下如何工作,以及服务器和客户端之间如何交互。
- IEC 62541-13 聚合:规定了如何计算和返回聚合。
两种架构的区别
核心的区别是OPC是基于DOM/COM上,应用层最顶层;OPC UA是基于TCP IP scoket 传输层。而COM / DCOM(分布式组件对象模型)是Microsoft Windows技术,这就决定了使用OPC经典架构必须要跑windows,对于跑linux或者其他RTOS的嵌入式设备就用不了了,于是OPC UA就出现了,根据上面对OPC UA的介绍,它是被定义为跨平台,并且可以通过一些方法兼经典架构的产品。
开源实现open62541
OPC UA有一个国际电工技术委员会(IEC)制定的规范文档IEC TR 62541 ,这也是为什么这个开源协议叫open62541
的原因。open62541官网。
open62541是使用C99和C++ 98语言的通用子集编写的OPC UA(OPC统一体系结构)的开源和免费实现。该库可用于所有主要编译器,并提供必要的工具以实现专用的OPC UA客户端和服务器,或将基于OPC UA的通信集成到现有应用程序中。open62541库是独立于平台的。所有特定于平台的功能都是通过可替换的插件实现的。为主要操作系统提供了插件实现。
open62541已获得Mozilla Public License v2.0 (MPLv2)的许可。这样就可以将open62541库与任何专有软件组合并分发。复制和分发时,仅对open62541库本身所做的更改需要在MPLv2下获得许可。插件以及服务器和客户端示例均位于公共领域(CC0许可证)。可以在任何许可下重复使用它们,而不必发布更改。
它的库可以通过两种形式发布:标准源码和二进制形式(静态库或者动态库)。此外,单文件源码形式将整个库合并为一个.c和.h文件,可以轻松将其添加到现有项目中。
当前github上稳定发布版本为v1.0.1
open62541功能介绍
open62541实现了OPC UA二进制协议栈以及客户端和服务器SDK。最终服务器二进制文件的大小可能完全低于100kb,具体取决于所选功能和信息模型的大小
- 通讯栈
- OPC UA二进制协议
- 分块(分割大信息)
- 可交换网络层(插件),用于使用自定义网络API(例如,在嵌入式目标上)
- 加密通信
- 客户端中的异步服务请求
- 信息模型
- 支持所有OPC UA节点类型(包括方法节点)
- 支持在运行时添加和删除节点和引用。
- 支持对象和变量类型的继承和实例化(自定义构造函数/析构函数,子节点的实例化)
- 单个节点的访问控制
- 订阅
- 支持订阅/监视项目以获取数据更改通知
- 每个受监视值的资源消耗非常低(基于事件的服务器体系结构)
- 代码生成
- 支持从标准XML定义生成数据类型
- 支持从标准XML定义生成服务器端信息模型(节点集)
示例认识OPC UA到底能做什么?
下载源码
下载release版本1.0版
git clone -b 1.0 https://github.com/open62541/open62541.git
open62541还有一些子模块,当enable一些特殊功能时就需要他们了,例如自定义namespace等,这个需要使用以下命令去下载(在上面下载源码后的源码根目录下操作)
git submodule update --init
编译
在ubuntu上编译,编译前有一些工具需要安装:
sudo apt-get install git build-essential gcc pkg-config cmake python
# 使能了一些额外的特性
sudo apt-get install cmake-curses-gui # for the ccmake graphical interface
sudo apt-get install libmbedtls-dev # for encryption support
sudo apt-get install check libsubunit-dev # for unit tests
sudo apt-get install python-sphinx graphviz # for documentation generation
sudo apt-get install python-sphinx-rtd-theme # documentation style
cd open62541
mkdir build
cd build
cmake ..
make
# select additional features
ccmake ..
make
# build documentation
make doc # html documentation
make doc_pdf # pdf documentation (requires LaTeX)
编译选项
主选项
- RelWithDebInfo -O2 optimization with debug symbols
- Release -O2 optimization without debug symbols
- Debug -O0 optimization with debug symbols
- MinSizeRel -Os optimization without debug symbols
SDK特性选项
- UA_ENABLE_AMALGAMATION
单文件源码文件,开启此选项会编译出open62541.c,open62541.h这两个源文件,方便和自己应用程序整合。一般不会用open62541.c而是用编译出来的库文件进行链接。 - UA_ENABLE_SUBSCRIPTIONS
Enable 订阅 - UA_ENABLE_SUBSCRIPTIONS_EVENTS (EXPERIMENTAL)
Enable 订阅和事件. This is a new feature and currently marked as EXPERIMENTAL. - UA_ENABLE_METHODCALLS
Enable 方法 - UA_ENABLE_MULTITHREADING
多线程支持 - UA_ENABLE_DISCOVERY
- UA_ENABLE_DISCOVERY_MULTICAST
服务发现 - UA_ENABLE_ENCRYPTION
使能加密,当前release版本v1.0仅支持基于mbedtls的加密,如果要换为openssl需要使用v1.1-rc1版本,不过这是个最新的rc版本,可能会有一些bug。
动态库
- BUILD_SHARED_LIBS
默认编译的是静态库
整合到示例中
编译运行server
这里我们需要写一个demo,需要open62541.h和libopen62541.a,它们分别位于build目录和build/bin目录,整个目录结构:
.
├── CMakeLists.txt
├── open62541
│ ├── bin
│ │ └── libopen62541.a
│ └── open62541.h
└── src
└── server.c
Server来自官方例子
#include "open62541.h"
#include <signal.h>
#include <stdlib.h>
UA_Boolean running = true;
static void stopHandler(int sign)
{
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
running = false;
}
int main(void)
{
signal(SIGINT, stopHandler);
signal(SIGTERM, stopHandler);
//创建一个server
UA_Server *server = UA_Server_new();
//使用默认配置参数
UA_ServerConfig_setDefault(UA_Server_getConfig(server));
//启动server
UA_StatusCode retval = UA_Server_run(server, &running);
UA_Server_delete(server);
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}
运行server,使用UaExpert
工具当做client来和server通信。
使用UaExpert
UaExpert有多种版本,windows,linux,Mac版本,安装打开,按如下方式添加server信息,并连接上server
UaExpert的地址空间视图
UaExpert把OPC UA抽象的信息模型以一种文件夹层次视图展现出来。如果以面向对象的思想来理解这个图就比较好理解(不完全正确)。先忽略Views,
- 在Types下定义了各种分类的对象类型(类)
例如/Root/Types/ObjectTypes/BaseObjectType/ServerType - 在Object目录下就是对象类型的实例(实例)
/Root/Objects/Server - 每一实例又是可以由一种或多种不同的实例进行组合。
将Server展开,它下面又有很多其他的对象类型的实例。这里的理解有很大的问题,它很像是多重继承,但是呢它又不是在类的定义时说明的这种多重继承关系。
再回到OPC UA标准
OPC UA系统架构
OPC UA采用客户端/服务器架构,每个系统可以包含多个客户端和服务器。一个客户端可以同时与一个或多个服务器交互,每个服务器可以与一个或多个客户端交互。一个应用可以
同时组合客户端和服务器,用于与其他服务器和客户端交互。其系统架构如图:
OPC UA系统架构
OPC UA客户端架构
OPC UA服务器架构
OPC UA信息模型
OPC UA信息模型就是一种描述如何将具体事物转换为可以在网络上对该事物交换信息(例如访问,控制)的方法。在阅读下面介绍时,最好用UaExpert作为client连上用open62541实现的server结合来看。
地址空间
要点:
- 地址空间是用来给服务器提供标准方式,以向客户端表示对象。
- 地址空间的实现途径是使用对象模型,通过变量和方法的对象,以及表达关系的对象。
- 地址空间中模型的元素被称为节点,为节点分配节点类来代表对象模型的元素。
- 对象及其组件在地址空间中表示为节点的集合,节点由属性描述并由引用相连。
- OPC UA建模的基本在于节点和节点间的引用。
对象模型
节点模型
要点:
- 节点根据用途分属于不同的节点类别(NodeClass),一些表示实例(/Root/Objects),一些表示类型(/Root/Types)。
- 节点类依据属性和引用来定义。OPC UA规范定义的节点类称为地址空间的元数据,地址空间中每个节点都是这些节点类的实例。
- 节点是节点类的实例,属性和引用是节点的基本组件。
- 属性(Attribute)用于描述节点,不同的节点类别有不同的属性(属性集)。节点类的定义中包括属性的定义,因此属性不包括在地址空间中。
- 引用(Reference)表示节点间的关系。引用被定义为引用类型节点的实例,存在于地址空间中。
节点通用属性
包含引用的节点为源节点,被引用的节点称目标节点。引用的目标节点可以与源节点在同一个地址空间,也可以在另一个OPC服务器的地址空间,甚至是目标节点可以不存在。
引用模型
类型定义节点
类型定义节点是服务器提供用于对象和变量的类型定义,HasTypeDefinition引用来连接一个实例,该实例的类型定义由类型定义节点定义。
下图以变量这种节点类别(NodeClass)来举例说明
OPC UA规范用来为不同厂商的设备和程序间提供接口标准化。其一大特点在于能够公开复杂的数据和复杂的系统。
OPC UA定义一个具体对象,可以用来描述一个车间,一条产线,一台设备或者是一个传感器。具象的事物包含多种信息,而信息以不同的形式统一被定义在地址空间中。地址空间是OPC服务器用来表示具体事物对象的一个标准方式。一个具体事物,如一台空调,在地址空间中被定义为对象,该对象中所包括的是空调的各种信息,信息以不同形式,存在于该对象中。如果有多台空调,逐个定义为对象是不可取的。可以通过定义一个空调的对象类型来描述空调所具有的共性,然后通过把对象设定为该对象类型,来产生多个具有该空调对象类型的实际对象,即实例化。在实例化的过程中,如何定义一个对象属于特定的对象类型,或者一个对象归属于另一个对象,则可以通过引用来表示其关系。空调被映射成了对象,使用对象类型实例化来产生多个对象,对象和对象类型都存在于地址空间中,它们有着一个共同的名字,节点。所以,可以这样理解,地址空间其实就是节点和引用(节点间关系)所存在的一个虚拟的空间(其实它是用来表示对象的标准方式)。
- 空调有风扇,有温度传感器,在地址空间中可以将其定义为空调对象包含的对象;
- 空调还有温度、风速、温度设定点(非实物,一种数学概念),可以定义为空调对象下的变量;
- 空调还可以开、可以关,开和关的动作可以定义为空调对象的方法。
- 空调也许还具有报警功能,向外发送通知,则可以定义为事件。
- 由此,对象、变量和方法构成了OPC UA最重要的节点类别。对象拥有变量和方法,而且可以触发事件。
标准的节点类
- 节点类用于定义OPC UA地址空间中的节点
- 地址空间中模型的元素被称为节点,为节点分配节点类来代表对象模型的元素。
- 节点类源于通用的基本节点类。
- 首先定义基本节点类,然后定义用于组织地址空间的节点类,之后再定义用于代表对象的节点类。
- 代表对象的节点类分为三种:用于定义实例,定义实例的类型,以及定义数据类型。
标准的节点类有如下几种:
- 基本节点类:能够派生所有其他节点类。
- 对象节点类
- 对象类型节点类
- 变量节点类:定义数据变量
- 变量类型节点类:定义特性
- 方法节点类:定义方法,方法没有类型定义,可以绑定到对象上。
- 引用类型节点类:定义引用。
- 视图节点类:定义地址空间中节点子集。
基本节点类
基本节点类的属性:
名称 | 使用 | 数据类型 |
NodeId | M | NodeId |
NodeClass | M | NodeClass |
BrowseName | M | QualifiedName |
DisplayName | M | LocalizedText |
Description | O | LocalizedText |
WriteMask | O | UInt32 |
UserWriteMask | O | UInt32 |
注:M代表必备项,O代表可选项
- NodeId:节点ID,在服务器中唯一标识一个节点。是定位和在服务器间交换信息的最重要概念。浏览地址空间时,服务器返回NodeId,客户端在服务调用时使用NodeId来定位节点。
- BrowseName:浏览名称,仅用于浏览目的,不宜用来显示节点的名称,用作浏览地址空间浏览路径的一个非本地化人员可读的名称。
- DisplayName:显示名称,包含了节点的本地化名称。如果客户端响应显示节点名称给用户,宜使用该属性。
- Description:描述,本地化的文本中解释节点的含义。
- WriteMask:写入掩码,公开了客户端写入节点属性的可能性。该属性不考虑任何用户访问权。
- UserWriteMask:考虑用户访问权时,公开的客户端写入节点属性的可能性。指定哪个节点属性可被当前连接到服务器上的用户修改。
对象节点类
对象节点类用于定义对象。而对象被用于代表系统、系统组件、现实世界的对象和软件对象。对象节点类继承了基本节点类的基本属性。
注:0…* 表示没有限制,不使用或者可以无限次用;0…1 表示最多用一次;1 表示必须提供一次
- 必备的EventNotifier属性表示对象是否可以被用于订阅事件或者读和写事件的历史。
- HasTypeDefinition引用指向被用作对象的类型定义的对象类型。
- 使用HasComponent引用来定义数据变量、对象和对象的方法。
- 使用HasProperty引用来定义对象的特性。
- HasModellingRule规定了建模规则,对象最多只能指定一个该引用。
- HasModelParent引用规定了对象的父模型。
对象类型节点类
为对象提供定义,用来定义对象类型。从基本节点类继承基本属性,附加IsAbstract属性表示对象类型是否是抽象的。
变量节点类
变量代表简单或复杂的值,总是被定义为地址空间里其他节点的特性或数据变量,变量由变量类型定义。
变量类型节点类
变量总是基于变量类型,指向其变量类型的HasTypeDefinition引用。
方法节点类
用来定义方法,从基本节点类继承基本属性,不定义附加属性。使用HasProperty引用可以为方法定义特性,InputArgument和OutputArgument特性。
引用类型节点类
引用被定义为引用类型节点的实例。引用类型节点类继承了基本节点类的基本属性,其BrowseName属性在服务器中必须唯一。IsAbstract属性指示引用类型是否抽象,抽象的引用类型不能被实例化,只能用于组织。
视图节点类
视图定义地址空间里的节点的子集(即地址空间的一个摘要),全部地址空间是缺省视图。针对庞大的下层系统(的地址空间),使用视图可以只关注感兴趣的数据。视图也是地址空间中的节点。浏览视图上下文时,开始于视图节点。视图中所有节点应该是可访问的,浏览可以进行跳转。视图节点可以作为进入地址空间的附加输入项,还可以作为组织地址空间的一种结构。视图的另一个重要作用是,跟踪地址空间的不同版本,其ViewVersion特性,只要视图内容(如属于视图的引用和节点)发生改变,特性就会被更新。视图节点类定义视图。
OPC UA地址空间中的节点是用节点类来定义的。OPC UA定义了基本的节点类,能够派生其他所有的节点类。而节点类在地址空间中也是使用节点来表示。为节点分配所属的节点类,则使用引用。
作为OPC中最重要的节点类别,对象、变量和方法(这三个概念基本可以表述一切具象事务),分别涉及到节点类中的对象/对象类型节点类、变量/变量类型节点类和方法节点类。
对象的类型定义是对象节点类,如一个基本对象其TypeDefinition可以是BaseObjectType,将对象和基本对象类型联系的纽带则是HasTypeDefinition引用。
如果是一个自定义的对象类型,则该对象的类型定义是对象类型节点类,即在基本对象类型的基础上进行重写,扩充其属性。通过实例化一个对象类型节点类,则得到属性重写的对象节点。
无论是何种类型的节点,在定义时首先需要关注该节点的引用,常用的引用如:
- HasTypeDefinition,指向了节点的类型定义,绑定对象或变量到其对象类型或变量类型。
- HasProperty,指定变量为特性,其源节点可以使任何节点类,目标节点应为变量。
- HasModellingRule,绑定建模规则到一个对象、变量或方法,如果没有建模规则,则不能正确实例化节点。
- HasModelParent,公开对象、变量或方法的父模型。
- HasComponent,关联对象或对象类型与其包含的对象、数据变量和方法。通过该引用,将对象的属性节点指定到对应的对象中。
- Organizes,用于组织地址空间中的节点,在地址空间中构造层次结构。它的源节点是对象或视图,如果它是对象,一般定义为FolderType或其子类型之一。
使用引用指定了节点间的关系,也就建立了地址空间的整体结构。将关注点定位到特定的节点,则需要关注节点的属性。任何类型的节点,都继承自基本节点类,拥有基本节点类所具有的属性,包括:NodeId,NodeClass,BrowseName,DisplayName,WriteMask,UserWriteMask。
基本节点类向外派生,产生了对象节点、变量节点、方法节点、视图节点以及事件节点。这些节点是对应节点类(类型节点)的实例。所以节点的重点在于节点类(定义节点类型的节点)
节点类有多种,但终究还是产生于元数据(UA规范定义的节点类)。更确切的说,节点类的基本就是BaseObjectType和BaseVariableType,以及描述数据(一般作为节点的Value属性)类型的BaseDataType。想要搞定节点,重点就是这三个。
对象,变量和方法
在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.