UA Bundle SDK .NET  2.2.1.258
 All Classes Namespaces Functions Variables Enumerations Enumerator Properties Events Groups Pages
Lesson 7: Adding support for Historical Access for Data

This lesson uses the model used for Lesson 3:

Figure 7.1:

Adding History

In this lesson we will add code that creates an in-memory historical archive for the Variables highlighted in red.

Step 1:

The first step requires the UnderlyingSystem to be modified to store the history and provide access to it via an object that implements the IHistoryDataSource.

The following method on the UnderlyingSystem class provides that access:

public GetHistoryDataSource(int blockAddress, int tag);

The implementation in this example is an instance of the InMemoryHistoryDataSource class which stores the values for a Variable as they change. The IHistoryDataSource is used by the NodeManager to access the raw data during a ReadRaw, ReadProcessed or ReadAtTime operation.

An implementation with an external archive such as an SQL database would replace the InMemoryHistoryDataSource with a class that knows how to access the external archive.

Step 2:

The NodeManager needs to indicate which Variables have history. This is done by setting the AccessLevel on the Variable and by adding the HA Configuration Object as a child of the Variable.

The following code added to Lesson7aNodeManager.Startup() does this:

if (property.History != null)
{
// enable historical access.
variable.AccessLevel |= AccessLevels.HistoryRead;
variable.UserAccessLevel |= AccessLevels.HistoryRead;
// this ensures the address is passed in history handles.
SetNodeUserData(variable.NodeId, new SystemAddress()
{
Address = block.Address,
Offset = property.Offset
});
NodeId configurationId = new NodeId(
block.Name + "." + property.Name + "." +
BrowseNames.HAConfiguration, InstanceNamespaceIndex);
// create historical configuration object.
settings = new CreateObjectSettings()
{
ParentNodeId = variable.NodeId,
ReferenceTypeId = ReferenceTypeIds.HasHistoricalConfiguration,
RequestedNodeId = configurationId,
BrowseName = BrowseNames.HAConfiguration,
TypeDefinitionId = UnifiedAutomation.UaBase.ObjectTypeIds.HistoricalDataConfigurationType,
// add optional properties.
OptionalBrowsePaths = new string[]
{
BrowseNames.StartOfArchive,
BrowseNames.StartOfOnlineArchive
}
};
CreateObject(Server.DefaultRequestContext, settings);
// link model to node.
LinkModelToNode(configurationId, property.HistoryConfiguration, null, null, 500);
}

Step 3:

The NodeManager now needs to link calls to historical read to the IHistoryDataSource. This is done by overriding the CreateHistoryContinuationPoint method on BaseNodeManager:

protected override HistoryDataReadRawContinuationPoint CreateHistoryContinuationPoint(
RequestContext context,
ReadRawModifiedDetails details,
HistoryDataHandle nodeHandle,
string indexRange,
QualifiedName dataEncoding)
{
// the data passed to SetNodeUserData is returned as the NodeData in the handle.
SystemAddress address = nodeHandle.NodeData as SystemAddress;
if (address == null)
{
return null;
}
IHistoryDataSource datasource = m_system.GetHistoryDataSource(address.Address, address.Offset);
if (datasource == null)
{
return null;
}
HistoryDataRawReader reader = new HistoryDataRawReader();
reader.Initialize(context, datasource, details);
HistoryDataReadRawContinuationPoint cp = new HistoryDataReadRawContinuationPoint()
{
Reader = reader,
NumValuesPerNode = details.NumValuesPerNode,
ApplyIndexRangeAndEncoding=!String.IsNullOrEmpty(indexRange)||!QualifiedName.IsNull(dataEncoding),
IndexRange = indexRange,
DataEncoding = dataEncoding
};
return cp;
}

The HistoryDataRawReader class uses the IHistoryDataSource to access the archive. It is used to implement the ReadRaw, ReadProcessed and ReadAtTime operations.