open62541[4]-Server connection variable and physical process

In OPC UA Server, there is often a lot of runtime information, which is generated by some underlying physical process, such as the temperature value of the boiler, which is actually generated during the operation of the boiler, and the operation of the boiler can be regarded as a physical process.

The server will provide a variable to store the temperature value of the boiler, so that the client can know the temperature of the boiler by reading this temperature value

Taking the system time as an example, it describes how to associate a variable with the system time, so that the client can obtain the system time through this variable. The system time is constantly changing and can be regarded as a physical process.

  1. Manually update variables

Add a variable in OPC UA Server to represent the system time, and then update it manually

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]);

    //Add variable to represent time
    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);
    //Manually update the time
    updateCurrentTime(server, currentNodeId);
}
int main()
{
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), & amp;wsaData); // use Winsock 2.2 version
    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;

}

After adding the time variable in the code, call updateCurrentTime() to update the time once.

  1. Callback for reading and writing variable values

The time variable was added earlier and the value was manually updated, but the system time is constantly changing. In order to ensure that the value of the variable is synchronized with the system time, it needs to be manually updated continuously. However, the client may obtain the system time at any time, which guarantees The server constantly updates the time, which consumes a lot of resources

So you can add a callback to this variable, and only do synchronization when the client needs to read the system time

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]);

    //Add variable to represent time
    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);
    //Manually update the time
    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), & amp;wsaData); // use Winsock 2.2 version
    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;

}

Each click will update the current time

Code Analysis:

1. Use UA_ValueCallback to create a callback, and add 2 callback functions, one is beforeReadTime() before reading, and the other is afterWriteTime() after writing

2. Use UA_Server_setVariableNode_valueCallback() to add the callback created in step 1 to the time variable

3. When the client reads the time variable, the server will first call beforeReadTime(), which updates the time variable

4. Finally, the Client can obtain the current system time of the Server through the value of the time variable

  1. Variable data source

In the previous operations, the values of the variables are all stored in the variable nodes, and the client gets the values from the variables, which means that the variables need to get the latest values from the corresponding physical process first, then save them, and finally make them available. The client reads, and the next step is to get the data directly from the source without reading the value of the variable itself. This requires adding a data source node on the server side, and redirecting every read and write request to the callback function.

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]);

    //Add variable to represent time
    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_addDataSourceVariableNode(server, currentNodeId, ParentNodeId, parentReferenceNodeId
    , currentName, variableTypeNodeId, attr, NULL, NULL);
    //Manually update the time
    updateCurrentTime(server, currentNodeId);
}
int main()
{
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), & amp;wsaData); // use Winsock 2.2 version
    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;

}

Analysis:

  1. The code uses UA_Server_addDataSourceVariableNode() to add data source nodes to time variables

  1. When the client reads the system time, it will turn to call the read function of the data source node, that is, readCUrrentTime(), which directly reads the current system time and assigns it to the target function for returning to the client

  1. This is different from the previous read callback, which is to obtain the current system time, then assign the actual value to the variable node, and finally the client obtains the value of the variable node, which is one more step

Reference

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

Reproduced it for learning

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge Algorithm skill treeHome pageOverview 40952 people are studying systematically