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"
{
UA_DISABLE_COPY(HistoryManagerBuildingAutomation);
public:
HistoryManagerBuildingAutomation();
virtual ~HistoryManagerBuildingAutomation();
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()
{
}
UaStatus HistoryManagerBuildingAutomation::readRaw (
OpcUa_TimestampsToReturn timestampsToReturn,
OpcUa_UInt32 maxValues,
OpcUa_DateTime& startTime,
OpcUa_DateTime& endTime,
OpcUa_Boolean returnBounds,
UaDataValues& dataValues)
{
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;
class HistoryManagerBuildingAutomation;
private:
BaCommunicationInterface* m_pCommIf;
HistoryManagerBuildingAutomation* m_pHistoryManager;
};
#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"
#include "historymanagerbuildingautomation.h"
#include "opcua_historicaldataconfigurationtype.h"
NmBuildingAutomation::NmBuildingAutomation()
{
m_pCommIf = new BaCommunicationInterface;
m_pHistoryManager = new HistoryManagerBuildingAutomation;
setHistoryManager(m_pHistoryManager);
}
NmBuildingAutomation::~NmBuildingAutomation()
{
delete m_pHistoryManager;
delete m_pCommIf;
}
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().
UaNodeId(Ba_ControllerType_Temperature, getNameSpaceIndex()),
"Temperature",
getNameSpaceIndex(),
defaultValue,
Ua_AccessLevel_CurrentRead | Ua_AccessLevel_HistoryRead,
this);
addStatus = addNodeAndReference(pControllerType, pAnalogItem, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
pAnalogItem->setEURange(tempRange);
pAnalogItem->setEngineeringUnits(tempEUInformation);
UaNodeId(Ba_ControllerType_Temperature_HA_Configuration, getNameSpaceIndex()),
"HA Configuration",
0,
this);
pHAConfig->setStepped(OpcUa_False);
addStatus = addNodeAndReference(pAnalogItem, pHAConfig, OpcUaId_HasHistoricalConfiguration);
UA_ASSERT(addStatus.isGood());
We need to add an identifier for the HA Configuration to the file buildingautomationtypeids.h
#define Ba_ControllerType 1000
#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
#define Ba_ControllerType_Temperature_HA_Configuration 1013
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.
pInstanceDeclaration = pNodeManager->getInstanceDeclarationVariable(Ba_ControllerType_Temperature);
UA_ASSERT(pInstanceDeclaration!=NULL);
addStatus = pNodeManager->addNodeAndReference(this, pAnalogItem, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
pAnalogItem->setEURange(tempRange);
pAnalogItem->setEngineeringUnits(tempEUInformation);
pUserData = new BaUserData(OpcUa_False, deviceAddress, 0);
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();
}
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"
#include "uabasenodes.h"
#include "servermanager.h"
{
UA_DISABLE_COPY(HistorizedVariable);
public:
HistorizedVariable();
virtual ~HistorizedVariable();
OpcUa_UInt32 m_monitoredItemId;
OpcUa_Boolean m_isValid;
std::list<UaDataValue> m_values;
};
{
UA_DISABLE_COPY(HistoryManagerBuildingAutomation);
public:
HistoryManagerBuildingAutomation();
virtual ~HistoryManagerBuildingAutomation();
OpcUa_TimestampsToReturn timestampsToReturn,
OpcUa_UInt32 maxValues,
OpcUa_DateTime& startTime,
OpcUa_DateTime& endTime,
OpcUa_Boolean returnBounds,
UaDataValues& dataValues);
void shutDown();
void addVariableToHistorize(
UaNode* pNode);
private:
std::map<UaNodeId, HistorizedVariable*> m_mapVariables;
};
#endif // __HISTORYMANAGERBUILDINGAUTOMATION_H__
The following code contains the corresponding implementation for the new class HistorizedVariable and the additional functionality in HistoryManagerBuildingAutomation.
#include "historymanagerbuildingautomation.h"
#include "serverconfig.h"
#include "variablehandleuanode.h"
#define HISTORYMANAGERBA_SAMPLING_INTERVAL 500
#define HISTORYMANAGERBA_QUEUE_SIZE 1000
HistoryManagerBuildingAutomation::HistoryManagerBuildingAutomation()
: m_pSession(NULL),
m_pServerManager(NULL)
{
}
HistoryManagerBuildingAutomation::~HistoryManagerBuildingAutomation()
{
}
void HistoryManagerBuildingAutomation::addVariableToHistorize(
UaNode* pNode)
{
if ( (pNode != NULL) && (pNode->
nodeClass() == OpcUa_NodeClass_Variable) )
{
HistorizedVariable* pVariable = new HistorizedVariable;
m_mapVariables[pNode->
nodeId()] = pVariable;
}
}
void HistoryManagerBuildingAutomation::startUp(
ServerManager* pServerManager)
{
m_pServerManager = pServerManager;
if ( m_pSession )
{
{
UaStringArray localeIDs;
localeIDs.create(1);
sTemp.copyTo(&localeIDs[0]);
status = m_pSession->activate(0, NULL, localeIDs).
statusCode();
}
}
{
OpcUa_UInt32 i = 0;
std::map<UaNodeId, HistorizedVariable*>::iterator it;
dataMonitoredItems.create(m_mapVariables.size());
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++;
}
{
i = 0;
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()
{
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);
}
if( m_pSession )
{
m_pSession->releaseReference();
m_pSession = NULL;
}
}
HistorizedVariable::HistorizedVariable()
: m_pVariable(NULL),
m_monitoredItemId(0),
m_isValid(OpcUa_False)
{
}
HistorizedVariable::~HistorizedVariable()
{
}
void HistorizedVariable::dataChange(
const UaDataValue& dataValue)
{
m_values.push_back(dataValue);
if ( m_values.size() > HISTORYMANAGERBA_QUEUE_SIZE )
{
m_values.erase(m_values.begin());
}
}
{
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.
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());
ret = addUaReference(pAreaAirConditioner, pAirConditioner, OpcUaId_HasNotifier);
UA_ASSERT(ret.isGood());
registerEventNotifier(pAreaAirConditioner->
nodeId(), pAirConditioner->nodeId());
m_pHistoryManager->addVariableToHistorize(pAirConditioner->getTargetNodeByBrowseName(
UaQualifiedName(
"Temperature", getNameSpaceIndex())));
}
else
{
pFurnace = new FurnaceControllerObject(
sControllerName,
UaNodeId(sControllerName, getNameSpaceIndex()),
m_defaultLocaleId,
this,
controllerAddress,
m_pCommIf);
ret = addNodeAndReference(pFolder, pFurnace, OpcUaId_Organizes);
UA_ASSERT(ret.isGood());
ret = addUaReference(pAreaFurnace, pFurnace, OpcUaId_HasNotifier);
UA_ASSERT(ret.isGood());
registerEventNotifier(pAreaFurnace->nodeId(), pFurnace->nodeId());
m_pHistoryManager->addVariableToHistorize(pFurnace->getTargetNodeByBrowseName(
UaQualifiedName(
"Temperature", getNameSpaceIndex())));
}
}
m_isStarted = OpcUa_True;
m_pHistoryManager->startUp(m_pServerManager);
return ret;
}
UaStatus NmBuildingAutomation::beforeShutDown()
{
m_pHistoryManager->shutDown();
return ret;
}
Implement history read raw
The following code contains the implementation for history read raw based on the data buffer in memory.
UaStatus HistoryManagerBuildingAutomation::readRaw (
OpcUa_TimestampsToReturn timestampsToReturn,
OpcUa_UInt32 maxValues,
OpcUa_DateTime& startTime,
OpcUa_DateTime& endTime,
OpcUa_Boolean returnBounds,
UaDataValues& dataValues)
{
std::map<UaNodeId, HistorizedVariable*>::iterator it;
OpcUa_UInt32 i = 0;
OpcUa_Int64 iStart = dtStart;
OpcUa_Int64 iEnd = dtEnd;
if ( maxValues == 0 )
{
maxValues = OpcUa_Int32_Max;
}
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;
if ( iStart < iEnd )
{
std::list<UaDataValue>::iterator itValues;
dataValues.create(pVariable->m_values.size());
for ( itValues=pVariable->m_values.begin(); itValues!=pVariable->m_values.end(); itValues++ )
{
OpcUa_Int64 iVal = dtVal;
if ( iVal < iStart )
{
continue;
}
if ( iVal > iEnd )
{
break;
}
if ( (i == 0) && (returnBounds != OpcUa_False) && (itValues != pVariable->m_values.begin()) )
{
itValues--;
itValues->copyTo(&dataValues[i]);
itValues++;
i++;
}
itValues->copyTo(&dataValues[i]);
i++;
if ( i == maxValues )
{
break;
}
}
dataValues.resize(i);
}
else
{
std::list<UaDataValue>::reverse_iterator ritValues;
dataValues.create(pVariable->m_values.size());
for ( ritValues=pVariable->m_values.rbegin(); ritValues!=pVariable->m_values.rend(); ritValues++ )
{
OpcUa_Int64 iVal = dtVal;
if ( iVal > iStart )
{
continue;
}
if ( iVal < iEnd )
{
break;
}
if ( (i == 0) && (returnBounds != OpcUa_False) && (ritValues != pVariable->m_values.rbegin()) )
{
ritValues--;
ritValues->copyTo(&dataValues[i]);
ritValues++;
i++;
}
ritValues->copyTo(&dataValues[i]);
i++;
if ( i == maxValues )
{
break;
}
}
dataValues.resize(i);
}
}
return ret;
}
The history plugin of the UaExpert can be used to read the history of the Temperature variables.