C++ Based OPC UA Client/Server/PubSub SDK  1.7.6.537
Lesson 7: Adding Support for Historical Data Access

The goal of this lesson is to show how to provide historical data in the Address Space.

The following figure shows the additional features provided by this lesson. The value of the Temperature variable will be historized and the historical information will be provided through the HistoryRead service. The historical configuration is provided through an additional object HA Configuration.

Figure 7-1 Object HA Configuration

l4gettingstartedlesson07_controller_objecttypeall.png

The necessary steps to provide access to the history and to indicate the availability of the history are described in the following.

Step 1: Creating a HistoryManager

The HistoryManager interface is used to integrate history access functionality with the SDK. We will use the helper class HistoryManagerBase for this example. This helper class provides a simplified synchronous API for the history access handling. The following code should be added to a header file with the name historymanagerbuildingautomation.h.

#ifndef __HISTORYMANAGERBUILDINGAUTOMATION_H__
#define __HISTORYMANAGERBUILDINGAUTOMATION_H__
#include "historymanagerbase.h"
class HistoryManagerBuildingAutomation : public HistoryManagerBase
{
UA_DISABLE_COPY(HistoryManagerBuildingAutomation);
public:
HistoryManagerBuildingAutomation();
virtual ~HistoryManagerBuildingAutomation();
// Interface HistoryManagerBase
virtual UaStatus readRaw (
const ServiceContext& serviceContext,
HistoryVariableHandle* pVariableHandle,
HistoryReadCPUserDataBase** ppContinuationPoint,
OpcUa_TimestampsToReturn timestampsToReturn,
OpcUa_UInt32 maxValues,
OpcUa_DateTime& startTime,
OpcUa_DateTime& endTime,
OpcUa_Boolean returnBounds,
OpcUa_HistoryReadValueId* /*pReadValueId*/,
UaDataValues& dataValues);
// Interface HistoryManagerBase
private:
};
#endif // __HISTORYMANAGERBUILDINGAUTOMATION_H__

The following code with the empty class implementation should be added to a file with the name historymanagerbuildingautomation.cpp

The class HistoryReadCPUserDataBA is used later on to implement the continuation point handling for history read raw. The next timestamp to return is used as continuation point data.

The following code is the empty implementation for the class HistoryManagerBuildingAutomation.

#include "historymanagerbuildingautomation.h"
// History manager specific continuation point class
class HistoryReadCPUserDataBA : public HistoryReadCPUserDataBase
{
public:
HistoryReadCPUserDataBA(const UaNodeId& affectedNode, HistoryManager::TransactionType historyTransactionType)
: HistoryReadCPUserDataBase(affectedNode, historyTransactionType)
{}
~HistoryReadCPUserDataBA(){}
// We use the next timestamp to return as continuation point
UaDateTime m_newStartTime;
};
HistoryManagerBuildingAutomation::HistoryManagerBuildingAutomation()
{
}
HistoryManagerBuildingAutomation::~HistoryManagerBuildingAutomation()
{
}
UaStatus HistoryManagerBuildingAutomation::readRaw (
const ServiceContext& serviceContext,
HistoryVariableHandle* pVariableHandle,
HistoryReadCPUserDataBase** ppContinuationPoint,
OpcUa_TimestampsToReturn timestampsToReturn,
OpcUa_UInt32 maxValues,
OpcUa_DateTime& startTime,
OpcUa_DateTime& endTime,
OpcUa_Boolean returnBounds,
OpcUa_HistoryReadValueId* pReadValueId,
UaDataValues& dataValues)
{
UaStatus ret = OpcUa_BadNotImplemented;
return ret;
}

Step 2: Integrating HistoryManager into NodeManager

We need to connect the HistoryManager with the SDK and the Nodes we want provide historical information for.

This connection is done through the NodeManager. We need to create an instance of the HistoryManager in the NodeManager and need to register this instance with the NodeManagerBase implementation.

The following code needs to be added to the existing header file for the NmBuildingAutomation class.

#ifndef __NMBUILDINGAUTOMATION_H__
#define __NMBUILDINGAUTOMATION_H__
#include "nodemanagerbase.h"
class BaCommunicationInterface;
// New code begins
class HistoryManagerBuildingAutomation;
// New code ends
class NmBuildingAutomation : public NodeManagerBase
...
private:
UaStatus createTypeNodes();
BaCommunicationInterface* m_pCommIf;
// New code begins
HistoryManagerBuildingAutomation* m_pHistoryManager;
// New code ends
};
#endif // __NMBUILDINGAUTOMATION_H__

The following code needs to be added to the existing implementation file (nmbuildingautomation.cpp) for the NmBuildingAutomation class.

The additional code creates the HistoryManager instance and registers this instance with the NodeManager base class.

#include "nmbuildingautomation.h"
#include "buildingautomationtypeids.h"
#include "airconditionercontrollerobject.h"
#include "furnacecontrollerobject.h"
#include "opcua_analogitemtype.h"
#include "bacommunicationinterface.h"
#include "baeventdata.h"
#include "opcua_offnormalalarmtype.h"
// New code begins
#include "historymanagerbuildingautomation.h"
#include "opcua_historicaldataconfigurationtype.h"
// New code ends
NmBuildingAutomation::NmBuildingAutomation()
: NodeManagerBase("urn:UnifiedAutomation:CppDemoServer:BuildingAutomation", OpcUa_True)
{
m_pCommIf = new BaCommunicationInterface;
// New code begins
// Create the HistoryManager responsible for this NodeManager
m_pHistoryManager = new HistoryManagerBuildingAutomation;
// Register the HistoryManager with the NodeManager base class
setHistoryManager(m_pHistoryManager);
// New code ends
}
NmBuildingAutomation::~NmBuildingAutomation()
{
delete m_pHistoryManager; // Add this line
delete m_pCommIf;
}

Step 3: Changes in the Information Model

After preparing the HistoryManager integration, we need to extend the information model and the address space to tell the client what kind of historical information is available.

There are two minor steps necessary to indicate that the Temperature variable provides historical data. The first step is to change the attribute’s AccessLevel and the Historizing to indicate that a client can read history in addition to the current value and that the value of the variable is currently historized.

The second step is to provide a HA Configuration object indicating the historical configuration for the variable. We can use the default settings of this object for the Temperature variable and we can share the object between all instances of the Temperature variable.

The changes of the attribute values and creation of the shared HA Configuration object are implemented with the following code in the method NmBuildingAutomation::createTypeNodes().

UaStatus NmBuildingAutomation::createTypeNodes()
{
...
/***************************************************************
* Create the Controller Type Instance declaration
*/
...
// Add Variable "Temperature" as AnalogItem
defaultValue.setDouble(0);
pAnalogItem = new OpcUa::AnalogItemType(
UaNodeId(Ba_ControllerType_Temperature, getNameSpaceIndex()),
"Temperature",
getNameSpaceIndex(),
defaultValue,
Ua_AccessLevel_CurrentRead /*+*/ | Ua_AccessLevel_HistoryRead /*+*/, // Modified Line
this);
pAnalogItem->setModellingRuleId(OpcUaId_ModellingRule_Mandatory);
addStatus = addNodeAndReference(pControllerType, pAnalogItem, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
// Set property values - the property nodes are already created
UaRange tempRange(0, 100);
pAnalogItem->setEURange(tempRange);
UaEUInformation tempEUInformation("", -1, UaLocalizedText("en", "\xc2\xb0\x46") /* °F */, UaLocalizedText("en", "Degrees Fahrenheit"));
pAnalogItem->setEngineeringUnits(tempEUInformation);
// New code begins
// Set Historizing flag to indicate that the server collects history for the variable
pAnalogItem->setHistorizing(OpcUa_True);
// Create HA Configuration node with all children
UaNodeId(Ba_ControllerType_Temperature_HA_Configuration, getNameSpaceIndex()),
"HA Configuration",
0,
this);
// Set configuration options or use default values
pHAConfig->setStepped(OpcUa_False);
// Add HA Configuration node
addStatus = addNodeAndReference(pAnalogItem, pHAConfig, OpcUaId_HasHistoricalConfiguration);
UA_ASSERT(addStatus.isGood());
// New code ends

We need to add an identifier for the HA Configuration to the file buildingautomationtypeids.h

/************************************************************
Controller Type and its instance declaration
*/
// Controller Type
#define Ba_ControllerType 1000
// Instance declaration
#define Ba_ControllerType_State 1001
#define Ba_ControllerType_Temperature 1002
#define Ba_ControllerType_TemperatureSetPoint 1003
#define Ba_ControllerType_PowerConsumption 1004
#define Ba_ControllerType_Start 1006
#define Ba_ControllerType_Stop 1007
#define Ba_ControllerType_Temperature_EURange 1008
#define Ba_ControllerType_Temperature_EngineeringUnits 1009
#define Ba_ControllerType_TemperatureSetPoint_EURange 1010
#define Ba_ControllerType_TemperatureSetPoint_EngineeringUnits 1011
#define Ba_ControllerType_StateCondition 1012
// New code begins
#define Ba_ControllerType_Temperature_HA_Configuration 1013
// New code ends
/************************************************************/

As a last step, we need to add a reference to the HA Configuration object to every Temperature variable instance. The following code is added to the constructor of the ControllerObject.

...
/**************************************************************
* Create the Controller components
*/
...
// Add Variable "Temperature"
pInstanceDeclaration = pNodeManager->getInstanceDeclarationVariable(Ba_ControllerType_Temperature);
UA_ASSERT(pInstanceDeclaration!=NULL);
// Create new variable and add it as component to this object
pAnalogItem = new OpcUa::AnalogItemType(this, pInstanceDeclaration, pNodeManager, m_pSharedMutex);
addStatus = pNodeManager->addNodeAndReference(this, pAnalogItem, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
// Set property values - the property nodes are already created
UaRange tempRange(0, 100);
pAnalogItem->setEURange(tempRange);
UaEUInformation tempEUInformation("", -1, UaLocalizedText("en", "\xc2\xb0\x46") /* °F */, UaLocalizedText("en", "Degrees Fahrenheit"));
pAnalogItem->setEngineeringUnits(tempEUInformation);
// Store information needed to access device
pUserData = new BaUserData(OpcUa_False, deviceAddress, 0);
pAnalogItem->setUserData(pUserData);
// Change value handling to get read and write calls to the node manager
// New code begins
// Add HA Configuration object reference
UaNode* pUaConfigNode = pNodeManager->getNode(UaNodeId(Ba_ControllerType_Temperature_HA_Configuration, pNodeManager->getNameSpaceIndex()));
if ( pUaConfigNode )
{
addStatus = pNodeManager->addUaReference(pAnalogItem, pUaConfigNode->getUaReferenceLists(), OpcUaId_HasHistoricalConfiguration);
UA_ASSERT(addStatus.isGood());
pUaConfigNode->releaseReference();
}
// New code ends

Step 4: Implement Internal Historizing

After the preparation of the address space, we need to prepare the historizing and the history read. For the historizing, we can use internal SDK functionality to subscribe for value changes and to store them in a buffer. We are not implementing a data storage in this example. We are just filling a memory buffer with the last 1000 values for each variable to historize.

In a first step, we will add a new class HistorizedVariable and we will extend the interface of HistoryManagerBuildingAutomation.

The class HistorizedVariable in the following code is used to manage the information about the variable to historize and to provide the data buffer in memory.

The history manager class is extended in the following code to add variables to historize and to start the internal historizing.

#ifndef __HISTORYMANAGERBUILDINGAUTOMATION_H__
#define __HISTORYMANAGERBUILDINGAUTOMATION_H__
#include "historymanagerbase.h"
// New code begins
#include "uabasenodes.h"
#include "servermanager.h"
class HistorizedVariable: public IOVariableCallback
{
UA_DISABLE_COPY(HistorizedVariable);
public:
HistorizedVariable();
virtual ~HistorizedVariable();
// Interface IOVariableCallback
void dataChange(const UaDataValue& dataValue);
// Mutex for the data and settings
UaMutex m_mutex;
// Variable to historize
UaVariable* m_pVariable;
// MonitoredItemId for internal monitoring
OpcUa_UInt32 m_monitoredItemId;
// Flag indicating if the variable is valid
OpcUa_Boolean m_isValid;
// Memory buffer for the changed values
std::list<UaDataValue> m_values;
};
// New code ends
class HistoryManagerBuildingAutomation : public HistoryManagerBase
{
UA_DISABLE_COPY(HistoryManagerBuildingAutomation);
public:
HistoryManagerBuildingAutomation();
virtual ~HistoryManagerBuildingAutomation();
// Interface HistoryManagerBase
virtual UaStatus readRaw (
const ServiceContext& serviceContext,
HistoryVariableHandle* pVariableHandle,
HistoryReadCPUserDataBase** ppContinuationPoint,
OpcUa_TimestampsToReturn timestampsToReturn,
OpcUa_UInt32 maxValues,
OpcUa_DateTime& startTime,
OpcUa_DateTime& endTime,
OpcUa_Boolean returnBounds,
OpcUa_HistoryReadValueId* /*pReadValueId*/,
UaDataValues& dataValues);
// Interface HistoryManagerBase
// New code begins
// Start up history manager for internal monitoring
void startUp(ServerManager* pServerManager);
// Shut down history manager to stop internal monitoring
void shutDown();
// Add a variable for historizing - must be called before startUp
void addVariableToHistorize(UaNode* pNode);
// New code ends
private:
// New code begins
// List of variable to historize
std::map<UaNodeId, HistorizedVariable*> m_mapVariables;
// Internal session used for monitoring
Session* m_pSession;
// Server manager used for internal monitoring
ServerManager* m_pServerManager;
// New code ends
};
#endif // __HISTORYMANAGERBUILDINGAUTOMATION_H__

The following code contains the corresponding implementation for the new class HistorizedVariable and the additional functionality in HistoryManagerBuildingAutomation.

#include "historymanagerbuildingautomation.h"
// New code begins
#include "serverconfig.h"
#include "variablehandleuanode.h"
#define HISTORYMANAGERBA_SAMPLING_INTERVAL 500
#define HISTORYMANAGERBA_QUEUE_SIZE 2000
// New code ends
// History manager specific continuation point class
class HistoryReadCPUserDataBA : public HistoryReadCPUserDataBase
{
public:
HistoryReadCPUserDataBA(const UaNodeId& affectedNode, HistoryManager::TransactionType historyTransactionType)
: HistoryReadCPUserDataBase(affectedNode, historyTransactionType)
{}
~HistoryReadCPUserDataBA(){}
// We use the next timestamp to return as continuation point
UaDateTime m_newStartTime;
};
HistoryManagerBuildingAutomation::HistoryManagerBuildingAutomation()
// New code begins
: m_pSession(NULL),
m_pServerManager(NULL)
// New code ends
{
}
HistoryManagerBuildingAutomation::~HistoryManagerBuildingAutomation()
{
// New code begins
std::map<UaNodeId, HistorizedVariable*>::iterator it;
for ( it=m_mapVariables.begin(); it!=m_mapVariables.end(); it++ )
{
delete it->second;
it->second = NULL;
}
// New code ends
}
// Add the following code snippet
// Add a variable for historizing - must be called before startUp
void HistoryManagerBuildingAutomation::addVariableToHistorize(UaNode* pNode)
{
if ( (pNode != NULL) && (pNode->nodeClass() == OpcUa_NodeClass_Variable) )
{
HistorizedVariable* pVariable = new HistorizedVariable;
pVariable->m_pVariable = (UaVariable*)pNode;
m_mapVariables[pNode->nodeId()] = pVariable;
}
}
// Start up history manager for internal monitoring
void HistoryManagerBuildingAutomation::startUp(ServerManager* pServerManager)
{
UaStatus status;
m_pServerManager = pServerManager;
// Create internal session
m_pSession = pServerManager->createInternalSession("InternalHistorizing", "en", NULL);
if (m_pSession)
{
// Create a SessionUserContext with root priviledges
// provide full access to everything for the internal session
pUserContext->setIsRoot(true);
m_pSession->setUserContext(pUserContext);
m_pSession->setClientUserId("InternalHistorizing");
pUserContext->releaseReference();
// Set enncryption flag to ensure full access to data that requires encryption
m_pSession->setSecureChannelMessageSecurityMode(OpcUa_MessageSecurityMode_SignAndEncrypt);
}
// Create internal monitored items for historizing
if ( status.isGood() )
{
DataMonitoredItemSpecArray dataMonitoredItems;
OpcUa_UInt32 i = 0;
std::map<UaNodeId, HistorizedVariable*>::iterator it;
dataMonitoredItems.create(m_mapVariables.size());
// Collect information for the monitored items to create
for(it=m_mapVariables.begin(); it!=m_mapVariables.end(); it++)
{
HistorizedVariable* pVariable = it->second;
pVariable->m_pVariable->nodeId().copyTo(&dataMonitoredItems[i].m_itemToMonitor.NodeId);
dataMonitoredItems[i].m_itemToMonitor.AttributeId = OpcUa_Attributes_Value;
dataMonitoredItems[i].m_requestedSamplingInterval = HISTORYMANAGERBA_SAMPLING_INTERVAL;
dataMonitoredItems[i].m_pDataCallback = pVariable;
i++;
}
// Create the monitored items
status = pServerManager->createDataMonitoredItems(m_pSession, dataMonitoredItems);
if ( status.isGood() )
{
i = 0;
// Store the create results
for(it=m_mapVariables.begin(); it!=m_mapVariables.end(); it++)
{
HistorizedVariable* pVariable = it->second;
if ( dataMonitoredItems[i].m_createResult.isGood() )
{
pVariable->m_isValid = OpcUa_True;
pVariable->m_monitoredItemId = dataMonitoredItems[i].m_monitoredItemId;
if ( dataMonitoredItems[i].m_isInitialValueProvided != OpcUa_False )
{
pVariable->dataChange(dataMonitoredItems[i].m_initialValue);
}
}
i++;
}
}
}
}
void HistoryManagerBuildingAutomation::shutDown()
{
std::map<UaNodeId, HistorizedVariable*>::iterator it;
// Remove monitored items
if ( m_pServerManager && m_pSession )
{
OpcUa_UInt32 i = 0;
UaUInt32Array monitoredItemIds;
monitoredItemIds.create(m_mapVariables.size());
i = 0;
for(it=m_mapVariables.begin(); it!=m_mapVariables.end(); it++)
{
HistorizedVariable* pVariable = it->second;
monitoredItemIds[i] = pVariable->m_monitoredItemId;
i++;
}
m_pServerManager->deleteMonitoredItems(m_pSession, monitoredItemIds, results);
}
for ( it=m_mapVariables.begin(); it!=m_mapVariables.end(); it++ )
{
delete it->second;
it->second = NULL;
}
m_mapVariables.clear();
// Release Session object
if( m_pSession )
{
m_pSession->releaseReference();
m_pSession = NULL;
}
}
HistorizedVariable::HistorizedVariable()
: m_pVariable(NULL),
m_monitoredItemId(0),
m_isValid(OpcUa_False)
{
}
HistorizedVariable::~HistorizedVariable()
{
}
// Handle data changes from the monitored items and store the values in the
// memory buffer
void HistorizedVariable::dataChange(const UaDataValue& dataValue)
{
UaMutexLocker locker(&m_mutex);
m_values.push_back(dataValue);
if ( m_values.size() > HISTORYMANAGERBA_QUEUE_SIZE )
{
m_values.erase(m_values.begin());
}
}
UaDataValue HistorizedVariable::getLastValue()
{
// Default implementation - method is not needed
return ret;
}

The last part of the historizing implementation is the use of the new functionality in the node manager.

As a first additional step the flag in the server capabilities indicating support of historical access for data is set.

The code that creates the controller objects is extended to register the Temperature variables of the controller objects with the history manager.

In addition, the startUp of history manager is called at the end of the node manager start up to start internal historizing. The shutDown of the history manager is called in the shut down of the node manager to stop the internal historizing.

UaStatus NmBuildingAutomation::afterStartUp()
{
...
createTypeNodes();
// New code begins
// We support historical data access
m_pHistoryManager->pHistoryServerCapabilities()->setAccessHistoryDataCapability(OpcUa_True);
// New code ends
...
/**************************************************************
* Create the Controller Object Instances
*/
for ( i=0; i<count; i++ )
{
ret = m_pCommIf->getControllerConfig(
i,
controllerType,
sControllerName,
controllerAddress);
if ( controllerType == BaCommunicationInterface::AIR_CONDITIONER )
{
pAirConditioner = new AirConditionerControllerObject(
sControllerName,
UaNodeId(sControllerName, getNameSpaceIndex()),
m_defaultLocaleId,
this,
controllerAddress,
m_pCommIf);
ret = addNodeAndReference(pFolder, pAirConditioner, OpcUaId_Organizes);
UA_ASSERT(ret.isGood());
// Add HasNotifier reference from alarm area to controller object
ret = addUaReference(pAreaAirConditioner, pAirConditioner, OpcUaId_HasNotifier);
UA_ASSERT(ret.isGood());
// Register event notifier tree
registerEventNotifier(pAreaAirConditioner->nodeId(), pAirConditioner->nodeId());
// New code begins
// Register Temperature node for historizing
m_pHistoryManager->addVariableToHistorize(pAirConditioner->getTargetNodeByBrowseName(UaQualifiedName("Temperature", getNameSpaceIndex())));
// New code ends
}
else
{
pFurnace = new FurnaceControllerObject(
sControllerName,
UaNodeId(sControllerName, getNameSpaceIndex()),
m_defaultLocaleId,
this,
controllerAddress,
m_pCommIf);
ret = addNodeAndReference(pFolder, pFurnace, OpcUaId_Organizes);
UA_ASSERT(ret.isGood());
// Add HasNotifier reference from alarm area to controller object
ret = addUaReference(pAreaFurnace, pFurnace, OpcUaId_HasNotifier);
UA_ASSERT(ret.isGood());
// Register event notifier tree
registerEventNotifier(pAreaFurnace->nodeId(), pFurnace->nodeId());
// New code begins
// Register Temperature node for historizing
m_pHistoryManager->addVariableToHistorize(pFurnace->getTargetNodeByBrowseName(UaQualifiedName("Temperature", getNameSpaceIndex())));
// New code ends
}
}
// New code begins
// Set started flag - this is necessary to be able to subscribe for data changes in HistoryManagerBuildingAutomation->startUp
m_isStarted = OpcUa_True;
m_pHistoryManager->startUp(m_pServerManager);
// New code ends
return ret;
}
UaStatus NmBuildingAutomation::beforeShutDown()
{
UaStatus ret;
m_pHistoryManager->shutDown(); // Add this line
return ret;
}

Step 5: Implement History Read Raw

The following code contains the implementation for history read raw based on the data buffer in memory.

UaStatus HistoryManagerBuildingAutomation::readRaw (
const ServiceContext& serviceContext,
HistoryVariableHandle* pVariableHandle,
HistoryReadCPUserDataBase** ppContinuationPoint,
OpcUa_TimestampsToReturn timestampsToReturn,
OpcUa_UInt32 maxValues,
OpcUa_DateTime& startTime,
OpcUa_DateTime& endTime,
OpcUa_Boolean returnBounds,
OpcUa_HistoryReadValueId* pReadValueId,
UaDataValues& dataValues)
{
// New code begins
OpcUa_ReferenceParameter(serviceContext);
OpcUa_ReferenceParameter(pReadValueId);
// The NodeManagerBase is creating history variable handles of the type HistoryVariableHandleUaNode
if ( pVariableHandle && (pVariableHandle->getHandleImplementation() != HistoryVariableHandle::UA_NODE) )
{
// Unexpected handle type
return OpcUa_BadNodeIdUnknown;
}
HistoryVariableHandleUaNode* pUaNodeVariableHandle = (HistoryVariableHandleUaNode*)pVariableHandle;
// Check if the NodeId provided in the HistoryVariableHandleUaNode is a valid node to read
std::map<UaNodeId, HistorizedVariable*>::iterator it;
it = m_mapVariables.find(pUaNodeVariableHandle->pUaNode()->nodeId());
if ( (it == m_mapVariables.end()) || (it->second->m_isValid == OpcUa_False) )
{
return OpcUa_BadNodeIdUnknown;
}
UaStatus ret;
HistorizedVariable* pVariable = it->second;
OpcUa_UInt32 i = 0;
UaDateTime dtStart(startTime);
UaDateTime dtEnd(endTime);
OpcUa_Int64 iStart = dtStart;
OpcUa_Int64 iEnd = dtEnd;
OpcUa_DateTime nullDateTime;
OpcUa_DateTime_Initialize(&nullDateTime);
// Check if we have a continuation point
HistoryReadCPUserDataBA* pContinuationPoint = (HistoryReadCPUserDataBA*)*ppContinuationPoint;
if ( pContinuationPoint )
{
// Set time from continuation point as new start time
dtStart = pContinuationPoint->m_newStartTime;
iStart = dtStart;
// Delete continuation point
delete pContinuationPoint;
// Set ppContinuationPoint point in/out to NULL
*ppContinuationPoint = NULL;
}
if ( maxValues == 0 )
{
maxValues = OpcUa_Int32_Max;
}
UaNodeId nodeIdToRead(pReadValueId->NodeId);
// Lock access to list of values
UaMutexLocker lock(&pVariable->m_mutex);
if ( iStart < iEnd )
{
// Read in forward direction
std::list<UaDataValue>::iterator itValues;
dataValues.create(pVariable->m_values.size());
for ( itValues=pVariable->m_values.begin(); itValues!=pVariable->m_values.end(); itValues++ )
{
// Check if we reached max values
if ( i == maxValues )
{
// Create a continuation point
HistoryReadCPUserDataBA* pContinuationPoint = new HistoryReadCPUserDataBA(nodeIdToRead, HistoryManager::TransactionReadRaw);
// Use next timestamp as starting point
pContinuationPoint->m_newStartTime = itValues->serverTimestamp();
// Return continuation point
*ppContinuationPoint = pContinuationPoint;
break;
}
UaDateTime dtVal(itValues->serverTimestamp());
OpcUa_Int64 iVal = dtVal;
if ( iVal < iStart )
{
// We have not found the start time yet
continue;
}
if ( iVal > iEnd )
{
// We are behind the end time
break;
}
if ( (i == 0) && (returnBounds != OpcUa_False) && (itValues != pVariable->m_values.begin()) )
{
// Bounds handling
itValues--;
itValues->copyTo(&dataValues[i]);
// Delete not requested timestamps
if ( (timestampsToReturn == OpcUa_TimestampsToReturn_Server) ||
(timestampsToReturn == OpcUa_TimestampsToReturn_Neither) )
{
dataValues[i].SourceTimestamp = nullDateTime;
}
if ( (timestampsToReturn == OpcUa_TimestampsToReturn_Source) ||
(timestampsToReturn == OpcUa_TimestampsToReturn_Neither) )
{
dataValues[i].ServerTimestamp = nullDateTime;
}
itValues++;
i++;
}
itValues->copyTo(&dataValues[i]);
// Delete not requested timestamps
if ( (timestampsToReturn == OpcUa_TimestampsToReturn_Server) ||
(timestampsToReturn == OpcUa_TimestampsToReturn_Neither) )
{
dataValues[i].SourceTimestamp = nullDateTime;
}
if ( (timestampsToReturn == OpcUa_TimestampsToReturn_Source) ||
(timestampsToReturn == OpcUa_TimestampsToReturn_Neither) )
{
dataValues[i].ServerTimestamp = nullDateTime;
}
i++;
}
// Make size smaller if necessary
dataValues.resize(i);
}
else
{
// Read in inverse direction
std::list<UaDataValue>::reverse_iterator ritValues;
dataValues.create(pVariable->m_values.size());
for ( ritValues=pVariable->m_values.rbegin(); ritValues!=pVariable->m_values.rend(); ritValues++ )
{
// Check if we reached max values
if ( i == maxValues )
{
// Create a continuation point
HistoryReadCPUserDataBA* pContinuationPoint = new HistoryReadCPUserDataBA(nodeIdToRead, HistoryManager::TransactionReadRaw);
// Use next timestamp as starting point
pContinuationPoint->m_newStartTime = ritValues->serverTimestamp();
// Return continuation point
*ppContinuationPoint = pContinuationPoint;
break;
}
UaDateTime dtVal(ritValues->serverTimestamp());
OpcUa_Int64 iVal = dtVal;
if ( iVal > iStart )
{
// We have not found the start time yet
continue;
}
if ( iVal < iEnd )
{
// We are behind the end time
break;
}
if ( (i == 0) && (returnBounds != OpcUa_False) && (ritValues != pVariable->m_values.rbegin()) )
{
// Bounds handling
ritValues--;
ritValues->copyTo(&dataValues[i]);
// Delete not requested timestamps
if ( (timestampsToReturn == OpcUa_TimestampsToReturn_Server) ||
(timestampsToReturn == OpcUa_TimestampsToReturn_Neither) )
{
dataValues[i].SourceTimestamp = nullDateTime;
}
if ( (timestampsToReturn == OpcUa_TimestampsToReturn_Source) ||
(timestampsToReturn == OpcUa_TimestampsToReturn_Neither) )
{
dataValues[i].ServerTimestamp = nullDateTime;
}
ritValues++;
i++;
}
ritValues->copyTo(&dataValues[i]);
// Delete not requested timestamps
if ( (timestampsToReturn == OpcUa_TimestampsToReturn_Server) ||
(timestampsToReturn == OpcUa_TimestampsToReturn_Neither) )
{
dataValues[i].SourceTimestamp = nullDateTime;
}
if ( (timestampsToReturn == OpcUa_TimestampsToReturn_Source) ||
(timestampsToReturn == OpcUa_TimestampsToReturn_Neither) )
{
dataValues[i].ServerTimestamp = nullDateTime;
}
i++;
}
// Make size smaller if necessary
dataValues.resize(i);
}
// New code end
return ret;
}

Step 6: Test History Read with UaExpert

The history plugin of the UaExpert can be used to read the history of the Temperature variables.

Connect to the server with UaExpert and open the History Trend View. Drag and drop some Temperature variables to the Configuration window and click on Update. The historical data will be shown in the graph below (see figure 7-2

Figure 7-2 Historical Trend View in UaExpert

serverlesson07_expert.png