UA Ansi C Server Professional  1.3.1.232
 All Data Structures Functions Variables Typedefs Enumerations Enumerator Groups Pages
Lesson 7: Adding Support for Historical Access

In this lesson we will explain how to add historizing capabilities to a variable node.

Content:

Step 1: Setting up a Data Logger

For logging values of a variable node, we start by creating a data logger. We can choose between the two sample implementations delivered with the SDK (SQLiteLogger or FileLogger), but it is possible to create your own implementation of a data logger and use it instead. In this example we will use one of the sample implementations, depending on which is enabled.

First we need to know the NodeId of the variable node we want to log (here we want to log the temperature value of the temperature sensor), so we extend the TemperatureSensor by a NodeId member and an Int member for storing the handle to the data logger item.

struct _TemperatureSensor
{
/* User data header */
UserDataType Type;
/* Protocol information */
OpcUa_Double *pValue;
/* Data logging information */
OpcUa_NodeId nodeIdTemperatureValue;
OpcUa_Int hDataLogItemTemperatureValue;
};
typedef struct _TemperatureSensor TemperatureSensor;

The new member nodeIdTemperatureValue of the extended TemperatureSensor structure is initialized and set in CustomProvider_CreateTemperatureSensor:

OpcUa_StatusCode CustomProvider_CreateTemperatureSensor(OpcUa_StringA a_sSensorName,
OpcUa_BaseNode *a_pOwner,
OpcUa_NodeId *a_pTemperatureSensorTypeId,
OpcUa_NodeId *a_pStartingNodeId,
TemperatureSensor **a_ppNewTemperatureSensor)
{
...
OpcUa_NodeId_Initialize(&pNewSensor->nodeIdTemperatureValue);
OpcUa_NodeId_CopyTo(a_pStartingNodeId, &pNewSensor->nodeIdTemperatureValue);
...
}

Using the NodeId of the temperature value variable, we can now create a data logger and the according data logger item. The data logger handle is stored in a global variable g_hDataLogger which is used to clean it up on shutdown. The item handle returned by UaServer_DataLoggerItemData_Create is stored in the user data associated with the temperature value variable.

void CustomProvider_SetupDataLogger()
{
OpcUa_BaseNode *pNode = 0;
UaServer_AddressSpace *pAddressSpace = &g_pCustomProvider->AddressSpace;
/* create file logger */
#if HAVE_DATA_LOGGER_FILE_BACKEND
g_hDataLogger = UaServer_FileLogger_Create( "historian" );
#endif
if (g_hDataLogger < 0) return;
/* add Temperature to data logging */
UaServer_GetNode(pAddressSpace, &g_pMyCustomMachine->pTemperatureSensor->nodeIdTemperatureValue, &pNode);
if ( pNode )
{
TemperatureSensor *pTemperatureSensor = (TemperatureSensor*)OpcUa_BaseNode_GetUserData(pNode);
UaServer_DataLoggerItemData_Create(&g_pMyCustomMachine->pTemperatureSensor->nodeIdTemperatureValue,
100,
OpcUa_MonitoringMode_Reporting,
100,
OpcUa_Null,
g_hDataLogger,
&pTemperatureSensor->hDataLogItemTemperatureValue);
}
/* start historian data logging */
UaServer_DataLogger_Start( g_hDataLogger );
}

Step 2: Implementing HistoryReadRawModified in the Provider

In order to make the recorded history values available to clients, we need to implement HistoryReadRawModified in our provider and forward the calls to the data logger. A new callback function is created and set in the provider interface:

/* Forward declarations */
...
IFMETHODIMP(CustomProvider_HistoryReadRawModifiedAsync)(UaServer_ProviderHistoryReadRawModifiedContext* a_pHistoryReadRawModifiedCtx);
...
IFMETHODIMP(CustomProvider_Initialize)(UaServer_Provider *a_pProvider,
UaServer_pProviderCBInterface *a_pProviderCBInterface,
UaServer_pProviderInterface *a_pProviderInterface)
{
...
a_pProviderInterface->HistoryReadRawModifiedAsync = CustomProvider_HistoryReadRawModifiedAsync;
...
}

The implementation of this function can be found in custom_provider_historyread.c. Just like in other services, we check if we are responsible for processing the request by comparing the namespace index. If so, we check if the variable is history readable and if it contains user data of type UserDataTemperature.

The implementation of this function can be found in custom_provider_historyread.c. Just like in other services, we check if we are responsible for processing the request by comparing the namespace index. If so, we check if the variable is history readable and if it contains user data of type UserDataTemperature.

IFMETHODIMP(CustomProvider_HistoryReadRawModifiedAsync)(UaServer_ProviderHistoryReadRawModifiedContext* a_pHistoryReadRawModifiedCtx)
{
...
/* process nodes */
for (i = 0; i < a_pHistoryReadRawModifiedCtx->NoOfNodesToRead; i++)
{
/* check if the node is from our provider's namespace */
if (a_pHistoryReadRawModifiedCtx->pNodesToRead[i].NodeId.NamespaceIndex == g_uCustomProvider_NamespaceIndex)
{
/* get the node pointer from the NodeId */
UaServer_GetNode(pAddressSpace, &a_pHistoryReadRawModifiedCtx->pNodesToRead[i].NodeId, &pNode);
if (pNode)
{
...
if (OpcUa_BaseNode_GetId(pNode)->IdentifierType == OpcUa_IdentifierType_Numeric)
{
OpcUa_StatusCode ret;
OpcUa_HistoryReadResult *pHistoryResult = &a_pHistoryReadRawModifiedCtx->pResponse->Results[i];
UserDataCommon *pUserData = (UserDataCommon*)OpcUa_BaseNode_GetUserData(pNode);
if (pUserData && pUserData->Type == UserDataTemperature)
{

Knowing that we have a history for this node, we can forward the call to the data logger, providing the handles to the data logger itself and the data logger item (stored in the user data of the node). This is all needed to be done for enabling history data logging for a node and for providing the values to clients.

TemperatureSensor *pTemperatureSensorData = (TemperatureSensor*)pUserData;
ret = UaServer_DataLogger_ReadValues(
g_hDataLogger,
pTemperatureSensorData->hDataLogItemTemperatureValue,
a_pHistoryReadRawModifiedCtx->pHistoryReadRawModifiedDetails,
a_pHistoryReadRawModifiedCtx->TimestampsToReturn,
a_pHistoryReadRawModifiedCtx->ReleaseContinuationPoints,
&a_pHistoryReadRawModifiedCtx->pNodesToRead[i],
pHistoryResult);

Step 3: Reading out the History with a Client

Now that we finished setting up history data logging for the temperature sensor value, we can read it out using a client.

Figure 7-1 Create a History Trend View using UaExpert

For reading history values with UaExpert, create a History Trend View by clicking 'Add Document', selecting 'History Trend View' and clicking 'Add'.

Figure 7-2 Read History using UaExpert

Now you can drag&drop the 'Temperature' variable of 'MyCustomTemperatureSensor' into the Configuration section of the History Trend View. The client will then read the history values of the variable and display them in the 'Numeric Values' tab in the History Data section of the view.

By changing 'Start Time' and 'End Time' you can change the range of values the client requests at the server, clicking 'Update' issues an update of the history values.