国家对于工业互联网、基础软件等关键领域的重视程度不断提升,为工业领域的硬件与软件国产化提供了坚实的政策保障。国产操作系统对工业物联网的一些重要领域的适配支持一直在推进。本次通过国产UOS系统移植测试OPC-UA协议。
1、OPC UA通信协议
OPC UA 协议,即 OPC Unified Architecture,是 OPC 基金会为满足工业自动化领域需求而开发的一种通信协议123。以下是对该协议的具体介绍:
特点
-
平台独立性:不依赖特定的操作系统或硬件,可在 Windows、Linux、macOS 等多种系统上运行,支持跨平台的数据交换。
-
面向服务的架构:采用 SOA 原则,将通信任务分解为读取、写入、订阅、浏览、方法调用等多项服务,具有高度的灵活性和可扩展性。
-
强大的安全功能:内置身份验证、加密、数据完整性检查和访问控制等安全功能,确保数据在传输过程中的机密性、完整性和可用性,满足工业自动化领域对安全性的严格要求。
-
信息建模:定义了一个抽象的信息模型,允许用户自定义数据类型和对象模型,能够处理各种复杂的工业数据,满足工业自动化系统中多样化的数据需求。
-
传输层独立性:支持 TCP/IP、WebSockets 等多种传输层协议,可适应不同的网络环境和应用需求。
主要应用领域
-
工业自动化领域:
-
设备集成与互操作:使得不同厂商生产的设备,如 PLC、传感器、工业机器人、控制系统等可以无缝集成和相互协作,提高生产效率和灵活性1。
-
数据采集和监控:方便地从不同设备和系统中收集数据,并进行实时监控和分析,有助于工业过程的优化和问题排查。
-
云平台连接:工业设备可以通过 OPC UA 与云平台进行连接,实现远程监控、管理、诊断和维护,同时为数据分析提供更多可能性。
-
-
能源领域:广泛应用于智能电网、智能电表等能源管理设备中,实现与其他系统的数据交换和远程控制,提高能源管理的效率和可靠性,实时采集能源消耗数据,进行能源分析和优化,降低能源成本。
-
楼宇自动化领域:可将空调系统、照明系统、安防系统等楼宇自动化设备与其他楼宇管理系统进行集成,实现智能化的楼宇管理,如对设备的远程控制、状态监测和能源管理等。
-
智能制造领域:作为工业物联网的关键技术之一,OPC UA 能够实现设备之间的互联互通和数据共享,为智能制造提供数据基础,通过对大量设备数据的分析和挖掘,可以实现生产过程的优化、预测性维护等智能化应用。
2、OPC UA技术特性
在过去,不同厂商生产的设备使用不同的通信协议,导致设备之间难以互相沟通。这给工业自动化带来了许多挑战,比如数据集成困难、系统复杂等。OPC UA应运而生,解决了这些问题,带来了许多好处:
**1. 开放性:**OPC UA是一种开放的技术标准,可以应用于不同的设备和系统。无论是传感器、控制器还是各种工业设备,只要支持OPC UA,它们就可以相互通信,实现无缝集成。
**2. 统一架构:**OPC UA提供了一种统一的架构和数据模型,使得不同设备的数据能够以统一的方式进行表示和交换。这样一来,设备之间的数据传输变得更加简单和可靠。
**3. 跨平台和跨语言:**OPC UA支持多种操作系统和编程语言。无论是Windows、Linux还是嵌入式系统,无论是C++、Java还是Python,都可以使用OPC UA进行通信,降低了集成的复杂性。
- OPC UA的工作原理
OPC UA采用了现代化的网络通信技术,基于Web服务和互联网技术的基础之上。它使用了一种称为"面向对象"的方式来描述设备和系统之间的通信。
在OPC UA中,每个设备和系统都被抽象为一个对象,这些对象有自己的属性、方法和事件。通过读取和写入对象的属性,调用对象的方法,以及监听对象的事件,设备和系统可以进行数据交换和控制操作。
此外,OPC UA还提供了多种传输方式,如TCP/IP、HTTPS等,可以根据实际情况选择最适合的传输方式。同时,OPC UA还提供了安全机制,确保通信的安全性和可靠性。
- opc连接过程
OPC UA 连接建立过程:
建立连接
客户端创建socket之后,发送Hello报文,其中包括客户端支持的buffer大小。服务器接收报文后返回Acknowledge报文完成buffer大小协商。协商好的buffer大小会上报给SecureChannel层,其中SendBufferSize字段指定在此连接上发送的MessageChunk的最大长度。
Hello/Acknowledge报文一般只能发送一次,若对端再一次接收到该报文会报错并关闭socket。如果socket创建之后,服务器很长一段时间(可配,不超过2分钟)内没有接收到Hello报文会主动关闭socket。
客户端接收到Acknowledge报文后,紧接着会发送OpenSecureChannel请求报文,服务器若接受该channel会将该socket和一个SecureChannelId关联,后续服务器发送应答报文时会根据SecureChannelId决定使用哪个socket发送。客户端接收到OpenSecureChannel应答报文后也会做相同的操作。
连接断开
客户端首先发送CloseSession请求报文准备关闭连接,服务器收到后,会回复一个CloseSessionResponse报文。
客户端发送CloseSecureChannel请求报文主动关闭连接,服务器接收该报文后会释放该channel相关的所有资源,但不会发送CloseSecureChannel应答报文。
3、open62541概述
open62541(http://open62541.org)是一个开源的免费实现OPC UA(OPC统一架构),用C99和C ++ 98语言的通用子集编写。该库可与所有主要编译器一起使用,并提供实现专用OPC UA客户端和服务器的必要工具,或将基于OPC UA的通信集成到现有应用程序中。open62541库与平台无关。所有特定于平台的功能都是通过可交换的插件实现的。为主要操作系统提供了插件实现。
- open62541功能
open62541实现了OPC UA二进制协议栈以及客户端和服务器SDK。它目前支持Micro Embedded Device Server Profile以及一些其他功能。服务器二进制文件的大小可能低于100kb,具体取决于所包含的信息模型。
- 通讯栈
OPC UA二进制协议
分块(分割大信息)
可交换网络层(插件),用于使用自定义网络API(例如,在嵌入式目标上)
- 加密通信
客户端中的异步服务请求
- 信息模型
支持所有OPC UA节点类型(包括方法节点)
支持在运行时添加和删除节点和引用。
支持对象和变量类型的继承和实例化(自定义构造函数/析构函数,子节点的实例化)
单个节点的访问控制
- 订阅
支持订阅/监视项目以获取数据更改通知
每个受监视值的资源消耗非常低(基于事件的服务器体系结构)
- 代码生成
支持从标准XML定义生成数据类型
支持从标准XML定义生成服务器端信息模型(节点集)
4、open62541移植
- 源码下载
- 编译与安装open625412
安装编译工具及相关依赖包:
sudo apt-get install git build-essential gcc pkg-config cmake python
# enable additional features
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
进入源码目录,创建build目录,通过cmake及make进行编译:
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)
需选择动态库与测试例程:
安装:
sudo make install
5、 简单测试例程
测试平台:x86平台、国产UOS v20桌面操作系统
测试程序编译时需要独立的open62541.c、 open62541.h文件进行编译,可在下述网址下载http://open62541.org。
-
服务端程序
#include <open62541/plugin/log_stdout.h>
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <signal.h>
#include <stdlib.h>
static volatile UA_Boolean running = true;
static void stopHandler(int sig) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "received ctrl-c");
running = false;
}
int main(void) {
signal(SIGINT, stopHandler);
signal(SIGTERM, stopHandler);
UA_Server *server = UA_Server_new();
UA_ServerConfig_setDefault(UA_Server_getConfig(server));
UA_StatusCode retval = UA_Server_run(server, &running);
UA_Server_delete(server);
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}
源程序目录需要包括测试程序与open62541.c、 open62541.h文件,通过下述命令一起编译。
$ gcc -std=c99 open62541.c myServer.c -o myServer
-
客户端程序
#include <open62541/client_config_default.h>
#include <open62541/client_highlevel.h>
#include <open62541/plugin/log_stdout.h>int main(void) {
UA_Client *client = UA_Client_new();
UA_ClientConfig_setDefault(UA_Client_getConfig(client));
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
if(retval != UA_STATUSCODE_GOOD) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"The connection failed with status code %s",
UA_StatusCode_name(retval));
UA_Client_delete(client);
return 0;
}UA_Variant value;
UA_Variant_init(&value);const UA_NodeId nodeId =
UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME);retval = UA_Client_readValueAttribute(client, nodeId, &value);
if(retval == UA_STATUSCODE_GOOD &&
UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_DATETIME])) {
UA_DateTime raw_date = *(UA_DateTime *) value.data;
UA_DateTimeStruct dts = UA_DateTime_toStruct(raw_date);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"date is: %u-%u-%u %u:%u:%u.%03u",
dts.day, dts.month, dts.year, dts.hour,
dts.min, dts.sec, dts.milliSec);} else {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Reading the value failed with status code %s",
UA_StatusCode_name(retval));
}
UA_Variant_clear(&value);
UA_Client_delete(client);
return 0;
}
源程序目录需要包括测试程序与open62541.c、 open62541.h文件,通过下述命令一起编译。