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

news2024/11/13 23:07:35

目录

一、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

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

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

医院管理系统源码PACS超声科室源码DICOM影像工作站

一、医学影像系统&#xff08;PACS&#xff09;是一种应用于医院影像科室的系统&#xff0c;主要任务是将日常产生的各种医学影像&#xff08;如核磁、CT、超声、X光机、红外仪、显微仪等设备产生的图像&#xff09;通过各种接口&#xff08;模拟、DICOM、网络&#xff09;以数…

社区活动 | OpenVINO™ DevCon 中国系列工作坊第二期 | 使用 OpenVINO™ 加速生成式 AI...

生成式 AI 领域一直在快速发展&#xff0c;许多潜在应用随之而来&#xff0c;这些应用可以从根本上改变人机交互与协作的未来。这一最新进展的一个例子是 GPT 模型的发布&#xff0c;它具有解决复杂问题的能力&#xff0c;比如通过医学和法律考试这种类似于人类的能力。然而&am…

Android Studio实现推箱子小游戏

项目目录 一、项目概述二、开发环境三、详细设计四、运行演示五、项目总结 一、项目概述 推箱子是一款非常受欢迎的益智游戏&#xff0c;游戏的玩法简单&#xff0c;但是需要玩家具备一定的逻辑思维能力和空间感知能力&#xff0c;因此深受广大玩家的喜爱。在游戏中&#xff0…

正点原子F4HAL库串口中断再解读

七步走&#xff0c;参考usart.c文件 void HAL_UART_MspInit(UART_HandleTypeDef *huart) 这个函数进行了&#xff08;1&#xff09;、&#xff08;2&#xff09;、&#xff08;3&#xff09;、&#xff08;5&#xff09;中的使能中断 void uart_init(u32 bound)函数进行了&…

『手撕 Mybatis 源码』07 - Proxy 对象创建

Proxy 对象创建 问题 sqlSession.getMapper(UserMapper.class) 是如何生成的代理对象&#xff1f; Mapper 代理方式初始化完成后&#xff0c;下一步进行获取代理对象来执行 public class MybatisTest {/*** 问题2&#xff1a;sqlSession.getMapper(UserMapper.class); 是如…

EMC学习笔记(五)传输线模型及反射、串扰

1.概述 在高速数字电路PCB设计中&#xff0c;当布线长度大于20分之一波长或信号延时超过6分之一信号上升沿时&#xff0c;PCB布线可被视为传输线。传输线有两种类型:微带线和带状线。与EMC设计有关的传输线特性包括:特征阻抗、传输延迟、固有电容和固有电感。反射与串扰会影响…

2023年第1季社区Task挑战赛贡献者榜单

基于数字身份凭证的业务逻辑设计&#xff0c;贡献了发放数字身份凭证的参考实现&#xff1b;提供企业碳排放、慈善公益等智能合约库业务场景案例&#xff1b;体验最新发布的WeCross-BCOS3-Stub&#xff0c;跟社区核心开发者碰撞想法并给出自己的见解……这些精彩贡献展现出社区…

<C++项目>高并发内存池

项目介绍&#xff1a; 原型是goole的开源项目tcmalloc(全称:Thread-Caching Malloc),用于替代系统的内存分配相关的函数(malloc, free).知名度非常高。 项目要求知识储备和难度&#xff1a; 会用到C/C、数据结构(链表、哈希桶)、操作系统内存管理、单例模式、多线程、互斥锁等等…

设计模式—“行为变化”

在组件构建过程中,组件行为的变化经常导致组件本身剧烈的变化。“行为变化”模式将组件的行为和组件本身进行解耦,从而支持组件行为的变化,实现两者之间的松耦合。 典型模式有:Command、Visitor 一、Command 动机 在软件构建过程中,"行为请求者"与“行为实现…

看完这篇,立马看懂理想首款纯电MEGA

作者 | 马波编辑 | 德新 6月17日&#xff0c;理想召开了首届家庭科技日活动。 这场打着家庭幌子的科技发布会&#xff0c;信息密度高到有些超出预期。但是看完这场发布会&#xff0c;理想超级旗舰车型 W01&#xff0c;也就是同时在本场发布会公布名称的理想MEGA&#xff0c;它…

硅谷之火重燃武大

关注、星标公众号&#xff0c;直达精彩内容 来源&#xff1a;技术让梦想更伟大 作者&#xff1a;李肖遥 昨天看到雷军雷总在武汉大学的毕业典礼上&#xff0c;朴实的演讲&#xff0c;看似吹牛&#xff0c;实则也是一种勉励。 雷军大学一年级时&#xff0c;在武大图书馆看到一本…

基于java,springboot的音乐分享平台

背景 音乐网站与分享平台的主要使用者分为管理员和用户&#xff0c;实现功能包括管理员&#xff1a;首页、个人中心、用户管理、音乐资讯管理、音乐翻唱管理、在线听歌管理、留言板管理、系统管理&#xff0c;用户&#xff1a;首页、个人中心、音乐翻唱管理、我的收藏管理&…

MySQL - 单表数据量大表优化方案

一. 前言 当MySQL单表记录数过大时&#xff0c;增删改查性能都会急剧下降&#xff0c;可以参考以下步骤来优化。 二. 单表优化 除非单表数据未来会一直不断上涨&#xff0c;否则不要一开始就考虑拆分&#xff0c;拆分会带来逻辑、部署、运维的各种复杂度&#xff0c;一般以整…

软考:软件工程:软件开发与测试,黑盒测试,白盒测试

软考&#xff1a;软件工程: 提示&#xff1a;系列被面试官问的问题&#xff0c;我自己当时不会&#xff0c;所以下来自己复盘一下&#xff0c;认真学习和总结&#xff0c;以应对未来更多的可能性 关于互联网大厂的笔试面试&#xff0c;都是需要细心准备的 &#xff08;1&#…

年轻人的储蓄困境:愿望与现实的交锋(原因及解决之道)

年轻人存款难吗&#xff1f;探讨年轻人存款问题是目前社会热议的话题之一。最近的一项调查称&#xff0c;大约五分之一的年轻人存款在一万元以内&#xff0c;而存款超过10万元的年轻人只占总人数的53.7%。这一数据引发了广泛的关注和讨论&#xff0c;究竟是什么原因导致了年轻人…

基于开源项目二次开发KKPrinter实现打印机共享,远程跨网络实现不同网络打印机共享,客户端利用虚拟打印机截取打印文件转发至物理打印机(附源码)

基于开源项目二次开发KKPrinter实现打印机共享&#xff0c;远程跨网络实现不同网络打印机共享&#xff0c;客户端利用虚拟打印机截取打印文件转发至物理打印机&#xff08;附源码&#xff09;。实现方案有很多种&#xff0c;这里主要介绍 2 种&#xff0c;并附详细的实现过程和…

MySQL学习笔记之索引优化与查询优化

文章目录 前言数据准备建表创建函数插入数据创建删除索引函数 索引失效案例全值匹配最佳左前缀法则主键插入顺序计算、函数导致列索引失效函数导致索引失效计算导致索引失效 类型转换导致列索引失效范围条件右边的列索引失效不等于导致列索引失效is not null不能使用索引like以…

探索“AI+算力”:加速创新与增长的无限潜力

随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;技术正成为各行各业的关注焦点。在这个充满激情和创新的时代&#xff0c;"AI算力"的结合应用成为科技行业的热门话题&#xff0c;甚至引发出"AI算力最强龙头"的网络热门等式。本文将探讨…

数据结构-哈希-哈希表实现

哈希表实现 一&#xff0c;哈希概念哈希概念常见哈希函数哈希冲突哈希冲突的解决 二&#xff0c;闭散列实现闭散列的结构插入查找删除闭散列总结 三&#xff0c;哈希桶实现哈希桶的结构插入查找删除析构拷贝构造赋值运算符重载 四&#xff0c;哈希表总结开散列与闭散列的比较哈…

设备虚拟化基础 - PCI

目录 1. 配置空间概念和作用 2. 通过配置空间发现设备 3. Linux读取PCI配置空间接口 4. 内核中具体读取配置空间实例 5. Virtion设备自定义空间 6. Linux读取Capabilities List代码解析 1. 配置空间概念和作用 详细的定义可以参考PCI Spec的第六章《Configuration Space…