open62541[4]-Server连接变量和物理过程

在OPC UA Server里,往往会有很多runtime信息,这些信息由底层的某种物理过程产生,如锅炉的温度值,实在锅炉运行过程中产生的,锅炉运行过程就可以看成是一个物理过程。

Server会提供一个变量,用于存放锅炉的温度值,这样Client通过读取这个温度值就能知道锅炉的温度了

以系统时间为例,讲述如何把一个变量和系统时间联系在一起,这样client就能通过这个变量获取系统时间,系统时间是不断变化的,可以看成一个物理过程

  1. 手动更新变量

在OPC UA Server里添加一个变量用来表示系统时间,然后手动更新

code:

#include<stdlib.h>
#include<signal.h>
#include"open62541.h"

#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib, "Iphlpapi.lib")

UA_Boolean running = true;

static void stopHandler(int sign)
{
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
    running = false;
}

static void updateCurrentTime(UA_Server* server, UA_NodeId timeNodeId)
{
    UA_DateTime nowTime = UA_DateTime_now();
    UA_Variant value;
    UA_Variant_setScalar(&value, &nowTime, &UA_TYPES[UA_TYPES_DATETIME]);
    UA_Server_writeValue(server, timeNodeId, value);
}

static void addCurrentTimeVariable(UA_Server* server)
{
    UA_DateTime nowTime = 0;
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    attr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)"Current time - value callback");
    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
    UA_Variant_setScalar(&attr.value, &nowTime, &UA_TYPES[UA_TYPES_DATETIME]);

    //添加变量表示时间
    UA_NodeId currentNodeId = UA_NODEID_STRING(1, (char*)"current-time-value-callback");
    UA_QualifiedName currentName = UA_QUALIFIEDNAME(1, (char*)"current-time-value-callback");
    UA_NodeId ParentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
    UA_NodeId variableTypeNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE);
    UA_Server_addVariableNode(server, currentNodeId, ParentNodeId, parentReferenceNodeId
        , currentName, variableTypeNodeId, attr, NULL, NULL);
    //手动更新时间
    updateCurrentTime(server, currentNodeId);
}
int main()
{
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData); // 使用Winsock 2.2版本
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);

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

    UA_StatusCode resVal = UA_Server_run(server, &running);
    UA_Server_delete(server);

    return resVal == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;

}

代码中添加了时间变量后,调用updateCurrentTime()更新了一次时间。

  1. 变量值读写的回调

前面添加了时间变量并手动更新了值,但是系统的时间是时刻变化的,为了保证变量的值和系统时间同步,就需要不断的手动更新,不过client随时都会可能去获取系统时间,这就保证server不断的更新时间,会消耗许多资源

所以可以给这个变量添加回调,当client需要读取系统时间时才去做同步

code:

#include<stdlib.h>
#include<signal.h>
#include"open62541.h"

#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib, "Iphlpapi.lib")

UA_Boolean running = true;

static void stopHandler(int sign)
{
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
    running = false;
}

static void updateCurrentTime(UA_Server* server, UA_NodeId timeNodeId)
{
    UA_DateTime nowTime = UA_DateTime_now();
    UA_Variant value;
    UA_Variant_setScalar(&value, &nowTime, &UA_TYPES[UA_TYPES_DATETIME]);
    UA_Server_writeValue(server, timeNodeId, value);
}

static void addCurrentTimeVariable(UA_Server* server)
{
    UA_DateTime nowTime = 0;
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    attr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)"Current time - value callback");
    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
    UA_Variant_setScalar(&attr.value, &nowTime, &UA_TYPES[UA_TYPES_DATETIME]);

    //添加变量表示时间
    UA_NodeId currentNodeId = UA_NODEID_STRING(1, (char*)"current-time-value-callback");
    UA_QualifiedName currentName = UA_QUALIFIEDNAME(1, (char*)"current-time-value-callback");
    UA_NodeId ParentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
    UA_NodeId variableTypeNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE);
    UA_Server_addVariableNode(server, currentNodeId, ParentNodeId, parentReferenceNodeId
        , currentName, variableTypeNodeId, attr, NULL, NULL);
    //手动更新时间
    updateCurrentTime(server, currentNodeId);
}

static void beforeReadTime(UA_Server* server,
    const UA_NodeId* sessionId, void* sessionContext,
    const UA_NodeId* nodeid, void* nodeContext,
    const UA_NumericRange* range, const UA_DataValue* data)
{
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "The variable was to be updated");
    updateCurrentTime(server, *nodeid);
}
static void afterWriteTime(UA_Server* server,
    const UA_NodeId* sessionId, void* sessionContext,
    const UA_NodeId* nodeId, void* nodeContext,
    const UA_NumericRange* range, const UA_DataValue* data)
{
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "The variable was updated");
}

static void addValueCallbackToCurrentTimeVariable(UA_Server* server)
{
    UA_NodeId currentNodeId = UA_NODEID_STRING(1, (char*)"current-time-value-callback");
    UA_ValueCallback callback;
    callback.onRead = beforeReadTime;
    callback.onWrite = afterWriteTime;
    UA_Server_setVariableNode_valueCallback(server, currentNodeId, callback);
}

int main()
{
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData); // 使用Winsock 2.2版本
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);

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

    UA_StatusCode resVal = UA_Server_run(server, &running);
    UA_Server_delete(server);

    return resVal == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;

}

每点击一次都会更新当前时间

代码解析:

1.使用UA_ValueCallback创建回调,并添加2个回调函数,一个是读之前beforeReadTime(),另一个 是写之后afterWriteTime()

2.使用UA_Server_setVariableNode_valueCallback()给时间变量添加第1步创建的回调

3.当Client读取时间变量时,Server就会先去调用beforeReadTime(),该函数对时间变量进行更新

4.最后Client就可以通过时间变量的值获取Server的当前系统时间

  1. 变量的数据源

之前的操作中变量的值都是存放在变量节点里的,client都是从变量里面拿到值,也就意味着变量需要先从对应的物理过程拿到最新值,然后保存下来,最后才能让client读取,接下来就更进一步,直接从源头拿数,不用去读取变量本身的值,这需要再server端添加数据源节点,把每一个读写请求都重定向到回调函数里面。

code:

#include<stdlib.h>
#include<signal.h>
#include"open62541.h"

#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib, "Iphlpapi.lib")

UA_Boolean running = true;

static void stopHandler(int sign)
{
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
    running = false;
}

static void updateCurrentTime(UA_Server* server, UA_NodeId timeNodeId)
{
    UA_DateTime nowTime = UA_DateTime_now();
    UA_Variant value;
    UA_Variant_setScalar(&value, &nowTime, &UA_TYPES[UA_TYPES_DATETIME]);
    UA_Server_writeValue(server, timeNodeId, value);
}

static void addCurrentTimeVariable(UA_Server* server)
{
    UA_DateTime nowTime = 0;
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    attr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)"Current time - value callback");
    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
    UA_Variant_setScalar(&attr.value, &nowTime, &UA_TYPES[UA_TYPES_DATETIME]);

    //添加变量表示时间
    UA_NodeId currentNodeId = UA_NODEID_STRING(1, (char*)"current-time-value-callback");
    UA_QualifiedName currentName = UA_QUALIFIEDNAME(1, (char*)"current-time-value-callback");
    UA_NodeId ParentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
    UA_NodeId variableTypeNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE);
    UA_DataDource timeDataSources;
    timeDataSources.read = readCurrentTime;
    timeDataSources.write = writeCurrentTime;
    UA_Server_addDataSoureceVariableNode(server, currentNodeId, ParentNodeId, parentReferenceNodeId
    , currentName, variableTypeNodeId, attr, NULL, NULL);
    //手动更新时间
    updateCurrentTime(server, currentNodeId);
}
int main()
{
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData); // 使用Winsock 2.2版本
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);

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

    UA_StatusCode resVal = UA_Server_run(server, &running);
    UA_Server_delete(server);

    return resVal == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;

}

解析:

  1. 代码中使用了UA_Server_addDataSourceVariableNode()给时间变量添加数据源节点

  1. 当client去读系统时间时,就会转去调用数据源节点的read函数,也就是readCUrrentTime(),该哈数直接读取当前系统时间,并赋值给目标函数,用于返回给client

  1. 这个和之前的读取回调不一样,那个是获取当前系统时间,然后把实际值赋值给变量节点,最后client再获取变量节点的值,多了一步操作步骤

参考

https://wanghao1314.blog.csdn.net/article/details/102653224

复现了一下 学习用

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为阿根廷助威

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值