OPC UA使用
OPC UA(开放式产品通信统一架构,Open Platform Communications Unified Architecture)是一种通信协议和通信架构,用于实现工业自动化系统中设备之间的数据交换和通信。
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、环境搭建
为了快速、完整地体验从 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 成功的
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 数据类型
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)
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 监控节点
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 事件
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(定时)
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()
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)