0、概述

OPC UA(开放式产品通信统一架构,Open Platform Communications Unified Architecture)是一种通信协议和通信架构,用于实现工业自动化系统中设备之间的数据交换和通信。

硬核技术分析OPC UA
Kepserver EX6配置opc ua服务端 以及客户端
opc uap官方参考文档
opc-ua技术资料网站汇总
OPC UA开源库介绍

UAExpert:
C++ UA Server SDK Document

C++库:
https://github.com/OPCFoundation
https://github.com/ASNeG
https://github.com/FreeOpcUa/freeopcua

open62541的C++库:
https://github.com/open62541pp/open62541pp

open62541(C库)
官网:https://open62541.org/
文档:https://www.open62541.org/doc/master/client.html
下载:https://github.com/open62541/open62541
Building open62541
open62541专栏
open62541中文文档
open62541(R1.1.2)中文文档 (译文)
Qt_OpcuaClient
OPCUAClient_Qt
QT实现OPC_UA客户端程序以及与OPC_UA服务器通信
使用KEPServerv6进行OPC_UA的服务器搭建

对象可包含:对象、变量和方法。引用类型是节点的基类
OPC UA学习心得 — 1 OPC基础
OPC UA学习心得 — 3 对象、变量和方法

1、环境搭建

工具:
KepServer的下载安装与使用说明

为了快速、完整地体验从 PLC( Programmable Logic Controller,编程逻辑控制器)采集数据、传输到上位机进行展示的完整流程,可基于 SIMATIC_PLCSIM_Advanced_V3 、 TIA Portal V16 、 KEPServerEX6 以及 UAExpert 搭建西门子 PLC 的仿真环境。

  • PLC是TCP服务器;
  • KEPServer是TCP客户端,同时也是OPC服务器,并且自带了是OPC客户端;
  • UAExpert是OPC客户端。

环境准备
1、安装SIMATIC_PLCSIM_Advanced_V3
先安装WinPcap_4_1_3.exe
再安装SIMATIC_PLCSIM_Advanced_V3.exe
SimEKBInstall2022.07.26.exe 选中需要的密钥,勾选你的软件,然后选择安装长密钥。
重启S7-PLCSIM Advanced V3.0
2、安装TIA Portal V16
双击安装
SimEKBInstall2022.07.26.exe在左侧列表双击TIA Portal,选择TIA Portal V16,勾选右侧的STEP 7 Professional V16,然后选择安装长密钥。
3、安装KepServer
4、安装UAExpert

1、编译

OPC UA/DA协议库open62541的源码编译及案例测试

1.1、下载源码

1、github下载:注意选择的是1.2版本,1.3.6编译pollfa有问题
https://github.com/open62541/open62541

2、git工具下载

git clone --recursive https://github.com/open62541/open62541.git
git clone --recursive https://gitee.com/qihuanweidu007/open62541.git  #国内gitee下载
#如果某些模块无法加载,重新调整网络后,继续加载确保无fail输出
cd open62541
git submodule update --remote --recursive

1.2、windows10编译

确保系统安装了cmake、MinGW、VC和python3工具,将工作路径加入环境变量path中。
MinGW直接使用的是Qt的minGW目录(D:\Qt\Qt5.12.3\Tools\mingw730_64\bin),将其加入到path中。
有两种编译方式:命令行编译、图形化编译。

1.2.1 命令行编译

#minGW编译
mkdir build_mingw					#创建编译目录
cd build_mingw
cmake -G "MinGW Makefiles" .. -DUA_ENABLE_AMALGAMATION=ON 
#如果提示无法查找到python3包,直接显式指定
cmake -G "MinGW Makefiles" .. -DUA_ENABLE_AMALGAMATION=ON -DPython3_EXECUTABLE="D:\\workForSoftware\\python36\\python.exe" 
#编译
mingw32-make -j4 #或cmake --build . --config release
 
#vc编译
md build_vc
cd build_vc
#本文采用vs2017版本
cmake -G "Visual Studio 15 2017" .. -DUA_ENABLE_AMALGAMATION=ON -DPython3_EXECUTABLE="D:\\workForSoftware\\python36\\python.exe" 
cmake --build . --config release
#可能会出现 error C2220: 警告被视为错误 - 没有生成“object”文件,
#可以通过文本编辑工具将生成的open62541.c文件改为ANSI格式,然后再次编译

1.2.2 cmake图形化编译

在open62541源码目录下新建build目录,将CMakeLists.txt拖入cmake-gui,where to build the binary选择刚才新建的build目录,选择msvc64位,并勾选UA_ENABLE_AMALGAMATION,以便合并生成open62541.h和open62541.c

1.3 Linux编译

1.3.1 源码编译

#进入open62541目录
mkdir build_linux
cd build_linux
cmake .. -DUA_ENABLE_AMALGAMATION=ON -DUA_MULTITHREADING=100 -DPython3_EXECUTABLE="/usr/bin/python3" 
make -j4
#open62541.c:68808:43: error: comparison is always false due to limited range of data type [-Werror=type-limits] if(i < T##_MIN || (i > 0 && (t)i >= T##_MAX))
#修改为:if(i < T##_MIN || (i > 0 && (t)i >= T##_MAX)) ,再次编译
make -j4

example编译:

#先调整CMakeLists.txt
#set(build_dir ${PROJECT_SOURCE_DIR}/../../build_mingw)
#set(build_dir ${PROJECT_SOURCE_DIR}/../../build_vc)
set(build_dir ${PROJECT_SOURCE_DIR}/../../build_linux)
 
#进入open62541目录
cd demo/server_test
mkdir build_linux
cd build_linux
cmake .. -DUA_ENABLE_AMALGAMATION=ON
make -j4

1.3.2 成功的

linux系统下open62541的安装

mkdir build
mkdir install
cd build
cmake -DUA_ENABLE_AMALGAMATION=ON -DUA_ENABLE_ENCRYPTION=ON -DCMAKE_INSTALL_PREFIX=../install/ ..

报错:

-- Could NOT find Git (missing: GIT_EXECUTABLE) 
-- Could NOT find Git (missing: GIT_EXECUTABLE) 
CMake Warning at tools/cmake/SetGitBasedVersion.cmake:82 (message):
  Failed to determine OPEN62541_VERSION from repository tags.  Using default
  version "".
Call Stack (most recent call first):
  CMakeLists.txt:40 (set_open62541_version)


-- open62541 Version: 1.2.9-unknown
-- Architectures included in amalgamation: posix
-- The selected architecture is: posix
CMake Error at /home/ppp/mysoft/cmake/share/cmake-3.22/Modules/FindPackageHandleStandardArgs.cmake:230 (message):
  Could NOT find MbedTLS (missing: MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY
  MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY)
Call Stack (most recent call first):
  /home/ppp/mysoft/cmake/share/cmake-3.22/Modules/FindPackageHandleStandardArgs.cmake:594 (_FPHSA_FAILURE_MESSAGE)
  tools/cmake/FindMbedTLS.cmake:35 (find_package_handle_standard_args)
  CMakeLists.txt:483 (find_package)


-- Configuring incomplete, errors occurred!
See also "/home/ppp/TianSi/open62541-1.2/build/CMakeFiles/CMakeOutput.log".

openAI解答:
在这里插入图片描述
解决:

sudo apt-get update
sudo apt-get install libmbedtls-dev
sudo apt-get install git
cmake -DUA_ENABLE_AMALGAMATION=ON -DUA_ENABLE_ENCRYPTION=ON -DCMAKE_INSTALL_PREFIX=../install/ ..

qt中使用注意事项:

unix:DEFINES += UA_ARCHITECTURE_POSIX
LIBS += -L$$PWD/ -lopen62541

# 添加 MbedTLS 头文件路径
INCLUDEPATH += /usr/include/mbedtls
# 链接 MbedTLS 库
LIBS += -lmbedtls -lmbedx509 -lmbedcrypto

qt中加入MbedTLS库:
在这里插入图片描述

2、使用

2.0 各种error

1、pro文件中加入定义和引入库

QMAKE_CFLAGS += -std=c99
DEFINES += UA_ARCHITECTURE_WIN32
LIBS += -lpthread libwsock32 libws2_32
win32: LIBS += -lWs2_32					#同上

2、服务端:Error binding a server socket: 以一种访问权限不允许的方式做了一个访问套接字的尝试。
127.0.0.1:4840端口被server_runtime.exe占用,即KEPServerEx 6.4 Runtime服务占用此端口,在服务管理中将其停止。
0.0.0.0:4840被opcualds.exe占用,C:\Program Files (x86)\Common Files\OPC Foundation\UA\Discovery\bin,安的太多了,对不上是哪个了。打开任务管理器,点击详细信息,找到PID为9952的进程,将其终止。

tasklist | findstr "19168"

正常的连接端口占用情况 :

2.1 数据类型

open62541中文文档

UA_Boolean		//bool
UA_SByte		//int8_t
UA_Byte			//uint8_t
UA_Int16		//int16_t 
UA_UInt16		//uint16_t
UA_Int32		//int32_t 
UA_UInt32		//uint32_t
UA_Int64		//int64_t 
UA_UInt64		//uint64_t 
UA_Float		//float 
UA_Double		//double

UA_StatusCode		//状态 uint32_t
UA_String			//字符串 长度加指针: size_t length;   UA_Byte *data;
UA_DateTime 		//日期时间 int64_t  自1601年1月1日(UTC)以来100纳秒间隔的数量
UA_Guid				//UA_Guid_equal()
UA_ByteString		//字节字符串 UA_ByteString_allocBuffer()、UA_BYTESTRING()、UA_BYTESTRING_ALLOC()
UA_XmlElement		//UA_String
UA_NodeId			//结构体:类型、联合值
					//UA_NODEID_NUMERIC()、UA_NODEID_STRING()、UA_NODEID_STRING_ALLOC()
					//UA_NODEID_GUID()、UA_NODEID_BYTESTRING()、UA_NODEID_BYTESTRING_ALLOC()
UA_ExpandedNodeId	//名称空间URI而不是索引,结构体:UA_EXPANDEDNODEID_NUMERIC()
UA_QualifiedName	//
UA_LocalizedText	//
UA_NumericRange		//
UA_Variant			//任何数据类型的值,结构体 UA_Variant_hasScalarType()、UA_Variant_setScalar()、UA_Variant_setScalarCopy()
UA_ExtensionObject	//任何数据类型的标量,
UA_DataValue		//关联状态代码和时间戳的数据值
UA_DiagnosticInfo	//与StatusCode关联的详细错误和诊断信息的结构
UA_TYPES			//该数组包含所有标准定义类型的描述。利用描述可进行通用操作,
					//T_init()、T_new()、T_copy()、T_deleteMembers()、T_delete()
					//UA_Int32_new()
					#define UA_TYPES_BOOLEAN 		0
					#define UA_TYPES_SBYTE 			1
					#define UA_TYPES_BYTE 			2
					#define UA_TYPES_INT16 			3
					#define UA_TYPES_UINT16 		4
					#define UA_TYPES_INT32 			5
					#define UA_TYPES_UINT32 		6
					#define UA_TYPES_INT64 			7
					#define UA_TYPES_UINT64 		8
					#define UA_TYPES_FLOAT 			9
					#define UA_TYPES_DOUBLE 		10
					#define UA_TYPES_STRING 		11
					#define UA_TYPES_DATETIME 		12
					#define UA_TYPES_GUID 			13
					#define UA_TYPES_BYTESTRING 	14
					#define UA_TYPES_XMLELEMENT 	15
					#define UA_TYPES_NODEID 		16
					#define UA_TYPES_EXPANDEDNODEID 17
					#define UA_TYPES_STATUSCODE 	18
					#define UA_TYPES_QUALIFIEDNAME 	19
					#define UA_TYPES_LOCALIZEDTEXT 	20
					#define UA_TYPES_EXTENSIONOBJECT 21
					#define UA_TYPES_DATAVALUE 		22
					#define UA_TYPES_VARIANT 		23
					#define UA_TYPES_DIAGNOSTICINFO 24
					....
					#define UA_TYPES_NODECLASS 		75
					#define UA_TYPES_EVENTNOTIFICATIONLIST 189
UA_UtcTime		//UA_DateTime
UA_LocaleId		//用户区域设置的标识符
UA_Duration		//UA_Double	(ms)	
UA_DataType		//	
array			//UA_Array_new()、UA_Array_copy()、UA_Array_delete()

//随机数生成器
void UA_random_seed(UA_UInt64 seed);
UA_UInt32 UA_UInt32_random(void); /* no cryptographic entropy */
UA_Guid UA_Guid_random(void);     /* no cryptographic entropy */

//发现服务集:
Service_FindServers()
Service_FindServersOnNetwork()
Service_RegisterServer()
Service_OpenSecureChannel()
Service_CloseSecureChannel()
Service_ActivateSession()
Service_CloseSession()

//NodeManagement服务集
Service_AddNodes()
Service_AddReferences()
Service_DeleteNodes()
Service_DeleteReferences()
Service_Browse()				//发现指定节点的引用
Service_BrowseNext()
Service_TranslateBrowsePathsToNodeIds()	//将文本节点路径转换为各自的ID
Service_RegisterNodes()			//客户端用于注册他们知道将重复访问的节点
Service_UnregisterNodes()
Service_Read()					//读节点的属性
Service_Write()					//写节点的属性
Service_Call()					//调用方法
Service_CreateMonitoredItems()	//创建一个或多个MonitoredItem并将其添加到订阅
Service_DeleteMonitoredItems()
Service_ModifyMonitoredItems()	//修改订阅的MonitoredItems
Service_SetMonitoringMode()		//为订阅的一个或多个MonitoredItem设置监视模式
Service_CreateSubscription()	//创建订阅
Service_ModifySubscription()	//修改订阅
Service_SetPublishingMode()		//在一个或多个订阅上启用通知发送
Service_Publish()				//发布服务
Service_Republish()				//请求订阅从其重新传输队列重新发布NotificationMessage
Service_DeleteSubscriptions()	//删除属于客户端会话的一个或多个订阅
 
UA_Node							//节点,结构体
UA_VariableTypeNode				//变量节点
UA_MethodNode					//方法节点,定义可调用函数,并使用Call服务调用
UA_ObjectNode					//对象节点,可能包含变量,方法和其他对象
UA_ObjectTypeNode				//ObjectTypes提供对象的定义
UA_ReferenceTypeNode 			//
UA_DataTypeNode
UA_ViewNode

UA_Node_setAttributes()			//

UA_Server_new()
UA_Server_delete()
UA_Server_run()
UA_Server_run_startup()
UA_Server_run_iterate()
UA_Server_run_shutdown()

UA_Server_addRepeatedCallback()	//重复回调
UA_Server_changeRepeatedCallbackInterval()
UA_Server_removeRepeatedCallback()
UA_Server_read()				//
UA_Server_write()
UA_Server_readNodeId()
UA_Server_readNodeClass()
UA_Server_readBrowseName()
UA_Server_readDisplayName()
UA_Server_readWriteMask()
UA_Server_readValue()
UA_Server_writeValue()
UA_Server_browse()

UA_Server_register_discovery()
UA_Server_setVariableNode_dataSource()
UA_Server_setMethodNode_callback()
UA_Server_call()

UA_Server_addVariableNode()
UA_Server_addObjectNode()
UA_Server_addObjectTypeNode()
UA_Server_addViewNode()
UA_Server_addReferenceTypeNode()
UA_Server_addDataTypeNode()
UA_Server_addDataSourceVariableNode()
UA_Server_addMethodNodeEx()
UA_Server_addMethodNode()

UA_Client						//客户端结构体
UA_ClientConfig					//客户端配置数据
UA_Client_new()
UA_Client_delete()
UA_Client_reset()
UA_Client_getState()
UA_Client_getContext()

UA_Client_connect()
UA_Client_connect_username()
UA_Client_disconnect()
UA_Client_close()
UA_Client_manuallyRenewSecureChannel()
UA_Client_getEndpoints()				//Gets a list of endpoints of a server
UA_Client_findServers()					//Gets a list of all registered servers at the given server
UA_Client_findServersOnNetwork()		//Get a list of all known server in the network
UA_Client_Service_read()
UA_Client_Service_write()
UA_Client_Service_call()
UA_Client_Service_addNodes()
UA_Client_Service_addReferences()
UA_Client_Service_deleteNodes()

UA_Client_runAsync()
UA_Client_AsyncService_read()
UA_Client_readNodeIdAttribute()
UA_Client_writeNodeIdAttribute()
UA_Client_writeNodeClassAttribute()
UA_Client_writeValueAttribute()

UA_Client_call()					//方法调用

UA_Client_addVariableNode()
UA_Client_addObjectNode()
UA_Client_addMethodNode()
UA_Client_deleteNode()

UA_CreateSubscriptionRequest_default()
UA_Client_Subscriptions_create()	//订阅
UA_Client_Subscriptions_modify()
UA_Client_Subscriptions_delete()
UA_Client_Subscriptions_deleteSingle()
UA_Client_Subscriptions_setPublishingMode()

UA_Client_MonitoredItems_createDataChanges()
UA_Client_MonitoredItems_createEvents()
UA_Client_MonitoredItems_delete()





2.1 服务端使用


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(int argc, char **argv) {
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);
 
    UA_ServerConfig *config = UA_ServerConfig_new_default();
    UA_Server *server = UA_Server_new(config);
 
    UA_StatusCode retval;
    /* create nodes from nodeset */
    if (myNS(server) != UA_STATUSCODE_GOOD) 
    {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Could not add the example nodeset. "
            "Check previous output for any error.");
        retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
    } 
    else 
    {
        UA_NodeId createdNodeId;
        UA_ObjectAttributes object_attr = UA_ObjectAttributes_default; 
        object_attr.description = UA_LOCALIZEDTEXT("en-US", "A pump!");
        object_attr.displayName = UA_LOCALIZEDTEXT("en-US", "Pump1");
 
        // we assume that the myNS nodeset was added in namespace 2.
        // You should always use UA_Server_addNamespace to check what the
        // namespace index is for a given namespace URI. UA_Server_addNamespace
        // will just return the index if it is already added.
        UA_Server_addObjectNode(server, UA_NODEID_NUMERIC(1, 0),					//UA_Server *server, const UA_NodeId requestedNewNodeId
                                UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),		//const UA_NodeId parentNodeId,
                                UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),			//const UA_NodeId referenceTypeId,
                                UA_QUALIFIEDNAME(1, "Pump1"),						//const UA_QualifiedName browseName,
                                UA_NODEID_NUMERIC(2, 1002),							//const UA_NodeId typeDefinition,
                                object_attr, NULL, &createdNodeId);					//const UA_ObjectAttributes attr,
                                													//void *nodeContext, UA_NodeId *outNewNodeId
 
 
        retval = UA_Server_run(server, &running);
    }
    UA_Server_delete(server);
    UA_ServerConfig_delete(config);
    return (int) retval;
}

使用UaExpert观察:

2.2 客户端使用

open62541版本的不同,使用方法也略有不同。
基于open62541库的OPC UA协议节点信息查询及多节点数值读写案例实践
open62541学习

2.2.1 创建客户端

官方代码:

UA_Client *client = UA_Client_new();
UA_ClientConfig_setDefault(UA_Client_getConfig(client));
UA_StatusCode status = UA_Client_connect(client, "opc.tcp://DESKTOP-xxxxx:4840");        //服务端默认的[0.0.0.0]4840
if(status != UA_STATUSCODE_GOOD) 
{
    UA_Client_delete(client);
    return status;
}
......
       
UA_Variant_clear(&value);
UA_Client_delete(client); 	/* Disconnects the client internally */

2.2.2 获取服务器端点(Endpoint)

获取服务器端点(Endpoint)

char *uri = "opc.tcp://127.0.0.1:49320";
/* Listing endpoints */
UA_EndpointDescription* endpointArray = NULL;
size_t endpointArraySize = 0;
UA_StatusCode retval = UA_Client_getEndpoints(client, uri,	&endpointArraySize, &endpointArray);
if (retval != UA_STATUSCODE_GOOD) {
	UA_Array_delete(endpointArray, endpointArraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
	UA_Client_delete(client);
	return (int)retval;
}
printf("%i endpoints found\n", (int)endpointArraySize);
for (size_t i = 0; i<endpointArraySize; i++) 
{
	printf("URL of endpoint %i is %.*s\n", (int)i,
		(int)endpointArray[i].endpointUrl.length,
		endpointArray[i].endpointUrl.data);
}
UA_Array_delete(endpointArray, endpointArraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);

2.2.3 读取节点值

UA_StatusCode status;	//在connect时就创建了
UA_Variant value;
UA_Variant_init(&value);
status = UA_Client_readValueAttribute(client, UA_NODEID_STRING(1, "the.answer"), &value);
if(status == UA_STATUSCODE_GOOD && UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_INT32]))
{
    printf("the value is: %i\n", *(UA_Int32*)value.data);
}
UA_Variant value; 
UA_NodeId nodeId = UA_NODEID_NUMERIC(0, 27647);  
status = UA_Client_readValueAttribute(client, nodeId, &value);

if(status == UA_STATUSCODE_GOOD && UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_DOUBLE])) 
{    
    UA_String *logic_value = (UA_String*) value.data;
    printf("logic value is: %s\n", logic_value->data);
}     
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\n",
                dts.day, dts.month, dts.year, dts.hour, dts.min, dts.sec, dts.milliSec);
}

2.2.4 写

static bool WriteValue(UA_Client *client,char* nodeIdStr,void *value,UINT type)
{
    boolean result = true;
    
    UA_Variant *myVariant = UA_Variant_new();
    UA_NodeId nodeId = UA_NODEID_STRING(2, nodeIdStr);
    //value对应type类型的值    
    UA_StatusCode code = UA_Variant_setScalarCopy(myVariant, value, &UA_TYPES[type]);
    if(code != UA_STATUSCODE_GOOD)
    {
        result = false;
        UA_Variant_delete(myVariant);
        return result;
    }
    code=UA_Client_writeValueAttribute(client, nodeId, myVariant);
    if (code != UA_STATUSCODE_GOOD)
    {
        result = false;
        UA_Variant_delete(myVariant);
        return result;
    }
    UA_Variant_delete(myVariant);
    return result;
}

2.2.5 监控节点

open62541 client批量监测

2.2.6 浏览节点

open62541 浏览服务器中节点 博客中有其他相关文章

官方浏览节点

#include <open62541.h>
#include "common_amal.h"
#include <iostream>
#include <string>
#include <vector>

using namespace std;

void listTreeRecursive(UA_Client *client, UA_NodeId nodeId)
{
    size_t i, j;

    /* Browse some objects */
    UA_BrowseRequest bReq;
    UA_BrowseRequest_init(&bReq);
    bReq.requestedMaxReferencesPerNode = 0;
    bReq.nodesToBrowse = UA_BrowseDescription_new();
    bReq.nodesToBrowseSize = 1;

    UA_NodeId_copy(&nodeId, &bReq.nodesToBrowse[0].nodeId);
    bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; /* return everything */

    UA_BrowseResponse bResp = UA_Client_Service_browse(client, bReq);
    UA_BrowseNextRequest bNextReq;
    UA_BrowseNextRequest_init(&bNextReq);
    // normally is set to 0, to get all the nodes, but we want to test browse next
    bNextReq.releaseContinuationPoints = UA_FALSE;
    bNextReq.continuationPoints = &bResp.results[0].continuationPoint;
    bNextReq.continuationPointsSize = 1;

    UA_BrowseNextResponse bNextResp = UA_Client_Service_browseNext(client, bNextReq);


    for ( i = 0; i < bResp.resultsSize; i++)
    {
        for ( j = 0; j < bResp.results[i].referencesSize; j++)
        {
            UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
            if ((ref->nodeClass == UA_NODECLASS_OBJECT || ref->nodeClass == UA_NODECLASS_VARIABLE||ref->nodeClass == UA_NODECLASS_METHOD)) {

                if (ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_NUMERIC) {
                    printf("%-9d %-16d %-16.*s %-16.*s\n", ref->nodeId.nodeId.namespaceIndex,
                           ref->nodeId.nodeId.identifier.numeric, (int) ref->browseName.name.length,
                           ref->browseName.name.data, (int) ref->displayName.text.length,
                           ref->displayName.text.data);

                    listTreeRecursive(client, UA_NODEID_NUMERIC(ref->nodeId.nodeId.namespaceIndex,
                                                                ref->nodeId.nodeId.identifier.numeric));

                } else if (ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_STRING) {
                    printf("%-9d %-16.*s %-16.*s %-16.*s\n", ref->nodeId.nodeId.namespaceIndex,
                           (int) ref->nodeId.nodeId.identifier.string.length,
                           ref->nodeId.nodeId.identifier.string.data,
                           (int) ref->browseName.name.length, ref->browseName.name.data,
                           (int) ref->displayName.text.length, ref->displayName.text.data);

                    listTreeRecursive(client, UA_NODEID_STRING(ref->nodeId.nodeId.namespaceIndex,
                                                               (char *) ref->nodeId.nodeId.identifier.string.data));
                }
            }
            /* TODO: distinguish further types */
        }
    }

    for ( i = 0; i < bNextResp.resultsSize; i++)
    {
        for ( j = 0; j < bNextResp.results[i].referencesSize; j++)
        {
            UA_ReferenceDescription *ref = &(bNextResp.results[i].references[j]);
            if ((ref->nodeClass == UA_NODECLASS_OBJECT || ref->nodeClass == UA_NODECLASS_VARIABLE||ref->nodeClass == UA_NODECLASS_METHOD)) {

                if (ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_NUMERIC) {
                    printf("%-9d %-16d %-16.*s %-16.*s\n", ref->nodeId.nodeId.namespaceIndex,
                           ref->nodeId.nodeId.identifier.numeric, (int) ref->browseName.name.length,
                           ref->browseName.name.data, (int) ref->displayName.text.length,
                           ref->displayName.text.data);

                    listTreeRecursive(client, UA_NODEID_NUMERIC(ref->nodeId.nodeId.namespaceIndex,
                                                                ref->nodeId.nodeId.identifier.numeric));

                } else if (ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_STRING) {
                    printf("%-9d %-16.*s %-16.*s %-16.*s\n", ref->nodeId.nodeId.namespaceIndex,
                           (int) ref->nodeId.nodeId.identifier.string.length,
                           ref->nodeId.nodeId.identifier.string.data,
                           (int) ref->browseName.name.length, ref->browseName.name.data,
                           (int) ref->displayName.text.length, ref->displayName.text.data);

                    listTreeRecursive(client, UA_NODEID_STRING(ref->nodeId.nodeId.namespaceIndex,
                                                               (char *) ref->nodeId.nodeId.identifier.string.data));
                }
            }
            /* TODO: distinguish further types */
        }
    }
    UA_BrowseRequest_deleteMembers(&bReq);
    UA_BrowseResponse_deleteMembers(&bResp);
}

int main(int argc, char **argv)
{
    UA_Client *client = UA_Client_new();
    UA_ClientConfig_setDefault(UA_Client_getConfig(client));
    UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://192.168.3.50:4840");

    if(retval != UA_STATUSCODE_GOOD) {
        printf("Unable to connect!");
        UA_Client_delete(client);
        return (int)retval;
    }

    printf("Browsing nodes in objects folder:\n");
    printf("%-9s %-16s %-16s %-16s\n", "NAMESPACE", "NODEID", "BROWSE NAME", "DISPLAY NAME");
    //listTreeRecursive(client, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER));
    listTreeRecursive(client, UA_NODEID_STRING_ALLOC(3, "PLC"));


    printf("Program End\n");

    /* Clean up */
    UA_Client_delete(client); /* Disconnects the client internally */
    return UA_STATUSCODE_GOOD;
}

Log 输出:

/mnt/c/Users/eirik/Documents/RocketFarmAS/OPCUATest/browseTest
[2019-09-05 13:27:29.668 (UTC+0200)] info/client	Connecting to endpoint opc.tcp://192.168.3.50:4840
[2019-09-05 13:27:29.668 (UTC+0200)] info/client	SecurityPolicy not specified -> use default #None
[2019-09-05 13:27:29.668 (UTC+0200)] warn/securitypolicy	Security policy None is used to create SecureChannel. Accepting all certificates
[2019-09-05 13:27:29.671 (UTC+0200)] info/client	TCP connection established
[2019-09-05 13:27:29.682 (UTC+0200)] info/client	Opened SecureChannel with SecurityPolicy http://opcfoundation.org/UA/SecurityPolicy#None
[2019-09-05 13:27:29.682 (UTC+0200)] info/client	Endpoint and UserTokenPolicy unconfigured, perform GetEndpoints
[2019-09-05 13:27:29.696 (UTC+0200)] info/client	Found 7 endpoints
[2019-09-05 13:27:29.696 (UTC+0200)] info/client	Endpoint 0 has 2 user token policies
[2019-09-05 13:27:29.696 (UTC+0200)] info/client	Selected Endpoint opc.tcp://192.168.3.50:4840 with SecurityMode None and SecurityPolicy http://opcfoundation.org/UA/SecurityPolicy#None
[2019-09-05 13:27:29.696 (UTC+0200)] info/client	Selected UserTokenPolicy Anonymous with UserTokenType Anonymous and SecurityPolicy http://opcfoundation.org/UA/SecurityPolicy#None
Browsing nodes in objects folder:
NAMESPACE NODEID           BROWSE NAME      DISPLAY NAME    
3         Counters         Counters         Counters        
3         5204             Icon             Icon            
3         "Counter"        Counter          Counter         
3         DataBlocksGlobal DataBlocksGlobal DataBlocksGlobal
3         5202             Icon             Icon            
3         "dbOpc"          dbOpc            dbOpc           
3         "dbOpc"."Sine01" Sine01           Sine01          
3         "dbOpc"."Sine02" Sine02           Sine02          
3         "dbOpc"."Sine03" Sine03           Sine03          
3         "dbOpc"."Sine04" Sine04           Sine04          
3         "dbOpc"."Random01" Random01         Random01        
3         "dbOpc"."Random02" Random02         Random02        
3         "dbOpc"."Random03" Random03         Random03        
3         "dbOpc"."Random04" Random04         Random04        
3         "dbOpc"."Sine01Ah" Sine01Ah         Sine01Ah        
3         "dbOpc"."Sine02Ah" Sine02Ah         Sine02Ah        
3         "dbOpc"."Sine03Al" Sine03Al         Sine03Al        
3         "dbOpc"."Sine04Al" Sine04Al         Sine04Al        
3         "dbOpc"."Random01Ah" Random01Ah       Random01Ah      
3         "dbOpc"."Random02Ah" Random02Ah       Random02Ah      
3         "dbOpc"."Random03Ah" Random03Ah       Random03Ah      
3         "dbOpc"."Random04Ah" Random04Ah       Random04Ah      
3         DataBlocksInstance DataBlocksInstance DataBlocksInstance
3         5203             Icon             Icon            
3         "idbSineAa01"    idbSineAa01      idbSineAa01     
3         "idbSineAa01".Inputs Inputs           Inputs          
3         "idbSineAa01"."XL" XL               XL              
3         "idbSineAa02"    idbSineAa02      idbSineAa02     
3         "idbSineAa02".Inputs Inputs           Inputs          
3         "idbSineAa02"."XL" XL               XL              
3         "idbSineAa04"    idbSineAa04      idbSineAa04     
3         "idbSineAa04".Inputs Inputs           Inputs          
3         "idbSineAa04"."XL" XL               XL              
3         "idbSineAa03"    idbSineAa03      idbSineAa03     
3         "idbSineAa03".Inputs Inputs           Inputs          
3         "idbSineAa03"."XL" XL               XL              
3         "idbPrngAa01"    idbPrngAa01      idbPrngAa01     
3         "idbPrngAa01".Inputs Inputs           Inputs          
3         "idbPrngAa01"."XL" XL               XL              
3         "idbPrngAa02"    idbPrngAa02      idbPrngAa02     
3         "idbPrngAa02".Inputs Inputs           Inputs          
3         "idbPrngAa02"."XL" XL               XL              
3         "idbPrngAa04"    idbPrngAa04      idbPrngAa04     
3         "idbPrngAa04".Inputs Inputs           Inputs          
3         "idbPrngAa04"."XL" XL               XL              
3         "idbPrngAa03"    idbPrngAa03      idbPrngAa03     
3         "idbPrngAa03".Inputs Inputs           Inputs          
3         "idbPrngAa03"."XL" XL               XL              
3         DeviceManual     DeviceManual     DeviceManual    
3         DeviceRevision   DeviceRevision   DeviceRevision  
3         EngineeringRevision EngineeringRevision EngineeringRevision
3         HardwareRevision HardwareRevision HardwareRevision
3         Inputs           Inputs           Inputs          
3         5204             Icon             Icon            
3         "InputBit"       InputBit         InputBit        
3         Manufacturer     Manufacturer     Manufacturer    
3         Memory           Memory           Memory          
3         5204             Icon             Icon            
3         "MemoryBit"      MemoryBit        MemoryBit       
3         "MemoryFloat"    MemoryFloat      MemoryFloat     
3         Model            Model            Model           
3         OperatingMode    OperatingMode    OperatingMode   
3         OrderNumber      OrderNumber      OrderNumber     
3         Outputs          Outputs          Outputs         
3         5204             Icon             Icon            
3         "OutputBit"      OutputBit        OutputBit       
3         RevisionCounter  RevisionCounter  RevisionCounter 
3         SerialNumber     SerialNumber     SerialNumber    
3         SoftwareRevision SoftwareRevision SoftwareRevision
3         Timers           Timers           Timers          
3         5204             Icon             Icon            
3         "Timer"          Timer            Timer           
3         5201             Icon             Icon            
Program End

Process finished with exit code 0

2.2.7 事件

open62541 事件(复杂的世界)

2.2.8 Subscription(订阅)

学习open62541 — [10] Client监测变量值
open62541 client批量监测
1、主要函数:
(1)变化处理回调函数:

static void handler_DataChanged(UA_Client *client, UA_UInt32 subId,
								void *subContext, UA_UInt32 monId,
								void *monContext, UA_DataValue *value)

订阅处理回调参数中:monContext为空,只能根据subId、monId来判断是哪个发来的。
其中double发回来的value,是string格式的UA_Variant。
夏令时(DST)

void dataChangeNotificationCallback(UA_Server *server, UA_UInt32 monitoredItemId,
                               void *monitoredItemContext, const UA_NodeId *nodeId,
                               void *nodeContext, UA_UInt32 attributeId,
                               const UA_DataValue *value) 
{
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Received Notification");    
    UA_NodeId * targetNodeId = (UA_NodeId*)monitoredItemContext;
    if (monitoredItemId == monid && UA_NodeId_equal(nodeId, targetNodeId))
    {
        UA_Int32 currentValue = *(UA_Int32*)(value->value.data);
        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Current Value: %d\n", currentValue);
        
    }
}

(2)单个订阅

UA_MonitoredItemCreateResult
UA_Client_MonitoredItems_createDataChange(UA_Client *client, UA_UInt32 subscriptionId,
                                          UA_TimestampsToReturn timestampsToReturn,
                                          const UA_MonitoredItemCreateRequest item, void *context,
                                          UA_Client_DataChangeNotificationCallback callback,
                                          UA_Client_DeleteMonitoredItemCallback deleteCallback) 

(3)多个订阅

UA_CreateMonitoredItemsResponse
UA_Client_MonitoredItems_createDataChanges(UA_Client *client,
                                           const UA_CreateMonitoredItemsRequest request,
                                           void **contexts,
                                           UA_Client_DataChangeNotificationCallback *callbacks,
                                           UA_Client_DeleteMonitoredItemCallback 	*deleteCallbacks)

2、添加订阅
nodeId注意不能是局部变量,否则处理函数中收不到。这里用的是static

void main()
{
	static UA_NodeId targetNodeId = UA_NODEID_STRING(1, "the.answer");
	addMonitoredItemToVariable(client, &targetNodeId);
}
void addMonitoredItemToVariable(UA_Client *client, UA_NodeId *target)
{

	UA_MonitoredItemCreateResult monResponse =
        UA_Client_MonitoredItems_createDataChange(client, response.subscriptionId,
                                                  UA_TIMESTAMPSTORETURN_BOTH,
                                                  monRequest, (void*)target, 
                                                  handler_DataChanged, NULL);
}

以下使用动态创建监控nodeId,会造成内存泄漏:

UA_NodeId * pContext = (UA_NodeId *)UA_malloc(sizeof(UA_NodeId));
UA_NodeId_copy(&TargetNodeId, pContext);

用智能指针传给回调的:
学习open62541 — [76] 使用智能指针处理内存释放问题
用智能指针来存放context内存

std::shared_ptr<UA_NodeId> 
addMonitoredItemToVariable(UA_Server *server, UA_NodeId TargetNodeId, UA_UInt32& monitoredItemId) 
{   
    std::shared_ptr<UA_NodeId> spContext = std::make_shared<UA_NodeId>();
    UA_NodeId_copy(&TargetNodeId, spContext.get());
    result = UA_Server_createDataChangeMonitoredItem(server, UA_TIMESTAMPSTORETURN_BOTH,
                                                     monRequest, (void*)spContext.get(), 
                                                     dataChangeNotificationCallback);    
    
    return spContext;
}

main() 创建一个holdSP来承接addMonitoredItemToVariable()的返回值,有了这个holdSP,那么智能指针指向的内存就不会被释放,程序结束时就会自动释放。(可用类成员变量来接)

int main(void) 
{     
    std::shared_ptr<UA_NodeId> spContext;
    monid = addMonitoredItemToVariable(server, targetNodeId, spContext);
    
}

2.2.9 Call(调用方法)

open62541Server中添加方法并由Client调用

2.2.10 Timer(定时)

学习open62541 — (24) 定时执行任务

typedef void (*UA_ClientCallback)(UA_Client *client, void *data);		//回调
UA_Server_addRepeatedCallback()				//添加循环定时任务,并获取其callbackId
UA_Server_addTimedCallback()  			//添加oneshot定时任务
UA_Client_changeRepeatedCallbackInterval() 	//更改循环周期
UA_Client_removeCallback()					//移除定时
//特别注意:
//UA_Server_addRepeatedCallback()里的时间参数类型是UA_Double,是个相对时间
//UA_Server_addTimedCallback()里的时间参数类型是UA_DateTime,是个绝对时间。

2.2.11 安全策略

学习OPEN62541 — [37] 与KEPSERVEREX进行简单通信
学习open62541 — [33] 加密(使用OpenSSL)
基于Windows手动编译openssl和直接安装openssl
Openssl库编译篇(Windows平台)(有编译好的)

UA_SignedSoftwareCertificate_new()
UA_SignedSoftwareCertificate_copy()
signActivateSessionRequest()
activateSessionAsync()
connectIterate()
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐