Aggregates (OPC UA Part 13)
July 16, 2026 · View on GitHub
Overview
The .NET Standard stack implements OPC UA Part 13 (OPC 10000-13) v1.05.07 Aggregates on the server side, computed over historical data retrieved through Historical Access (Part 11). All 37 standard aggregate functions are supported and advertised through the address space.
Aggregates are requested with the HistoryRead service using ReadProcessedDetails (a startTime,
endTime, ProcessingInterval, one or more AggregateType NodeIds, and an optional
AggregateConfiguration). The server divides the time range into intervals of ProcessingInterval
milliseconds and produces one aggregate value per interval.
Supported aggregate functions
| Category | Functions |
|---|---|
| Interpolative | Interpolative |
| Averages / integrals | Average, TimeAverage, TimeAverage2, Total, Total2 |
| Extrema | Minimum, Maximum, MinimumActualTime, MaximumActualTime, Range, Minimum2, Maximum2, MinimumActualTime2, MaximumActualTime2, Range2 |
| Counts | Count, AnnotationCount, DurationInStateZero, DurationInStateNonZero, NumberOfTransitions |
| Start / end | Start, End, Delta, StartBound, EndBound, DeltaBounds |
| Quality / time-in-state | DurationGood, DurationBad, PercentGood, PercentBad, WorstQuality, WorstQuality2 |
| Statistics | StandardDeviationSample, VarianceSample, StandardDeviationPopulation, VariancePopulation |
The exact per-aggregate semantics (type, bounding behaviour, timestamp, status-code rules, special cases) follow Part 13 §5.4.3.4–§5.4.3.40.
Architecture
HistoryRead (ReadProcessedDetails)
↓
AsyncCustomNodeManager / CustomNodeManager2 → HistorianDispatcher.DispatchProcessedReadAsync
↓ ├─ provider implements IHistorianProcessedProvider → native push-down
↓ └─ otherwise: framework fallback
↓ ├─ AnnotationCount → IHistorianAnnotationProvider (counts Annotations)
↓ └─ other aggregates → AggregateManager.CreateCalculator
↓ → stream raw values through IAggregateCalculator
AggregateManager(Opc.Ua.Server) owns the registered aggregate factories, the server defaultAggregateConfiguration, and theMinimumProcessingInterval. The standard functions are registered viaAggregatorsand advertised intoServer.ServerCapabilities.AggregateFunctionsandHistoryServerCapabilities.AggregateFunctions.IAggregateCalculatorimplementations (AggregateCalculatorand the specializedAverage/MinMax/Count/StartEnd/Status/StdDevcalculators) compute the aggregates from a stream of rawDataValues.- A historian provider may compute aggregates itself by implementing
IHistorianProcessedProvider(native push-down). When it does not, the framework streams raw values through the calculator.
AnnotationCount
AnnotationCount (Part 13 §5.4.3.20) counts Annotations in each interval, not raw data values.
It is therefore computed from the node's annotation history via IHistorianAnnotationProvider, not from
the raw-value calculator. If the resolved provider does not expose annotation history, an AnnotationCount
request returns Bad_AggregateNotSupported.
Server configuration
AggregateConfiguration controls how non-Good data affects the result:
| Property | Default | Meaning |
|---|---|---|
TreatUncertainAsBad | true | Whether Uncertain samples are treated as Bad when computing the aggregate StatusCode (Part 13 §4.2.1.2). |
PercentDataBad | 100 | Minimum % of Bad data in an interval for the interval StatusCode to be Bad. |
PercentDataGood | 100 | Minimum % of Good data in an interval for the interval StatusCode to be Good. |
UseSlopedExtrapolation | false | Stepped (hold-last) vs sloped extrapolation past the last value. Ignored for Simple Bounds. |
The server's default configuration is returned by AggregateManager.GetDefaultConfiguration(...) and is
used whenever a request sets AggregateConfiguration.UseServerCapabilitiesDefaults = true. Per Part 13
§4.2.1.2 the default TreatUncertainAsBad value is true.
Migration note: in earlier builds the server default used
TreatUncertainAsBad = false. Clients that require the old behaviour should send an explicitAggregateConfigurationwithTreatUncertainAsBad = falserather than relying onUseServerCapabilitiesDefaults. See the Migration Guide.
AggregateManager.MinimumProcessingInterval bounds the smallest interval the server will accept.
Client usage
Use the HistoryClient (session.Historian()) ReadProcessedAsync helper, which streams one
DataValue per processing interval as an IAsyncEnumerable<DataValue>:
// Average of a historizing variable over the last hour, in 1-minute buckets.
DateTime end = DateTime.UtcNow;
DateTime start = end.AddHours(-1);
await foreach (DataValue value in session.Historian().ReadProcessedAsync(
nodeId,
aggregateFunctionId: ObjectIds.AggregateFunction_Average,
startTime: start,
endTime: end,
processingInterval: 60_000 /* ms */))
{
Console.WriteLine($"{value.SourceTimestamp:O}: {value.Value} ({value.StatusCode})");
}
Pass an explicit AggregateConfiguration to override the server defaults:
await foreach (DataValue value in session.Historian().ReadProcessedAsync(
nodeId,
ObjectIds.AggregateFunction_TimeAverage,
start,
end,
processingInterval: 60_000,
configuration: new AggregateConfiguration
{
TreatUncertainAsBad = false,
PercentDataBad = 100,
PercentDataGood = 100,
UseSlopedExtrapolation = false
}))
{
// ...
}
Discover the aggregates a server supports with session.Historian().GetServerCapabilitiesAsync(...) or by
browsing Server.ServerCapabilities.AggregateFunctions.
Notes and limitations
AnnotationCountrequires a provider with annotation history; otherwise it returnsBad_AggregateNotSupported.- The bundled in-memory historian (
InMemoryHistorianProvider) supports all aggregates through the framework fallback and supports annotation history, soAnnotationCountworks out of the box. - Custom providers can override aggregate computation by implementing
IHistorianProcessedProvider.
References
- OPC 10000-13 (Aggregates) v1.05.07: https://reference.opcfoundation.org/Core/Part13/v105/docs/
- Historical Access (Part 11)
- Migration Guide