Table of Contents
Toolbox

Initial support for servers with historical data access in open62541

Toolbox

An OPC UA server supporting historical access allows clients to access historical data or historical events. Such a server can act as a process or event historian. open62541-based servers were till recently completely missing the ability to support these use cases. basysKom has extended the server API to support access to historical data using the “read raw” functionality specified in OPC UA part 11. This allows to create a simple process historian. This article provides an overview about this API and a short tutorial how to use it.

Overview about the open62541 historical data access

Most of the new functionality is contained within a plugin called UA_HistoryDatabase. This plugin contains three main API elements.

  • UA_HistoryDatabase (same name as the plugin) which contains the main interface between the server itself and the plugin.
  • UA_HistoryDataBackend which implements the integration with a specific database. A sample implementation based on a simple in-memory database is provided.
  • UA_HistoryDataGathering which encapsulates the gathering and storage of data. 

The following diagram gives an overview how these API elements interact.

open62541 - historical data access

Tutorial

The following tutorial will first demonstrate how to create a simple server, hosting a single variable node. We will extend this server to store this value in an in-memory database each time it changes.

Let’s start with a server:

#include "open62541.h"
#include 
 
static UA_Boolean running = true;
static void stopHandler(int sign) {
    (void)sign;
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
    running = false;
}
 
 
int main(void) {
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);
 
    UA_ServerConfig *config = UA_ServerConfig_new_default();
    UA_Server *server = UA_Server_new(config);
    /* Define the attribute of the uint32 variable node */
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    UA_UInt32 myUint32 = 40;
    UA_Variant_setScalar(&attr.value, &myUint32, &UA_TYPES[UA_TYPES_UINT32]);
    attr.description = UA_LOCALIZEDTEXT("en-US","myUintValue");
    attr.displayName = UA_LOCALIZEDTEXT("en-US","myUintValue");
    attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
 
    /* Add the variable node to the information model */
    UA_NodeId uint32NodeId = UA_NODEID_STRING(1, "myUintValue");
    UA_QualifiedName uint32Name = UA_QUALIFIEDNAME(1, "myUintValue");
    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
    UA_NodeId outNodeId;
    UA_NodeId_init(&outNodeId);
    UA_StatusCode retval = UA_Server_addVariableNode(server,
                                                     uint32NodeId,
                                                     parentNodeId,
                                                     parentReferenceNodeId,
                                                     uint32Name,
                                                     UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
                                                     attr,
                                                     NULL,
                                                     &outNodeId);
    fprintf(stderr, "UA_Server_addVariableNode %s\n", UA_StatusCode_name(retval));
    retval = UA_Server_run(server, &running);
    fprintf(stderr, "UA_Server_run %s\n", UA_StatusCode_name(retval));
    UA_Server_run_shutdown(server);
    UA_Server_delete(server);
    UA_ServerConfig_delete(config);
    return (int)retval;
}

The resulting server can be browsed and the variable can be read. We use UAExpert for this tasks. 

Initial support for servers with historical data access in open62541 1 basysKom, HMI Dienstleistung, Qt, Cloud, Azure

We now extend this example so that the server will be able to handle request for historical values for this node. Each of the following snippets contains enough context so it is clear where to add the code into the initial server code.

[...]
UA_ServerConfig *config = UA_ServerConfig_new_default();
 
 
// Instanciate a UA_HistoryDataGathering with an initial node id store size of 1.
 
// The store will grow if additional nodes are registered, but this is expensive.
UA_HistoryDataGathering gathering = UA_HistoryDataGathering_Default(1);
// configure the server with an instance of the UA_HistoryDatabase plugin
config->historyDatabase = UA_HistoryDatabase_default(gathering);
UA_Server *server = UA_Server_new(config);
UA_VariableAttributes attr = UA_VariableAttributes_default;
UA_UInt32 myUint32 = 40;
UA_Variant_setScalar(&attr.value, &myUint32, &UA_TYPES[UA_TYPES_UINT32]);
attr.description = UA_LOCALIZEDTEXT("en-US","myUintValue");
attr.displayName = UA_LOCALIZEDTEXT("en-US","myUintValue");
attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
 
// announce support for history read to clients
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE | UA_ACCESSLEVELMASK_HISTORYREAD;
// tell the server to enable historizing for this node
attr.historizing = true;
 
UA_NodeId uint32NodeId = UA_NODEID_STRING(1, "myUintValue");
UA_QualifiedName uint32Name = UA_QUALIFIEDNAME(1, "myUintValue");
[...]

Next we configure the node to update the database each time the nodes value is set.

[...]
                                                 NULL,
                                                 &outNodeId);
fprintf(stderr, "UA_Server_addVariableNode %s\n", UA_StatusCode_name(retval));
 
UA_HistorizingNodeIdSettings setting;
/* Use the default sample in-memory database. Reserve space for 3 nodes with an initial room of 100 values each.
 * This will also automaticaly grow if needed. */
setting.historizingBackend = UA_HistoryDataBackend_Memory(3, 100);
 
/* We want the server to serve a maximum of 100 values per request.
 * This value depends on the platform you are running the server on - a big
 * server can serve more values in a single request. */
setting.maxHistoryDataResponseSize = 100;
// update the database each time the node value is set
setting.historizingUpdateStrategy = UA_HISTORIZINGUPDATESTRATEGY_VALUESET;
// register the node for gathering data in the database
retval = gathering.registerNodeId(server, gathering.context, &outNodeId, setting);
fprintf(stderr, "registerNodeId %s\n", UA_StatusCode_name(retval));
retval = UA_Server_run(server, &running);
fprintf(stderr, "UA_Server_run %s\n", UA_StatusCode_name(retval));
[...]

Again we rely on the UAExpert to validate the server. First update the variable a few times (so we actually have some historical values). Next we want to read them.  For that we use the “History Trend View” which is available via Document → Add → Document Type. The resulting view shows either a graph or (in a second tab) a list of your historical values. If no values are plotted or the list is empty make sure that the right time interval is used in the request.

Initial support for servers with historical data access in open62541 2 basysKom, HMI Dienstleistung, Qt, Cloud, Azure

Conclusion & future work

It is now possible to create open62541-based servers which support the gathering and reading of historical data values. A number of plugin interfaces has been defined which can be used to implement functionality such as support for additional databases (e.g. sqlite). There is additional work to be done to support other history read details such as “read event”, “read modified”, “read processed” or “read at a time”. Also the history update service still needs to be implemented.

In addition to being a contributor to the project, basysKom also provides commercial support for the open62541 OPC UA stack. Talk to us if you are interested in project/application support, the implementation of missing features or bugfixing.

One Response

Leave a Reply

Your email address will not be published. Required fields are marked *

Peter Rustler

Peter Rustler

Peter Rustler is Backend Software Engineer on industrial and embedded applications. He works for basysKom GmbH since 2011, where he is consulting customers. Contributer to various projects such as open62541 and Qt-Nfc. He has a strong background in Embedded Linux, systems programming, distributed systems and application development. He holds a Bachelor of Computer Science from the University of Applied Sciences in Darmstadt and has an education in communication electronics.
Share on facebook
Share on twitter
Share on linkedin
Share on reddit
Share on xing
Share on email
Share on stumbleupon
Share on whatsapp
Share on pocket

Read more

open62541
Azure
Jannis Völker
Connect OPC UA with open62541 to MS Azure IoT Hub

The open62541 OPC UA stack with its Pub-Sub extension now supports MQTT over TLS as well as MQTT-brokers requiring a login (contributed by basysKom). This allows the direct communication between open62541 and the Azure IoT Hub and therefore highly simplifies the connection of OPC UA based IoT Devices to the cloud.

Read More »