基于open62541库的OPC UA协议节点信息查询及多节点数值读写案例实践

目录

一、OPC UA协议简介

二、open62541库简介

三、 opcua协议的多点查询、多点读写案例服务端opcua_server

        3.1 opcua_server工程目录

       3.2 程序源码

        3.3 工程组织文件

        3.4 编译及启动

 四、opcua协议的多点查询、多点读写案例客户端opcua_client

        4.1 opcua_client工程目录

        4.2 程序源码

        4.3 工程组织文件

      4.4 编译及测试


一、OPC UA协议简介

        OPC UA(Open Platform Communications Unified Architecture)是一个通用的、安全的、跨平台的通信协议,用于在工业互联网、物联网(IoT)环境中传递数据和信息。它基于工业领域通用的OPC(Open Platform Communications)协议,并针对现代设备互联的需要进行了扩展和优化。

        OPC UA协议支持多种网络协议,包括TCP/IP、UDP、WebSockets等。该协议提供了一种标准化的、可扩展的通讯机制,用于设备之间的通信和数据交换,广泛应用于工业自动化、智能制造等领域。

        OPC UA协议是一种功能强大、安全可靠、可扩展性好的通用通信协议,被广泛应用于工业自动化、物联网设备接入等领域,OPC UA协议具有以下特点:

1. 安全性高:支持多种加密算法,可以通过数字证书进行身份验证、权限管理等。

2. 跨平台:支持不同操作系统上的应用程序和设备之间的通信。

3. 扩展性强:支持自定义协议数据单元类型和协议对象。

4. 数据模型灵活:可以适应多种应用场景,例如数据传输、事件通知、访问控制等。

5. 可以承载多种应用协议:比如物联网组网协议、智能制造协议等。

二、open62541库简介

        open62541是一款基于C语言实现的OPC UA通信库。该库实现了OPC UA标准的客户端和服务器端,并支持各种操作系统和编译器。该OPC UA通信库功能丰富、易于移植、可扩展性好、性能优越,被广泛应用于工业自动化、智能制造、物联网等领域,是开发OPC UA应用的一个重要选择,其特点如下:

1. 开源免费:使用MIT许可协议,可用于商业和非商业项目。

2. 高度可扩展:支持插件式开发,可以方便地增加协议扩展、认证机制、加密协议等功能。

3. 易于移植:实现了跨平台的特性,可以在不同操作系统和硬件平台上使用。

4. 高性能:采用了基于事件的架构和异步I/O机制,具有非常好的性能和伸缩性。

5. 支持多线程:使用线程安全的方法进行函数调用,可以在多线程环境下稳定工作。

6. 可嵌入性强:支持将库文件嵌入到应用程序中,方便整合使用。

关于open62541库的源码编译、安装、环境部署等请参考本专栏的博文:

OPC UA/DA协议库open62541的源码编译及案例测试_open62541源码_py_free-物联智能的博客-CSDN博客

三、 opcua协议的多点查询、多点读写案例服务端opcua_server

        3.1 opcua_server工程目录

        创建opcua_server工程,该opc服务添加一些节点,并为这些节点设置初始值,然后等待opc客户端链接及操作。

        工程目录如下:

opcua_server
    bin
    build_linux
    build_mingw
    src
        main.c
    CMakeLists.txt

       3.2 程序源码

        main.c文件内容如下:

#include <signal.h>
#ifdef __linux__
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#else
#include "open62541.h"
#endif

// 用于记录SENSOR_1节点值的变量
UA_Int32 sensor1Value = 0;
UA_StatusCode add_sensor_variable(UA_Server *server,char* nodeid_desc,int val) {     
    /* Add a variable node */
    /* 1) Define the node attributes */
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    attr.displayName = UA_LOCALIZEDTEXT("en-US", nodeid_desc);              //属性展示信息
    attr.description = UA_LOCALIZEDTEXT("en-US", nodeid_desc);              //属性描述信息
    // attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
    UA_Int32 myInteger = val;
    UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);   //设置属性值

    /* 2) Define where the node shall be added with which browsename */
    UA_NodeId newNodeId = UA_NODEID_STRING(1, nodeid_desc);                     //节点=命名空间+节点名称,客户端端标识类型是UA_NODEIDTYPE_STRING
    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);      //父节点,客户端端标识类型是UA_NODEIDTYPE_NUMERIC
    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); //关联节点
    UA_NodeId variableType = UA_NODEID_NULL; /* take the default variable type *///变量类型
    UA_QualifiedName browseName = UA_QUALIFIEDNAME(1, nodeid_desc);             //节点浏览名

    /* 3) Add the node */
    return UA_Server_addVariableNode(server, newNodeId, parentNodeId, parentReferenceNodeId,
                              browseName, variableType, attr, NULL, NULL);
}

UA_Boolean running = true;
void signalHandler(int sig) {
    running = false;
}

int main() {
    signal(SIGINT, signalHandler); /* catch ctrl-c */

    UA_Server *server = UA_Server_new();
    UA_ServerConfig_setDefault(UA_Server_getConfig(server));

    // 添加节点
    add_sensor_variable(server,"SENSOR_1",41);
    add_sensor_variable(server,"SENSOR_2",42);

    // 启动服务端
    UA_StatusCode retval = UA_Server_run(server, &running);
    UA_Server_delete(server);
    return (int)retval;
}

        上述代码说明:

        1)头文件的引用win和linux情况不同:在win下无论是采用mingw-gcc还是采用Visual Studio编译工具,都会先生成open62541.h/open62541.c文件,然后在编译成库,因此open62541.h相当于一个总集API接口头文件,调用该文件及相当于调用了一系列头文件;在linux下gcc编译源码时,是不会生成中间文件的,而是直接一步到位生成库,头文件依然采用源码的头文件,因此需要单独引用涉及的头文件。

        2)注意到添加节点时,节点编号是有命名空间+节点名称组成的,需要指定节点的id和父节点ID,这些信息是要告知客户端的,这样客户端才能访问

    UA_NodeId newNodeId = UA_NODEID_STRING(1, nodeid_desc);                     //节点=命名空间+节点名称,客户端端标识类型是UA_NODEIDTYPE_STRING
    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);      //父节点,客户端端标识类型是UA_NODEIDTYPE_NUMERIC

        3)节点的数值变量可以指定名称、描述、访问权限、数值类型等信息,如果客户端需要写入,需要指定UA_ACCESSLEVELMASK_WRITE权限的,如果客户端需要针对数值类型做判断也是需要知道其类型的,如UA_TYPES_INT32。

    attr.displayName = UA_LOCALIZEDTEXT("en-US", nodeid_desc);              //属性展示信息
    attr.description = UA_LOCALIZEDTEXT("en-US", nodeid_desc);              //属性描述信息
    // attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
    UA_Int32 myInteger = val;
    UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);   //设置属性值

        在本程序中,节点的命名、描述、浏览 字段都指定了一样的字符串,实际项目中,这些是可以不一样的。

       4)程序设置了服务端的默认配置,其默认侦听端口是4840:

    UA_Server *server = UA_Server_new();
    UA_ServerConfig_setDefault(UA_Server_getConfig(server));

 

        3.3 工程组织文件

        CMakeLists.txt工程文件如下,

# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (opcua_server)
#
if(WIN32)
    message(STATUS "windows compiling...")
    add_definitions(-D_PLATFORM_IS_WINDOWS_)
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
    set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
    set(WIN_OS true)
else(WIN32)
    message(STATUS "linux compiling...")
    add_definitions( -D_PLATFORM_IS_LINUX_)
    add_definitions("-Wno-invalid-source-encoding")
	  # add_definitions("-O2")
    set(UNIX_OS true)
    set(_DEBUG true)
    
endif(WIN32)

set(UA_IPV6 0)
set(org_dir ${PROJECT_SOURCE_DIR}/../..)
#set(build_dir ${PROJECT_SOURCE_DIR}/../../build_mingw)
#set(build_dir ${PROJECT_SOURCE_DIR}/../../build_vc)
set(build_dir ${PROJECT_SOURCE_DIR}/../../build_linux)
#
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
 
# 指定源文件的目录,并将名称保存到变量
SET(source_h
    #
	#${build_dir}/open62541.h
  )
  
#头文件目录
include_directories(
	${build_dir}
	${org_dir}/include
	${build_dir}/src_generated
	${org_dir}/arch
	${org_dir}/deps
	${org_dir}/plugins/include
)
 
if (${UNIX_OS})

SET(source_cpp
    #
	${PROJECT_SOURCE_DIR}/src/main.c
  )
  
add_definitions(
	"-DUA_ARCHITECTURE_POSIX"
    "-std=c99"
  )
  set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0")
 
link_directories()
# 指定生成目标
add_executable(opcua_server ${source_h} ${source_cpp} )
#link
target_link_libraries(opcua_server 
  -pthread
  ${build_dir}/bin/libopen62541.a 
)
 
endif(${UNIX_OS})
 
if (${WIN_OS})

SET(source_cpp
    #
	${PROJECT_SOURCE_DIR}/src/main.c
	${build_dir}/open62541.c
  )
 
add_definitions(
  "-DUA_ARCHITECTURE_WIN32"
)
 
link_directories(
	"${build_dir}/bin"
)
 
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
 
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin)
# 指定生成目标
add_executable(opcua_serverd ${source_h} ${source_cpp} )

#link
target_link_libraries(opcua_serverd 
  ws2_32.lib wsock32.lib Iphlpapi.lib
  ${build_dir}/bin/libopen62541.a #mingw
  #${build_dir}/bin/Debug/open62541d.lib  #vc
)

else(CMAKE_BUILD_TYPE)
 
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin)
# 指定生成目标
add_executable(opcua_server ${source_h} ${source_cpp})

#link
target_link_libraries(opcua_server 
  ws2_32.lib wsock32.lib Iphlpapi.lib
  ${build_dir}/bin/libopen62541.a #mingw
  #${build_dir}/bin/Release/open62541.lib  #vc
)

endif (CMAKE_BUILD_TYPE)

endif(${WIN_OS})

        工程文件配置了vs mingw-gcc linux-gcc的支持,本文采用的是linux-gcc编译opcua_server工程,因此

set(build_dir ${PROJECT_SOURCE_DIR}/../../build_linux)

        另外需要注意的是linux需要pthread库支持,win下需要 ws2_32.lib wsock32.lib Iphlpapi.lib库的支持。

        3.4 编译及启动

        本文的编译环境是ubuntu20.4-server-64bit的虚拟机环境,安装了cmake gcc 并编译了open62541库及安装:

         进入工程目录下的build_linux:

cmake ..
make -j4

         然后启动编译好的程序:

../bin/opcua_server

 四、opcua协议的多点查询、多点读写案例客户端opcua_client

        4.1 opcua_client工程目录

        创建opcua_client工程,该opc程序链接服务端,然后读取节点数据、写入节点数据,查询节点信息,并打印输出这些信息。

opcua_client
    bin
    build_linux
    build_mingw
    build_vc
    src
        main.c
    CMakeLists.txt

        4.2 程序源码

        main.c文件内容,需要注意几点:

1)服务端节点是UA_NODEID_STRING的,客户端创建节点也是该类型,并给出一致的命名空间及节点名称,例如1,SENSOR_1;

2) 在解析数值时,根据数值类型判断,如果知道服务端指定该节点的数值类型,可以快速过滤掉其他节点,例如指定UA_TYPES_INT32;

3)在数据写入时,设定的写入变量的数值类型必须和服务端节点的数值类型一致,服务无法成功写入;

4)查询节点信息时,要查询在服务端的添加节点,就是需要将指定的父节点来查找,会更快的获得节点信息,例如服务端添加节点时,是在父节点UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER)下添加的。

#ifdef __linux__
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#else
#include "open62541.h"
#endif

void read_node(UA_Client *client)
{
    // 读取和解析单个节点的值
    UA_Variant value;
    UA_Client_readValueAttribute(client, UA_NODEID_STRING(1, "SENSOR_1"), &value);
    if(value.type == &UA_TYPES[UA_TYPES_INT32]) {
        UA_Int32 intValue = *(UA_Int32 *)value.data;
        printf("Sensor 1 Value: %i\n", intValue);
    } else {
        printf("Unsupported Data Type\n");
    }
    // UA_Variant_deleteMembers(&value);
}

void read_nodes(UA_Client *client)
{
 // 读取和解析多个节点的值
    UA_ReadValueId valueIds[2];
    UA_ReadValueId_init(&valueIds[0]);
    UA_ReadValueId_init(&valueIds[1]);
    //
    valueIds[0].nodeId = UA_NODEID_STRING(1, "SENSOR_1");
    valueIds[0].attributeId = UA_ATTRIBUTEID_VALUE;
    //
    valueIds[1].nodeId = UA_NODEID_STRING(1, "SENSOR_2");
    valueIds[1].attributeId = UA_ATTRIBUTEID_VALUE;


    UA_ReadRequest request;
    UA_ReadRequest_init(&request);
    request.nodesToReadSize = 2;
    request.nodesToRead = valueIds;
    UA_String out = UA_STRING_NULL;
    // UA_print(&request, &UA_TYPES[UA_TYPES_READREQUEST], &out);
    // printf("%.*s\n", (int)out.length, out.data);
    // UA_String_clear(&out);
    //
    UA_ReadResponse response;
    UA_ReadResponse_init(&response);
    // UA_print(&response, &UA_TYPES[UA_TYPES_READRESPONSE], &out);
    // printf("%.*s\n", (int)out.length, out.data);
    // UA_String_clear(&out);

    response = UA_Client_Service_read(client, request);
    //
    // UA_print(&response, &UA_TYPES[UA_TYPES_READRESPONSE], &out);
    // printf("%.*s\n", (int)out.length, out.data);
    // UA_String_clear(&out);

    if(UA_STATUSCODE_GOOD == response.responseHeader.serviceResult) {
        for(size_t i = 0; i < response.resultsSize; ++i) {
            UA_Variant value = response.results[i].value;
            if(value.type == &UA_TYPES[UA_TYPES_INT32]) {
                UA_Int32 intValue = *(UA_Int32 *)value.data;
                printf("Sensor %i Value: %i\n", i+1, intValue);
            } else {
                printf("Unsupported Data Type\n");
            }
            // UA_Variant_deleteMembers(&value);
        }
    }
    //
    printf("UA_ReadResponse_clear call\n");
    UA_ReadResponse_clear(&response);
};

void write_nodes(UA_Client *client)
{
 // 配置及写入多个节点的值
    UA_WriteValue valueIds[2];
    UA_WriteValue_init(&valueIds[0]);
    UA_WriteValue_init(&valueIds[1]);
    //
    valueIds[0].nodeId = UA_NODEID_STRING(1, "SENSOR_1");
    valueIds[0].attributeId = UA_ATTRIBUTEID_VALUE;
    //
    valueIds[1].nodeId = UA_NODEID_STRING(1, "SENSOR_2");
    valueIds[1].attributeId = UA_ATTRIBUTEID_VALUE; 

    UA_Variant infoVar;
    UA_UInt32 uint1Value = 101;
	UA_Variant_init(&infoVar);
	UA_Variant_setScalar(&infoVar, &uint1Value, &UA_TYPES[UA_TYPES_INT32]);
	valueIds[0].value.value = infoVar;
	valueIds[0].value.hasValue = true;

    UA_UInt32 uint2Value = 102;
	UA_Variant_init(&infoVar);
	UA_Variant_setScalar(&infoVar, &uint2Value, &UA_TYPES[UA_TYPES_INT32]);
	valueIds[1].value.value = infoVar;
	valueIds[1].value.hasValue = true;

    printf("WriteReques init\n");
    UA_WriteRequest wReq;
	UA_WriteRequest_init(&wReq);
	wReq.nodesToWrite = valueIds;
	wReq.nodesToWriteSize = 2;

    printf("WriteResponse return\n");
	UA_WriteResponse wResp = UA_Client_Service_write(client, wReq);
    printf("serviceResult analysis\n");
	UA_StatusCode retval = wResp.responseHeader.serviceResult;
	if (retval == UA_STATUSCODE_GOOD) {
		if (wResp.resultsSize == 1)
			retval = wResp.results[0];
		else
			retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
        printf("serviceResult analysis:%0x8d\n",retval);
	}
    printf("UA_WriteResponse_clear call\n");
	UA_WriteResponse_clear(&wResp);
}


//节点查看
void Browse_nodes(UA_Client *client, UA_NodeId nodeId)
{
    /* Browse some objects */
    printf("Browsing nodes in objects folder:\n");
    UA_BrowseRequest bReq;
    UA_BrowseRequest_init(&bReq);
    bReq.requestedMaxReferencesPerNode = 0;//限制查到的最大节点数,0 不限制
    bReq.nodesToBrowse = UA_BrowseDescription_new();
    bReq.nodesToBrowseSize = 1;//需要浏览的节点个数,这里只寻找nodeId节点下的节点所以为1
    /*UA_BROWSEDIRECTION_FORWARD表示向下查找(即查找添加在节点下的节点),
    UA_BROWSEDIRECTION_INVERSE表示向上查找(即查找节点的父节点),
    UA_BROWSEDIRECTION_BOTH表示上下都进行查找*/
	bReq.nodesToBrowse[0].browseDirection = UA_BROWSEDIRECTION_FORWARD;
	bReq.nodesToBrowse[0].includeSubtypes = UA_TRUE;//是否包含subtypes
    bReq.nodesToBrowse[0].nodeId = nodeId;
    
	bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; //返回浏览到的节点包含的信息,名称、显示名称......,UA_BROWSERESULTMASK_ALL表示返回所有信息
    
    UA_BrowseResponse bResp = UA_Client_Service_browse(client, bReq);
    //输出浏览到的每个节点信息
    printf("%-9s %-16s %-16s %-16s\n", "NAMESPACE", "NODEID", "BROWSE NAME", "DISPLAY NAME");
    for(size_t i = 0; i < bResp.resultsSize; ++i) {
        for(size_t j = 0; j < bResp.results[i].referencesSize; ++j) {
            UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
            if(ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_NUMERIC) {
                printf("%-9u %-16u %-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);
            } else if(ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_STRING) {
                printf("%-9u %-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);
            }
            /* TODO: distinguish further types */
        }
    }
    // UA_BrowseRequest_clear(&bReq);
    UA_BrowseResponse_clear(&bResp);
}

int main() {
    UA_Client *client = UA_Client_new();
    UA_ClientConfig_setDefault(UA_Client_getConfig(client));

    // 设置OPC UA服务器的URL
    UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://192.168.157.143:4840");
    if(retval != UA_STATUSCODE_GOOD) {
        UA_Client_delete(client);
        return retval;
    }
    //读取单个节点数值
    printf("read one node for one times!\n");
    read_node(client);

   //读取多个节点数值
   printf("read more node for one times!\n");
   read_nodes(client);
   //写入多个节点数值
   printf("write more node for one times!\n");
   write_nodes(client);
   //读取多个节点数值
   printf("read more node for one times again!\n");
   read_nodes(client);
    
    // 浏览节点
    printf("Browse_nodes call!\n");
    Browse_nodes(client,UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER));
    //
    printf("Browse_nodes call!\n");
    Browse_nodes(client,UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER));

    UA_Client_disconnect(client);
    UA_Client_delete(client);

    return 0;
}

        4.3 工程组织文件

        CMakeLists.txt

# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (opcua_client)
#
if(WIN32)
    message(STATUS "windows compiling...")
    add_definitions(-D_PLATFORM_IS_WINDOWS_)
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
    set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
    set(WIN_OS true)
else(WIN32)
    message(STATUS "linux compiling...")
    add_definitions( -D_PLATFORM_IS_LINUX_)
    add_definitions("-Wno-invalid-source-encoding")
	  # add_definitions("-O2")
    set(UNIX_OS true)
    set(_DEBUG true)
    
endif(WIN32)

set(UA_IPV6 0)
set(org_dir ${PROJECT_SOURCE_DIR}/../..)
set(build_dir ${PROJECT_SOURCE_DIR}/../../build_mingw)
#set(build_dir ${PROJECT_SOURCE_DIR}/../../build_vc)
#set(build_dir ${PROJECT_SOURCE_DIR}/../../build_linux)
#
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
 
# 指定源文件的目录,并将名称保存到变量
SET(source_h
    #
	#${build_dir}/open62541.h
  )
  
SET(source_cpp
    #
	${PROJECT_SOURCE_DIR}/src/main.c
	${build_dir}/open62541.c
  )
  
#头文件目录
include_directories(
	${build_dir}
	#${org_dir}/include
	#${build_dir}/src_generated
	#${org_dir}/arch
	#${org_dir}/deps
	#${org_dir}/plugins/include
)
 
if (${UNIX_OS})
 
add_definitions(
  "-DUA_ARCHITECTURE_POSIX"
  )
  set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0")
 
link_directories()
# 指定生成目标
add_executable(opcua_client ${source_h} ${source_cpp} )
#link
target_link_libraries(opcua_client 
  -lpthread
  ${build_dir}/bin/libopen62541.a 
)
 
endif(${UNIX_OS})
 
if (${WIN_OS})

add_definitions(
  "-DUA_ARCHITECTURE_WIN32"
)
 
link_directories(
	"${build_dir}/bin"
)
 
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
 
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin)
# 指定生成目标
add_executable(opcua_clientd ${source_h} ${source_cpp} )

#link
target_link_libraries(opcua_clientd 
  ws2_32.lib wsock32.lib Iphlpapi.lib
  ${build_dir}/bin/libopen62541.a #mingw
  #${build_dir}/bin/Debug/open62541d.lib  #vc
)

else(CMAKE_BUILD_TYPE)
 
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin)
# 指定生成目标
add_executable(opcua_client ${source_h} ${source_cpp})

#link
target_link_libraries(opcua_client 
  ws2_32.lib wsock32.lib Iphlpapi.lib
  ${build_dir}/bin/libopen62541.a #mingw
  #${build_dir}/bin/Release/open62541.lib  #vc
  #D:\\workForOrgCode\\open62541\\build_vc\\bin\\Release\\open62541.lib  #vc
)

endif (CMAKE_BUILD_TYPE)

endif(${WIN_OS})

      4.4 编译及测试

        本文客户端编译采用的是cmake+mingw-gcc编译,

cmake .. -G "MinGW Makefiles"

cmake --build . --config debug

         编译完成后启动测试程序,可以顺利单节点读取数据、多节点读取数据,并多节点同时写入数据及多节点读取数据验证写入正确性,另外可指定父节点查询其下各个子节点信息:

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/32979.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用 Jetpack Compose 构建 Spacer

欢迎阅读本篇关于如何使用 Jetpack Compose 构建 Spacer 的博客。Jetpack Compose 是 Google 的现代 UI 工具包&#xff0c;主要用于构建 Android 界面。其声明式的设计使得 UI 开发更加简洁、直观。 一、什么是 Spacer&#xff1f; 在 UI 设计中&#xff0c;我们通常需要在不…

CSS之平面转换

简介 作用&#xff1a;为元素添加动态效果&#xff0c;一般与过渡配合使用 概念&#xff1a;改变盒子在平面内的形态&#xff08;位移、旋转、缩放、倾斜&#xff09; 平面转换也叫 2D 转换&#xff0c;属性是 transform 平移 transform: translate(X轴移动距离, Y轴移动距…

【SpringCloud——Elasticsearch(下)】

一、数据聚合 聚合&#xff0c;可以实现对文档数据的统计、分析、运算。常见的聚合有三类&#xff1a; ①、桶聚合&#xff1a;用来对文档做分组 TermAggregation&#xff1a;按照文档字段值分组。Date Histogram&#xff1a;按照日期解题分组&#xff0c;例如一周为一组&am…

javaee sql注入问题

jsp页面 <% page language"java" contentType"text/html; charsetutf-8"pageEncoding"utf-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> &…

QT树的实现

理论 在Model/View结构中&#xff0c;数据模型为视图组件和代理组件提供存取数据的标准接口。在QT中&#xff0c;所有的数据模型类都从QAbstactItemModel继承而来&#xff0c;不管底层的数据结构是如何组织数据的&#xff0c;QAbstractItemModel的子类都以表格的层次结构表示数…

大数据需要一场硬件革命

光子盒研究院 计算领域的进步往往集中在软件上&#xff1a;华丽的应用程序和软件可以跟踪人和生态系统的健康状况、分析大数据&#xff0c;并在智力竞赛中击败人类冠军。与此同时&#xff0c;对支撑所有这些创新的硬件进行全面改革的努力相对来说&#xff0c;略显小众。 自2020…

Scala里的WordCount 案例

7.7.5 普通 WordCount 案例 package chapter07object TestWordCount__简单版 {def main(args: Array[String]): Unit {//单词计数&#xff1a;将集合中出现的相同单词计数&#xff0c;进行计数&#xff0c;取计数排名的前三的结果val stringList List("Hello Scala Hbas…

【数据可视化方案分享】电商数据分析

本文所分享的电商数据分析报表均来自奥威BI软件的电商数据分析方案&#xff01;该方案是一套包含数据采集、数据建模、数据分析报表的系统化、标准化数据分析方案&#xff0c;下载套用&#xff0c;立见效果&#xff01; 注意&#xff0c;奥威BI软件的电商数据分析方案分两类&a…

【基于Django框架的在线教育平台开发-01】账号登录及退出登录功能开发

文章目录 1 模型层开发2 视图层开发3 form表单验证4 配置urls.py5 模板层开发6 效果展示 1 模型层开发 用户数据表如下所示&#xff1a; FieldTypeExtraidintPrime Key & Auto Incrementpasswordvarchar(128)last_logindatetime(6)Allow Nullis_superusertinyint(1)usern…

mysql 常见锁类型

表锁 & 行锁 在 MySQL 中锁的种类有很多&#xff0c;但是最基本的还是表锁和行锁&#xff1a;表锁指的是对一整张表加锁&#xff0c;一般是 DDL 处理时使用&#xff0c;也可以自己在 SQL 中指定&#xff1b;而行锁指的是锁定某一行数据或某几行&#xff0c;或行和行之间的…

第二章 数据处理篇:transforms

教程参考&#xff1a; https://pytorch.org/tutorials/ https://github.com/TingsongYu/PyTorch_Tutorial https://github.com/yunjey/pytorch-tutorial 详细的transform的使用样例可以参考&#xff1a;ILLUSTRATION OF TRANSFORMS 文章目录 为什么要使用transformstransforms方…

二叉树题目:单值二叉树

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;单值二叉树 出处&#xff1a;965. 单值二叉树 难度 3 级 题目描述 要求 如果二叉树每个结点都具有相同的值&am…

SQL死锁

目录 前言&#xff1a; 分析&#xff1a; 死锁产生的原因&#xff1a; sql死锁 模拟&#xff1a; 解决办法&#xff1a; (本质&#xff1a;快速筛选或高效处理、以此减少锁冲突) ①大事务拆成小事务&#xff0c;尽可能缩小事务范围 大事务:将多个操作放在一个事务中执行…

【MOOC 测验】第5章 链路层

1、局域网的协议结构一般不包括&#xff08; &#xff09; A. 数据链路层B. 网络层C. 物理层D. 介质访问控制层 逻辑链路控制子层、介质访问控制子层、物理层 2、下列关于二维奇偶校验的说法&#xff0c;正确的是&#xff08; &#xff09; A. 可以检测和纠正双比特差错B…

【CVRP测评篇】 算法性能如何?来测!

我跨越了2100015秒的距离&#xff0c;为你送上更全面的算法性能评测。 目录 往期优质资源1 CVRP数据集2 实验准备2.1 计算机配置2.2 调参方法2.3 参数设定2.4 实验方法 3 实验结果3.1 最优解统计3.1.1各数据集上的算法性能对比3.1.2 求解结果汇总3.1.3小结一下3.1.4 还有话说 3…

【软考网络管理员】2023年软考网管初级常见知识考点(10)- 网际协议IP及IPV6,IPV4详解

涉及知识点 分类的IP地址&#xff0c;子网划分&#xff0c;CIDR和路由汇聚&#xff0c;IPV4数据报格式&#xff0c;IPV6协议&#xff0c;软考网络管理员常考知识点&#xff0c;软考网络管理员网络安全&#xff0c;网络管理员考点汇总。 原创于&#xff1a;CSDN博主-《拄杖盲学…

剑指 Offer 68 - II. 二叉树的最近公共祖先 / LeetCode 236. 二叉树的最近公共祖先(搜索与回溯)

题目&#xff1a; 链接&#xff1a;剑指 Offer 68 - II. 二叉树的最近公共祖先&#xff1b;LeetCode 236. 二叉树的最近公共祖先 难度&#xff1a;中等 上一题博客&#xff1a;剑指 Offer 68 - I. 二叉搜索树的最近公共祖先 / LeetCode 235. 二叉搜索树的最近公共祖先&#xf…

SSH远程直连Docker容器

文章目录 1. 下载docker镜像2. 安装ssh服务3. 本地局域网测试4. 安装cpolar5. 配置公网访问地址6. SSH公网远程连接测试7.固定连接公网地址8. SSH固定地址连接测试8. SSH固定地址连接测试 转载自cpolar极点云文章&#xff1a;SSH远程直连Docker容器 在某些特殊需求下,我们想ssh…

机器学习李宏毅学习笔记34

文章目录 前言一、Knowledge distillation二、Parameter quantization三、Architecture design四、Dynamic computation总结 前言 神经网络压缩&#xff08;二&#xff09;其他方法 一、Knowledge distillation 先train一个大的network叫做teacher network&#xff0c;小的ne…

Vue3:计算属性、监听器

computed 计算属性 计算属性是指 基于现有状态派生 (演变) 出新的状态&#xff0c;现有状态发生变化&#xff0c;派生状态重新计算。 computed 接收回调函数作为参数&#xff0c;基于回调函数中使用的响应式数据进行计算属性的创建&#xff0c;回调函数的返回值就是基于现有状态…