一、环境依赖
1、openssl:主要用于实现SSL和TLS协议,提供通讯中的加密。
2、Open62541:一个开源的C语言库,用来实现OPC UA客户端和服务器,也就是我们本文的核心。
3、WS2_32.lib:Windows Sockets应用程序的接口, 用于和网络应用程序。
文章中提供了依赖文件。
二、服务端源程序
#include <open62541.h>
#include <iostream>
#include <atomic>
#include <thread>
#include <string>
static UA_Boolean running(true);
char mulDn[2][40] = { "SharedDataNode1", "SharedDataNode2"}; //节点名称
char l[] = "en"; //语言
static double sharedData[2] = { 1.1, 2.2}; //节点初始数据
UA_Server *server = nullptr;
static std::atomic <bool> starting = true;
void addMultipleNodes()
{
if (!server)
{
std::cerr << "opcua server dose not open....." << std::endl;
return;
}
// 变量节点的属性设置
UA_VariableAttributes attr = UA_VariableAttributes_default;
attr.dataType = UA_TYPES[UA_TYPES_DOUBLE].typeId;
attr.valueRank = UA_VALUERANK_SCALAR;
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
for (int i = 0; i < 2; ++i) {
// 设置每个节点的属性
attr.displayName = UA_LOCALIZEDTEXT(l, mulDn[i]);
UA_Variant_setScalar(&attr.value, &sharedData[i], &UA_TYPES[UA_TYPES_DOUBLE]);
// 请求的新节点 ID
UA_NodeId requestedNewNodeId = UA_NODEID_STRING(1, mulDn[i]);
// 父节点 ID (可以设置为 NULL 作为根节点)
UA_NodeId parentNodeId = UA_NODEID_NULL;//UA_NODEID_NUMERIC(1, UA_NS0ID_OBJECTSFOLDER);
// 引用类型 ID (可以设置为 NULL)
UA_NodeId referenceTypeId = UA_NODEID_NULL;
// 浏览名称
UA_QualifiedName browseName = UA_QUALIFIEDNAME(1, mulDn[i]);
// 类型定义 ID (可以设置为 NULL)
UA_NodeId typeDefinition = UA_NODEID_NULL;
// 存储输出的新节点 ID
UA_NodeId outNewNodeId;
// 添加变量节点到服务器
UA_StatusCode retval = UA_Server_addVariableNode(server, requestedNewNodeId,
parentNodeId, referenceTypeId,
browseName, typeDefinition,
attr, NULL, &outNewNodeId);
if (retval != UA_STATUSCODE_GOOD) {
std::cerr << "Failed to add variable node: " << mulDn[i] << " code " << retval << std::endl;
}
else {
std::cout << "Variable node " << mulDn[i] << " added successfully with NodeId: "
<< outNewNodeId.identifier.string.data << std::endl;
}
}
}
// 启动 OPC UA 服务端
void startServer() {
starting.store(true);
// 创建服务器
server = UA_Server_new();
UA_ServerConfig_setDefault(UA_Server_getConfig(server)); // 使用默认配置
addMultipleNodes();
UA_ValueCallback callback;
callback.onWrite = myWriteCallback;
// 为该节点设置写入回调函数
//UA_Server_setVariableNode_valueCallback(server, newNodeId, callback);
// 运行服务器
std::cout << "Starting OPC UA Server..." << std::endl;
UA_StatusCode retval = UA_Server_run(server, &running);
if (retval != UA_STATUSCODE_GOOD) {
std::cerr << "Failed to start the server!" << std::endl;
}
// 释放服务器资源
UA_Server_delete(server);
}
void startOpcua()
{
std::cout << "Starting OPC UA Server..." << std::endl;
// 启动服务端线程
std::thread serverThread(startServer);
serverThread.detach();
while(starting.load())
{
Sleep(20);
}
}
void stopOpcua()
{
starting.store(false);
running = false;
}
int main() {
startOpcua();
return 0;
}
运行后,可以用UA Expert软件查看opcua服务端是否正常,以及查看节点是否正常加载。如果一切正常可以,则可以操作看到下面的结果。不会用UA Expert的小伙伴,也可以继续往下进行,实现客户端,直接读取节点数据。
四、客户端
#include <open62541.h>
#include <stdio.h>
int main() {
// 创建一个 OPC UA 客户端
UA_Client *client = UA_Client_new();
UA_ClientConfig_setDefault(UA_Client_getConfig(client));
// 连接到 OPC UA 服务器
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840"); // 使用你的服务器地址
if (retval != UA_STATUSCODE_GOOD) {
printf("连接 OPC UA 服务器失败\n");
UA_Client_delete(client);
return -1;
}
char dn[] = "SharedDataNode1"; //服务端的节点名
// 定义节点 ID
UA_NodeId nodeId = UA_NODEID_STRING(1, dn); // 你要读取的节点ID
// 读取数据
UA_Variant value;
UA_Variant_init(&value);
retval = UA_Client_readValueAttribute(client, nodeId, &value);
if (retval == UA_STATUSCODE_GOOD) {
// 如果读取成功,输出数据
printf("读取的值: ");
if (UA_Variant_isScalar(&value)) {
printf("%f\n", *(UA_Double *)value.data);
}
}
else {
// 如果读取失败
printf("读取节点失败\n");
}
// 清理
UA_Variant_clear(&value);
UA_Client_disconnect(client);
UA_Client_delete(client);
return 0;
}
本代码实现的是opcua客户端代码,功能是读取服务端存在的一个节点的数据。程序运行前,需要先运行我们的服务端程序,否则无法连接。服务端正常启动后,再运行我们的客户端,终端上就可以看到读取的数据,下面截图框出来的就是。
除了从服务端读取数据外,还可以往服务器中写数据,修改对应节点的值。
#include <open62541.h>
#include <stdio.h>
#include "OpcuaWrite.h"
int writeToOpc(char *dataNode, double v)
{
// 创建一个 OPC UA 客户端
UA_Client *client = UA_Client_new();
UA_ClientConfig_setDefault(UA_Client_getConfig(client));
// 连接到 OPC UA 服务器
UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840"); // 使用你的服务器地址
if (retval != UA_STATUSCODE_GOOD) {
printf("连接 OPC UA 服务器失败\n");
UA_Client_delete(client);
return -1;
}
//char dn[] = "SharedDataNode";
// 定义目标节点 ID
UA_NodeId nodeId = UA_NODEID_STRING(1, dataNode); // 目标节点的 ID
// 创建要写入的值
UA_Double valueToWrite = v;
// 创建一个变体并将数据写入
UA_Variant value;
UA_Variant_init(&value);
value.type = &UA_TYPES[UA_TYPES_DOUBLE]; // 数据类型
value.data = &valueToWrite; // 数据值
// 写入数据到目标节点
retval = UA_Client_writeValueAttribute(client, nodeId, &value);
if (retval == UA_STATUSCODE_GOOD) {
// 如果写入成功,打印成功消息
printf("成功写入值: %f\n", valueToWrite);
}
else {
// 如果写入失败,打印错误消息
printf("写入数据失败\n");
}
// 清理
//UA_Variant_clear(&value);
UA_Client_disconnect(client);
UA_Client_delete(client);
return 0;
}
int main()
{
char dn[] = "SharedDataNode1";
return writeToOpc(dn, 25.5);;
}