in src/Azure.IIoT.OpcUa.Publisher.Testing/src/HistoricalAccess/HistoricalAccessNodeManager.cs [1193:1369]
private HistoryReadRequest CreateHistoryReadRequest(
ServerSystemContext context,
ReadRawModifiedDetails details,
NodeHandle handle,
HistoryReadValueId nodeToRead)
{
var sizeLimited = details.StartTime == DateTime.MinValue || details.EndTime == DateTime.MinValue;
var applyIndexRangeOrEncoding = nodeToRead.ParsedIndexRange != NumericRange.Empty || !QualifiedName.IsNull(nodeToRead.DataEncoding);
var returnBounds = !details.IsReadModified && details.ReturnBounds;
var timeFlowsBackward = (details.StartTime == DateTime.MinValue) || (details.EndTime != DateTime.MinValue && details.EndTime < details.StartTime);
// find the archive item.
var item = Reload(context, handle);
if (item == null)
{
throw new ServiceResultException(StatusCodes.BadNotSupported);
}
var values = new LinkedList<DataValue>();
LinkedList<ModificationInfo> modificationInfos = null;
if (details.IsReadModified)
{
modificationInfos = new LinkedList<ModificationInfo>();
}
// read history.
var view = item.ReadHistory(details.StartTime, details.EndTime, details.IsReadModified, handle.Node.BrowseName);
var startBound = -1;
var endBound = -1;
var ii = timeFlowsBackward ? view.Count - 1 : 0;
while (ii >= 0 && ii < view.Count)
{
try
{
var timestamp = (DateTime)view[ii].Row[0];
// check if looking for start of data.
if (values.Count == 0)
{
if (timeFlowsBackward)
{
if ((details.StartTime != DateTime.MinValue && timestamp >= details.StartTime) || (details.StartTime == DateTime.MinValue && timestamp >= details.EndTime))
{
startBound = ii;
if (timestamp > details.StartTime)
{
continue;
}
}
}
else
{
if (timestamp <= details.StartTime)
{
startBound = ii;
if (timestamp < details.StartTime)
{
continue;
}
}
}
}
// check if absolute max values specified.
if (sizeLimited && details.NumValuesPerNode > 0 && details.NumValuesPerNode < values.Count)
{
break;
}
// check for end bound.
if (details.EndTime != DateTime.MinValue && timestamp >= details.EndTime)
{
if (timeFlowsBackward)
{
if (timestamp <= details.EndTime)
{
endBound = ii;
break;
}
}
else
{
if (timestamp >= details.EndTime)
{
endBound = ii;
break;
}
}
}
// check if the start bound needs to be returned.
if (returnBounds && values.Count == 0 && startBound != ii && details.StartTime != DateTime.MinValue)
{
// add start bound.
if (startBound == -1)
{
values.AddLast(new DataValue(Variant.Null, StatusCodes.BadBoundNotFound, details.StartTime, details.StartTime));
}
else
{
values.AddLast(RowToDataValue(context, nodeToRead, view[startBound], applyIndexRangeOrEncoding));
}
// check if absolute max values specified.
if (sizeLimited && details.NumValuesPerNode > 0 && details.NumValuesPerNode < values.Count)
{
break;
}
}
// add value.
values.AddLast(RowToDataValue(context, nodeToRead, view[ii], applyIndexRangeOrEncoding));
modificationInfos?.AddLast((ModificationInfo)view[ii].Row[6]);
}
finally
{
if (timeFlowsBackward)
{
ii--;
}
else
{
ii++;
}
}
}
// add late bound.
while (returnBounds && details.EndTime != DateTime.MinValue)
{
// add start bound.
if (values.Count == 0)
{
if (startBound == -1)
{
values.AddLast(new DataValue(Variant.Null, StatusCodes.BadBoundNotFound, details.StartTime, details.StartTime));
}
else
{
values.AddLast(RowToDataValue(context, nodeToRead, view[startBound], applyIndexRangeOrEncoding));
}
}
// check if absolute max values specified.
if (sizeLimited && details.NumValuesPerNode > 0 && details.NumValuesPerNode < values.Count)
{
break;
}
// add end bound.
if (endBound == -1)
{
values.AddLast(new DataValue(Variant.Null, StatusCodes.BadBoundNotFound, details.EndTime, details.EndTime));
}
else
{
values.AddLast(RowToDataValue(context, nodeToRead, view[endBound], applyIndexRangeOrEncoding));
}
break;
}
return new HistoryReadRequest
{
Values = values,
ModificationInfos = modificationInfos,
NumValuesPerNode = details.NumValuesPerNode,
Filter = null
};
}