基于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 程序源码

扫描二维码关注公众号,回复: 15633383 查看本文章

        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

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

猜你喜欢

转载自blog.csdn.net/py8105/article/details/131338578