UA Server SDK C++ Bundle  1.3.2.200
 All Data Structures Namespaces Functions Variables Typedefs Enumerations Enumerator Groups Pages
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.

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

Content:

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();
// 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,
UaDataValues& dataValues);
private:
};
#endif // __HISTORYMANAGERBUILDINGAUTOMATION_H__

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

#include "historymanagerbuildingautomation.h"
HistoryManagerBuildingAutomation::HistoryManagerBuildingAutomation()
{
}
HistoryManagerBuildingAutomation::~HistoryManagerBuildingAutomation()
{
}
const ServiceContext& serviceContext,
HistoryVariableHandle* pVariableHandle,
HistoryReadCPUserDataBase** ppContinuationPoint,
OpcUa_TimestampsToReturn timestampsToReturn,
OpcUa_UInt32 maxValues,
OpcUa_DateTime& startTime,
OpcUa_DateTime& endTime,
OpcUa_Boolean returnBounds,
UaDataValues& dataValues)
{
UaStatus ret = OpcUa_BadNotImplemented;
return ret;
}

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 (nmbuildingautomation.h) for the NmBuildingAutomation class.

#include "nodemanagerbase.h"
class BaCommunicationInterface;
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
class HistoryManagerBuildingAutomation;
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++
class NmBuildingAutomation : public NodeManagerBase
private:
UaStatus createTypeNodes();
BaCommunicationInterface* m_pCommIf;
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
HistoryManagerBuildingAutomation* m_pHistoryManager;
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++
};
#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 begin +++++++++++++++++++++++++++++++++++++++++++
#include "historymanagerbuildingautomation.h"
#include "opcua_historicaldataconfigurationtype.h"
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++
NmBuildingAutomation::NmBuildingAutomation()
: NodeManagerBase("MyUaServer/BuildingAutomation", OpcUa_True)
{
m_pCommIf = new BaCommunicationInterface;
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
// Create the HistoryManager responsible for this NodeManager
m_pHistoryManager = new HistoryManagerBuildingAutomation;
// Register the HistoryManager with the NodeManager base class
setHistoryManager(m_pHistoryManager);
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++
}
NmBuildingAutomation::~NmBuildingAutomation()
{
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
delete m_pHistoryManager;
delete m_pCommIf;
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++
}

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 attributes 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().

// Add Variable "Temperature" as AnalogItem
defaultValue.setDouble(0);
pAnalogItem = new OpcUa::AnalogItemType(
UaNodeId(Ba_ControllerType_Temperature, getNameSpaceIndex()),
"Temperature",
getNameSpaceIndex(),
defaultValue,
//++++++ changed code begin +++++++++++++++++++++++++++++++++++++++
Ua_AccessLevel_CurrentRead | Ua_AccessLevel_HistoryRead,
//++++++ changed code end +++++++++++++++++++++++++++++++++++++++++
this);
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 begin +++++++++++++++++++++++++++++++++++++++++++
// 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 end +++++++++++++++++++++++++++++++++++++++++++++

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 begin +++++++++++++++++++++++++++++++++++++++++++
#define Ba_ControllerType_Temperature_HA_Configuration 1013
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++
/************************************************************/

As 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.

// 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 begin +++++++++++++++++++++++++++++++++++++++++++
// 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 end +++++++++++++++++++++++++++++++++++++++++++++

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 historze and to start the internal historizing.

#ifndef __HISTORYMANAGERBUILDINGAUTOMATION_H__
#define __HISTORYMANAGERBUILDINGAUTOMATION_H__
#include "historymanagerbase.h"
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
#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 end +++++++++++++++++++++++++++++++++++++++++++++
class HistoryManagerBuildingAutomation : public HistoryManagerBase
{
UA_DISABLE_COPY(HistoryManagerBuildingAutomation);
public:
HistoryManagerBuildingAutomation();
virtual ~HistoryManagerBuildingAutomation();
// 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,
UaDataValues& dataValues);
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
// 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);
private:
// 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 end +++++++++++++++++++++++++++++++++++++++++++++
};
#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 begin +++++++++++++++++++++++++++++++++++++++++++
#include "serverconfig.h"
#include "variablehandleuanode.h"
#define HISTORYMANAGERBA_SAMPLING_INTERVAL 500
#define HISTORYMANAGERBA_QUEUE_SIZE 1000
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++
HistoryManagerBuildingAutomation::HistoryManagerBuildingAutomation()
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
: m_pSession(NULL),
m_pServerManager(NULL)
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++
{
}
HistoryManagerBuildingAutomation::~HistoryManagerBuildingAutomation()
{
}
// 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->getServerConfig()->createSession(0, UaNodeId());
if ( m_pSession )
{
status = m_pSession->open("InternalHistorizing", UaByteString(), OpcUa_UInt32_Max).statusCode();
if ( status.isGood() )
{
UaStringArray localeIDs;
localeIDs.create(1);
UaString sTemp("en");
sTemp.copyTo(&localeIDs[0]);
status = m_pSession->activate(0, NULL, localeIDs).statusCode();
}
}
// 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()
{
// Remove monitored items
if ( m_pServerManager && m_pSession )
{
OpcUa_UInt32 i = 0;
UaUInt32Array monitoredItemIds;
UaStatusCodeArray results;
std::map<UaNodeId, HistorizedVariable*>::iterator it;
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);
}
// 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
{
UaMutexLocker locker(&m_mutex);
m_values.push_back(dataValue);
if ( m_values.size() > HISTORYMANAGERBA_QUEUE_SIZE )
{
m_values.erase(m_values.begin());
}
}
{
// 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.

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.

/**************************************************************
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 begin +++++++++++++++++++++++++++++++++++++++++++
// Register Temperature node for historizing
m_pHistoryManager->addVariableToHistorize(pAirConditioner->getTargetNodeByBrowseName(UaQualifiedName("Temperature", getNameSpaceIndex())));
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++
}
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 begin +++++++++++++++++++++++++++++++++++++++++++
// Register Temperature node for historizing
m_pHistoryManager->addVariableToHistorize(pFurnace->getTargetNodeByBrowseName(UaQualifiedName("Temperature", getNameSpaceIndex())));
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++
}
}
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
// 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 end +++++++++++++++++++++++++++++++++++++++++++++
return ret;
}
{
UaStatus ret;
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
m_pHistoryManager->shutDown();
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++
return ret;
}

Implement history read raw

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

const ServiceContext& serviceContext,
HistoryVariableHandle* pVariableHandle,
HistoryReadCPUserDataBase** ppContinuationPoint,
OpcUa_TimestampsToReturn timestampsToReturn,
OpcUa_UInt32 maxValues,
OpcUa_DateTime& startTime,
OpcUa_DateTime& endTime,
OpcUa_Boolean returnBounds,
UaDataValues& dataValues)
{
UaStatus ret;
std::map<UaNodeId, HistorizedVariable*>::iterator it;
OpcUa_UInt32 i = 0;
UaDateTime dtStart(startTime);
UaDateTime dtEnd(endTime);
OpcUa_Int64 iStart = dtStart;
OpcUa_Int64 iEnd = dtEnd;
// The NodeManagerBase is creating history variable handles of the type HistoryVariableHandleUaNode
HistoryVariableHandleUaNode* pUaNodeVariableHandle = (HistoryVariableHandleUaNode*)pVariableHandle;
if ( maxValues == 0 )
{
maxValues = OpcUa_Int32_Max;
}
// Check if the
it = m_mapVariables.find(pUaNodeVariableHandle->pUaNode()->nodeId());
if ( (it == m_mapVariables.end()) || (it->second->m_isValid == OpcUa_False) )
{
ret = OpcUa_BadNodeIdUnknown;
}
else
{
HistorizedVariable* pVariable = it->second;
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++ )
{
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]);
itValues++;
i++;
}
itValues->copyTo(&dataValues[i]);
i++;
// Check if we reached max values
if ( i == maxValues )
{
break;
}
}
// 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++ )
{
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]);
ritValues++;
i++;
}
ritValues->copyTo(&dataValues[i]);
i++;
// Check if we reached max values
if ( i == maxValues )
{
break;
}
}
// Make size smaller if necessary
dataValues.resize(i);
}
}
return ret;
}

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