Skip to content

How-to guide

Historical data

Read the historical values of a node over time ranges, at specific times, and aggregated, modify and delete them, and read the event history.

This content was machine translated and may contain errors. Read in German

How-to guide

Historical data ​

Types usedOpcClientOpcNodeIdIOpcNodeHistoryNavigatorOpcHistoryValueOpcModifiedHistoryValueOpcAggregateType

According to the OPC UA specification, every node of the Variable node class can record the values of its Value attribute over time. On every change of the Value attribute, the server then stores the new value together with the timestamp of the Value attribute. These pairs of value and timestamp are called historical data. The server itself decides where it stores the data. The client uses the IsHistorizing attribute of the node to determine whether the server provides historical data for the node, that is, whether it stores value changes historically. The client selects the node whose historical data it accesses through the NodeId (OpcNodeId). The client can read, update, replace, delete, or create historical data. Reading is the most common case. The server determines whether and which of these operations it allows for a node. The client does not have to process all historical values, regardless of whether it reads with or without a navigator.

Read values of a time range ​

The client reads historical data over a time range.

Read all values within an open time range, in which StartTime or EndTime is undefined:

Read all values from a specific timestamp (StartTime) onward:

csharp
var startTime = new DateTime(2017, 2, 16, 10, 0, 0);
var history = client.ReadNodeHistory(
        startTime, endTime: null, "ns=2;s=Machine/Job/Speed");
vbnet
Dim startTime = New DateTime(2017, 2, 16, 10, 0, 0)
Dim history = client.ReadNodeHistory( _
        startTime, endTime:=Nothing, "ns=2;s=Machine/Job/Speed")

Read all values up to a specific timestamp (EndTime):

csharp
var endTime = new DateTime(2017, 2, 16, 15, 0, 0);
var history = client.ReadNodeHistory(
        null, endTime, "ns=2;s=Machine/Job/Speed");
vbnet
Dim endTime = New DateTime(2017, 2, 16, 15, 0, 0)
Dim history = client.ReadNodeHistory( _
        Nothing, endTime, "ns=2;s=Machine/Job/Speed")

Read all values within a closed time range, in which StartTime and EndTime are defined:

csharp
var startTime = new DateTime(2017, 2, 16, 10, 0, 0);
var endTime = new DateTime(2017, 2, 16, 15, 0, 0);
 
var history = client.ReadNodeHistory(
        startTime, endTime, "ns=2;s=Machine/Job/Speed");
vbnet
Dim startTime = New DateTime(2017, 2, 16, 10, 0, 0)
Dim endTime = New DateTime(2017, 2, 16, 15, 0, 0)

Dim history = client.ReadNodeHistory( _
        startTime, endTime, "ns=2;s=Machine/Job/Speed")

Process the values (through an instance that implements the IEnumerable interface):

csharp
foreach (var value in history) {
    Console.WriteLine(
            "{0}: {1}",
            value.Timestamp,
            value);
}
vbnet
For Each value In history
    Console.WriteLine( _
            "{0}: {1}",
            value.Timestamp,
            value)
Next

Read values page by page ​

When reading historical data page by page, the client retrieves only a specific number of values from the server at a time.

Read a specific number of values per page:

csharp
var historyNavigator = client.ReadNodeHistory(
        10, "ns=2;s=Machine/Job/Speed");
vbnet
Dim historyNavigator = client.ReadNodeHistory( _
        10, "ns=2;s=Machine/Job/Speed")

Read a specific number of values per page within an open time range, in which StartTime or EndTime is undefined:

Read a specific number of values per page from a specific timestamp (StartTime) onward:

csharp
var startTime = new DateTime(2017, 2, 16, 15, 0, 0);
var historyNavigator = client.ReadNodeHistory(
        startTime, 10, "ns=2;s=Machine/Job/Speed");
vbnet
Dim startTime = New DateTime(2017, 2, 16, 15, 0, 0)
Dim historyNavigator = client.ReadNodeHistory( _
        startTime, 10, "ns=2;s=Machine/Job/Speed")

Read a specific number of values per page up to a specific timestamp (EndTime):

csharp
var endTime = new DateTime(2017, 2, 16, 15, 0, 0);
var historyNavigator = client.ReadNodeHistory(
        null, endTime, 10, "ns=2;s=Machine/Job/Speed");
vbnet
Dim endTime = New DateTime(2017, 2, 16, 15, 0, 0)
Dim historyNavigator = client.ReadNodeHistory( _
        Nothing, endTime, 10, "ns=2;s=Machine/Job/Speed")

Read a specific number of values per page within a closed time range:

csharp
var startTime = new DateTime(2017, 2, 16, 10, 0, 0);
var endTime = new DateTime(2017, 2, 16, 15, 0, 0);
 
var historyNavigator = client.ReadNodeHistory(
        startTime, endTime, 10, "ns=2;s=Machine/Job/Speed");
vbnet
Dim startTime = New DateTime(2017, 2, 16, 10, 0, 0)
Dim endTime = New DateTime(2017, 2, 16, 15, 0, 0)

Dim historyNavigator = client.ReadNodeHistory( _
        startTime, endTime, 10, "ns=2;s=Machine/Job/Speed")

Process the values (through an instance that implements the IOpcNodeHistoryNavigator interface):

csharp
do {
    foreach (var value in historyNavigator) {
        Console.WriteLine(
                "{0}: {1}",
                value.Timestamp,
                value);
    }
} while (historyNavigator.MoveNextPage());
 
historyNavigator.Close();
vbnet
Do
    For Each value In historyNavigator
        Console.WriteLine( _
                "{0}: {1}", _
                value.Timestamp, _
                value)
    Next
Loop While historyNavigator.MoveNextPage()

historyNavigator.Close()

Always make sure that the Close method of the navigator is called. Only then can the server discard the historical data it has buffered for this request. Instead of calling Close explicitly, you can also use the navigator in a using block:

csharp
using (historyNavigator) {
    do {
        foreach (var value in historyNavigator) {
            Console.WriteLine(
                    "{0}: {1}",
                    value.Timestamp,
                    value);
        }
    } while (historyNavigator.MoveNextPage());
}
vbnet
Using historyNavigator
    Do
        For Each value In historyNavigator
            Console.WriteLine( _
                    "{0}: {1}", _
                    value.Timestamp, _
                    value)
        Next
    Loop While historyNavigator.MoveNextPage()
End Using

The navigator uses at most 999 requests to retrieve a page. If the server has not completed the page by then, the navigator throws an OpcException with the status BadCommunicationError. This also applies to the navigator of the event history.

Read the history of multiple nodes ​

Types usedOpcClientOpcNodeIdIOpcNodeHistoryNavigatorOpcHistoryValue

ReadNodesHistory reads the historical values of multiple nodes for the same time range in one call. If you specify a number of values per page, the method returns a navigator. Its indexer returns the values of a single node. The client loads further pages the same way as for a single node:

csharp
var nodeIds = new OpcNodeId[] { "ns=2;s=Machine/Speed", "ns=2;s=Machine/Temperature" };
 
using (var history = client.ReadNodesHistory(
        DateTime.UtcNow.AddHours(-1),
        DateTime.UtcNow,
        numberOfValues: 100,
        nodeIds)) {
    foreach (OpcHistoryValue value in history[nodeIds[0]])
        Console.WriteLine("{0}: {1}", value.Timestamp, value.Value);
}
vbnet
Dim nodeIds = New OpcNodeId() {"ns=2;s=Machine/Speed", "ns=2;s=Machine/Temperature"}

Using history = client.ReadNodesHistory( _
        DateTime.UtcNow.AddHours(-1), _
        DateTime.UtcNow, _
        numberOfValues:=100, _
        nodeIds)
    For Each value As OpcHistoryValue In history(nodeIds(0))
        Console.WriteLine("{0}: {1}", value.Timestamp, value.Value)
    Next
End Using

Without a number per page, ReadNodesHistory returns all values of the time range as a dictionary with the identifier of the node as the key:

csharp
IDictionary<OpcNodeId, IEnumerable<OpcHistoryValue>> history = client.ReadNodesHistory(
        DateTime.UtcNow.AddHours(-1),
        DateTime.UtcNow,
        "ns=2;s=Machine/Speed",
        "ns=2;s=Machine/Temperature");
 
foreach (var entry in history)
    Console.WriteLine("{0}: {1} values", entry.Key, entry.Value.Count());
vbnet
Dim history As IDictionary(Of OpcNodeId, IEnumerable(Of OpcHistoryValue)) = _
        client.ReadNodesHistory( _
                DateTime.UtcNow.AddHours(-1), _
                DateTime.UtcNow, _
                "ns=2;s=Machine/Speed", _
                "ns=2;s=Machine/Temperature")

For Each entry In history
    Console.WriteLine("{0}: {1} values", entry.Key, entry.Value.Count())
Next

Read aggregated values ​

For processed reading of historical data, you select one of several kinds of aggregation through OpcAggregateType.

Read the minimum value within a time range:

csharp
var minSpeed = client.ReadNodeHistoryProcessed(
        startTime,
        endTime,
        OpcAggregateType.Minimum,
        "ns=2;s=Machine/Job/Speed");
vbnet
Dim minSpeed = client.ReadNodeHistoryProcessed( _
        startTime, _
        endTime, _
        OpcAggregateType.Minimum, _
        "ns=2;s=Machine/Job/Speed")

Read the average value within a time range:

csharp
var avgSpeed = client.ReadNodeHistoryProcessed(
        startTime,
        endTime,
        OpcAggregateType.Average,
        "ns=2;s=Machine/Job/Speed");
vbnet
Dim avgSpeed = client.ReadNodeHistoryProcessed( _
        startTime, _
        endTime, _
        OpcAggregateType.Average, _
        "ns=2;s=Machine/Job/Speed")

Read the maximum value within a time range:

csharp
var maxSpeed = client.ReadNodeHistoryProcessed(
        startTime,
        endTime,
        OpcAggregateType.Maximum,
        "ns=2;s=Machine/Job/Speed");
vbnet
Dim maxSpeed = client.ReadNodeHistoryProcessed( _
        startTime, _
        endTime, _
        OpcAggregateType.Maximum, _
        "ns=2;s=Machine/Job/Speed")

If you divide the values into intervals of equal length, the server returns one aggregated value per interval. You specify the length of the intervals in milliseconds. Without it, the server calculates one value over the entire time range:

Read the average value per hour within a time range:

csharp
// Read the average value of each hour within the time window.
var avgSpeedPerHour = client.ReadNodeHistoryProcessed(
        startTime,
        endTime,
        OpcAggregateType.Average,
        aggregateInterval: TimeSpan.FromHours(1).TotalMilliseconds,
        "ns=2;s=Machine/Job/Speed");
vbnet
'Read the average value of each hour within the time window.
Dim avgSpeedPerHour = client.ReadNodeHistoryProcessed( _
        startTime, _
        endTime, _
        OpcAggregateType.Average, _
        aggregateInterval:=TimeSpan.FromHours(1).TotalMilliseconds, _
        "ns=2;s=Machine/Job/Speed")

Aggregated values carry only the source timestamp (SourceTimestamp), not a server timestamp.

Read values at specific times ​

When the client reads values at specific times, the server returns one value per point in time. If there is no recorded value at a point in time, the server usually determines the value from the neighboring values:

csharp
var times = new DateTime[] {
    new DateTime(2017, 2, 16, 10, 0, 0),
    new DateTime(2017, 2, 16, 12, 0, 0)
};
 
var speeds = client.ReadNodeHistoryAtTime(times, "ns=2;s=Machine/Job/Speed");
 
foreach (var value in speeds)
    Console.WriteLine("{0}: {1}", value.Timestamp, value);
vbnet
Dim times = New DateTime() { _
    New DateTime(2017, 2, 16, 10, 0, 0), _
    New DateTime(2017, 2, 16, 12, 0, 0) _
}

Dim speeds = client.ReadNodeHistoryAtTime(times, "ns=2;s=Machine/Job/Speed")

For Each value In speeds
    Console.WriteLine("{0}: {1}", value.Timestamp, value)
Next

Read modified values ​

If a client has updated, replaced, or deleted historical values (section Modify historical data), a server can keep the previous values together with information about the modification. The ReadNodeHistoryModified method returns these values as instances of OpcModifiedHistoryValue. As with the other methods, the client can also read the values page by page:

csharp
var modifiedSpeeds = client.ReadNodeHistoryModified(
        startTime,
        endTime,
        "ns=2;s=Machine/Job/Speed");
 
foreach (var value in modifiedSpeeds) {
    Console.WriteLine(
            "{0}: {1} ({2} at {3} by {4})",
            value.Timestamp,
            value,
            value.ModificationType,
            value.ModificationTime,
            value.ModificationUserName);
}
vbnet
Dim modifiedSpeeds = client.ReadNodeHistoryModified( _
        startTime, _
        endTime, _
        "ns=2;s=Machine/Job/Speed")

For Each value In modifiedSpeeds
    Console.WriteLine( _
            "{0}: {1} ({2} at {3} by {4})", _
            value.Timestamp, _
            value, _
            value.ModificationType, _
            value.ModificationTime, _
            value.ModificationUserName)
Next

Against a server that uses OpcNodeHistorian, ReadNodeHistoryModified and DeleteNodeHistoryModified refer to the timestamps of the values, not to the time of the modification.

If the server rejects the request for the node, ReadNodeHistoryProcessed, ReadNodeHistoryAtTime, and ReadNodeHistoryModified throw an OpcException with the status of the server, for example BadAggregateNotSupported. If the server accepts the node but returns no values, the result is empty.

Modify historical data ​

Types usedOpcClientOpcNodeIdOpcValueOpcStatusOpcStatusCollection

To modify historical data, the client passes the values as instances of OpcValue. Their SourceTimestamp defines the point in time of the value. The server returns an OpcStatus for each value. OPC UA defines the following rules:

  • CreateNodeHistory adds values only at points in time for which no value exists yet.
  • ReplaceNodeHistory replaces only existing values.
  • UpdateNodeHistory adds values or replaces existing values.

Add new values:

csharp
OpcStatusCollection results = client.CreateNodeHistory(
        "ns=2;s=Machine/Job/Speed",
        new OpcValue(1200, sourceTimestamp: new DateTime(2017, 2, 16, 10, 0, 0)),
        new OpcValue(1300, sourceTimestamp: new DateTime(2017, 2, 16, 10, 5, 0)));
 
foreach (var result in results) {
    if (result.IsBad)
        Console.WriteLine("Failed to create value: {0}", result.Description);
}
vbnet
Dim results As OpcStatusCollection = client.CreateNodeHistory( _
        "ns=2;s=Machine/Job/Speed", _
        New OpcValue(1200, sourceTimestamp:=New DateTime(2017, 2, 16, 10, 0, 0)), _
        New OpcValue(1300, sourceTimestamp:=New DateTime(2017, 2, 16, 10, 5, 0)))

For Each result In results
    If result.IsBad Then
        Console.WriteLine("Failed to create value: {0}", result.Description)
    End If
Next

Replace an existing value:

csharp
OpcStatus result = client.ReplaceNodeHistory(
        "ns=2;s=Machine/Job/Speed",
        new OpcValue(1250, sourceTimestamp: new DateTime(2017, 2, 16, 10, 0, 0)));
vbnet
Dim result As OpcStatus = client.ReplaceNodeHistory( _
        "ns=2;s=Machine/Job/Speed", _
        New OpcValue(1250, sourceTimestamp:=New DateTime(2017, 2, 16, 10, 0, 0)))

Add or replace a value:

csharp
OpcStatus result = client.UpdateNodeHistory(
        "ns=2;s=Machine/Job/Speed",
        new OpcValue(1250, sourceTimestamp: new DateTime(2017, 2, 16, 10, 0, 0)));
vbnet
Dim result As OpcStatus = client.UpdateNodeHistory( _
        "ns=2;s=Machine/Job/Speed", _
        New OpcValue(1250, sourceTimestamp:=New DateTime(2017, 2, 16, 10, 0, 0)))

Delete the values of a time range or at specific times:

csharp
// Delete all values within the time window.
OpcStatusCollection results = client.DeleteNodeHistory(
        "ns=2;s=Machine/Job/Speed",
        startTime,
        endTime);
 
// Delete the value of the timestamp specified.
OpcStatus result = client.DeleteNodeHistoryAtTime(
        "ns=2;s=Machine/Job/Speed",
        time: new DateTime(2017, 2, 16, 10, 5, 0));
vbnet
'Delete all values within the time window.
Dim results As OpcStatusCollection = client.DeleteNodeHistory( _
        "ns=2;s=Machine/Job/Speed", _
        startTime, _
        endTime)

'Delete the value of the timestamp specified.
Dim result As OpcStatus = client.DeleteNodeHistoryAtTime( _
        "ns=2;s=Machine/Job/Speed", _
        time:=New DateTime(2017, 2, 16, 10, 5, 0))

Read the event history ​

Types usedOpcClientOpcNodeIdOpcEventFilterOpcEventIOpcEventHistoryNavigator

Besides the values of variables, a server can also record past events. The client reads the event history through the node that reports the events, for example, the server node or an event node (page Events). An event filter determines which events and which fields the server returns. You create it the same way as when subscribing to events (section Filter events):

csharp
var severity = new OpcSimpleAttributeOperand(OpcEventTypes.Event, "Severity");
 
var filter = OpcFilter.Using(client)
        .FromEvents(OpcEventTypes.AlarmCondition)
        .Where(severity > OpcEventSeverity.Medium)
        .Select();
 
IEnumerable<OpcEvent> events = client.ReadEventHistory(
        startTime,
        endTime,
        "ns=2;s=Machine",
        filter);
 
foreach (var historicalEvent in events)
    Console.WriteLine("{0}: {1}", historicalEvent.Time, historicalEvent.Message);
vbnet
Dim severity = New OpcSimpleAttributeOperand(OpcEventTypes.Event, "Severity")

Dim filter = OpcFilter.Using(client) _
        .FromEvents(OpcEventTypes.AlarmCondition) _
        .Where(severity > OpcEventSeverity.Medium) _
        .Select()

Dim events As IEnumerable(Of OpcEvent) = client.ReadEventHistory( _
        startTime, _
        endTime, _
        "ns=2;s=Machine", _
        filter)

For Each historicalEvent In events
    Console.WriteLine("{0}: {1}", historicalEvent.Time, historicalEvent.Message)
Next

Read the events page by page: As with historical values, a navigator returns the events page by page. Here, too, make sure that the navigator is closed, for example, with a using block:

csharp
using (var eventNavigator = client.ReadEventHistory(
        startTime,
        endTime,
        numberOfEvents: 10,
        "ns=2;s=Machine",
        filter)) {
    do {
        foreach (var historicalEvent in eventNavigator)
            Console.WriteLine("{0}: {1}", historicalEvent.Time, historicalEvent.Message);
    } while (eventNavigator.MoveNextPage());
}
vbnet
Using eventNavigator = client.ReadEventHistory( _
        startTime, _
        endTime, _
        numberOfEvents:=10, _
        "ns=2;s=Machine", _
        filter)
    Do
        For Each historicalEvent In eventNavigator
            Console.WriteLine("{0}: {1}", historicalEvent.Time, historicalEvent.Message)
        Next
    Loop While eventNavigator.MoveNextPage()
End Using

A server is not required to provide past values or events.

Historical data of an OPC HDA server ​

The client reads the historical data of an OPC HDA server (Historical Data Access) with the same methods. The section Historical data (HDA) on the OPC Classic page describes the procedure.