namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
///
/// Driver capability for historical-data reads (OPC UA HistoryRead). Optional —
/// only drivers whose backends carry historian data implement this. Currently:
/// Galaxy (Wonderware Historian via the optional plugin), OPC UA Client (forward
/// to upstream server).
///
public interface IHistoryProvider
{
///
/// Read raw historical samples for a single attribute over a time range.
/// The Core wraps this with continuation-point handling.
///
Task ReadRawAsync(
string fullReference,
DateTime startUtc,
DateTime endUtc,
uint maxValuesPerNode,
CancellationToken cancellationToken);
///
/// Read processed (aggregated) samples — interval-bucketed average / min / max / etc.
/// Optional — drivers that only support raw history can throw .
///
Task ReadProcessedAsync(
string fullReference,
DateTime startUtc,
DateTime endUtc,
TimeSpan interval,
HistoryAggregateType aggregate,
CancellationToken cancellationToken);
///
/// Read one sample per requested timestamp — OPC UA HistoryReadAtTime service. The
/// driver interpolates (or returns the prior-boundary sample) when no exact match
/// exists. Optional; drivers that can't interpolate throw .
///
///
/// Default implementation throws. Drivers opt in by overriding; keeps existing
/// IHistoryProvider implementations compiling without forcing a ReadAtTime path
/// they may not have a backend for.
///
Task ReadAtTimeAsync(
string fullReference,
IReadOnlyList timestampsUtc,
CancellationToken cancellationToken)
=> throw new NotSupportedException(
$"{GetType().Name} does not implement ReadAtTimeAsync. " +
"Drivers whose backends support at-time reads override this method.");
///
/// Read historical alarm/event records — OPC UA HistoryReadEvents service. Distinct
/// from the live event stream — historical rows come from an event historian (Galaxy's
/// Alarm Provider history log, etc.) rather than the driver's active subscription.
///
///
/// Optional filter: null means "all sources", otherwise restrict to events from that
/// source-object name. Drivers may ignore the filter if the backend doesn't support it.
///
/// Inclusive lower bound on EventTimeUtc.
/// Exclusive upper bound on EventTimeUtc.
/// Upper cap on returned events — the driver's backend enforces this.
/// Request cancellation.
///
/// Default implementation throws. Only drivers with an event historian (Galaxy via the
/// Wonderware Alarm & Events log) override. Modbus / the OPC UA Client driver stay
/// with the default and let callers see BadHistoryOperationUnsupported.
///
Task ReadEventsAsync(
string? sourceName,
DateTime startUtc,
DateTime endUtc,
int maxEvents,
CancellationToken cancellationToken)
=> throw new NotSupportedException(
$"{GetType().Name} does not implement ReadEventsAsync. " +
"Drivers whose backends have an event historian override this method.");
///
/// Filter-aware historical event read — OPC UA HistoryReadEvents service with full
/// EventFilter support (SelectClauses + WhereClause). Distinct from the simpler
///
/// overload which is sufficient for "give me the standard BaseEventType fields"
/// queries; this overload is for clients that send a custom EventFilter on the
/// wire (per-select-clause Variant population, where-filter evaluation).
///
///
/// Driver-specific node identifier. May be a notifier object (e.g. the driver-root
/// folder) — drivers that support cluster-wide queries treat it as
/// "all sources in the namespace".
///
/// Filter spec — time range + select clauses + optional where clause.
/// Request cancellation.
///
///
/// Default implementation throws — drivers opt in by overriding. Existing drivers
/// that only handle the parameterless overload stay green; new drivers that need
/// filter-aware event history (OPC UA Client passthrough, future event-historian
/// backends) override this method.
///
///
/// The OPC UA Client driver implements this by translating
/// into ReadEventDetails and calling Session.HistoryReadAsync against
/// the upstream server.
///
///
Task ReadEventsAsync(
string fullReference,
EventHistoryRequest request,
CancellationToken cancellationToken)
=> throw new NotSupportedException(
$"{GetType().Name} does not implement filter-aware ReadEventsAsync(EventHistoryRequest). " +
"Drivers whose backends carry historical events with EventFilter support override this method.");
}
///
/// Filter spec for the filter-aware
/// overload. Mirrors the OPC UA ReadEventDetails wire shape (StartTime, EndTime,
/// NumValuesPerNode, EventFilter) but transport-neutral so non-UA drivers can implement it
/// without taking a dependency on the UA SDK type.
///
/// Inclusive lower bound on event time.
/// Exclusive upper bound on event time.
/// Maximum events per node (0 = no driver-side cap, server may still apply one).
///
/// Per-field projection. Each entry names a BaseEventType-rooted field (or a
/// typed-path field via ) the caller
/// wants returned. null means "use the driver's default field set" — typically
/// EventId, SourceName, Time, Message, Severity, ReceiveTime.
///
///
/// Optional content-filter restriction (e.g. EventType OfType AlarmConditionType).
/// Drivers may ignore the where clause if their backend doesn't support it; that's a
/// best-effort projection rather than a hard error.
///
public sealed record EventHistoryRequest(
DateTime StartTime,
DateTime EndTime,
uint NumValuesPerNode,
IReadOnlyList? SelectClauses,
ContentFilterSpec? WhereClause);
///
/// Transport-neutral mirror of OPC UA's SimpleAttributeOperand — picks one field
/// from a node by typed browse path. is the OPC UA NodeId
/// of the type that the path is rooted at (e.g. BaseEventType);
/// is a sequence of QualifiedName-style segments ("ns:Name" or just "Name"
/// when ns=0). An empty means "the node itself".
///
///
/// Type the path is rooted at. null defaults to the OPC UA BaseEventType
/// when the driver has a UA mapping. Format is driver-specific NodeId text (e.g.
/// "i=2041" for BaseEventType).
///
/// Browse-path segments. Empty list = the typed node itself.
///
/// Stable key the driver uses when populating . The
/// server-side dispatcher uses this to align the returned values with the wire-side
/// SelectClause order, even when a driver doesn't honour the BrowsePath verbatim.
///
public sealed record SimpleAttributeSpec(
string? TypeDefinitionId,
IReadOnlyList BrowsePath,
string FieldName);
///
/// Transport-neutral mirror of OPC UA's ContentFilter. The current shape carries the
/// raw filter operands as opaque OPC UA ExtensionObject bytes — drivers that need to
/// evaluate the filter (Galaxy historian) parse it themselves; the OPC UA Client driver
/// forwards it untouched. A future PR may replace this with a structured AST when more
/// than one driver needs to evaluate where-clauses locally.
///
///
/// Optional binary-encoded ContentFilter from the wire. null when no
/// where-clause was supplied.
///
public sealed record ContentFilterSpec(byte[]? EncodedOperands);
/// Result of a HistoryRead call.
/// Returned samples in chronological order.
/// Opaque token for the next call when more samples are available; null when complete.
public sealed record HistoryReadResult(
IReadOnlyList Samples,
byte[]? ContinuationPoint);
///
/// Aggregate function for processed history reads. Mirrors the OPC UA Part 13 §5
/// standard aggregate catalog. Each value maps 1:1 onto an
/// Opc.Ua.ObjectIds.AggregateFunction_* NodeId — the OPC UA Client driver does the
/// translation in OpcUaClientDriver.MapAggregateToNodeId; other drivers either
/// evaluate the aggregate locally (Galaxy historian) or surface
/// BadAggregateNotSupported for the values their backend can't honour.
///
///
///
/// Stable ordinals. The first 5 values (..)
/// carry ordinals 0-4 from the original PR — additions are appended to keep prior
/// persisted enums (config files, Admin UI dropdowns) compatible.
///
///
/// Server-side support. Not every upstream OPC UA server implements every
/// Part 13 aggregate. Implementations advertise their support through
/// AggregateConfiguration on the Server object; clients can probe it at runtime.
/// Aggregates that the upstream rejects come back with
/// StatusCode=BadAggregateNotSupported on the per-row HistoryRead result —
/// the driver passes that through verbatim (cascading-quality rule, Part 11 §8).
///
///
public enum HistoryAggregateType
{
// ---- Original 5 (ordinals 0-4 — keep stable) ----
/// Average of all values in the interval. Part 13 §5.4.
Average,
/// Minimum value in the interval. Part 13 §5.5.
Minimum,
/// Maximum value in the interval. Part 13 §5.6.
Maximum,
/// Sum of values in the interval (numeric only). Part 13 §5.10.
Total,
/// Count of Good-quality samples in the interval. Part 13 §5.18.
Count,
// ---- Time-weighted averages (Part 13 §5.4) ----
/// Time-weighted average — values held until next sample. Part 13 §5.4.2.
TimeAverage,
/// Time-weighted average using simple-bounds extrapolation. Part 13 §5.4.3.
TimeAverage2,
// ---- Interpolation (Part 13 §5.3) ----
/// Interpolated value at each interval boundary. Part 13 §5.3.
Interpolative,
// ---- Min/Max with timestamps and range (Part 13 §5.5–§5.7) ----
/// Timestamp of the minimum-value sample. Part 13 §5.5.4.
MinimumActualTime,
/// Timestamp of the maximum-value sample. Part 13 §5.6.4.
MaximumActualTime,
/// Maximum minus minimum across the interval. Part 13 §5.7.
Range,
/// Range computed using simple-bounds extrapolation. Part 13 §5.7.
Range2,
// ---- Annotation / duration / quality coverage (Part 13 §5.16–§5.21) ----
/// Number of annotations attached to samples in the interval. Part 13 §5.21.
AnnotationCount,
/// Total time (ms) covered by Good-quality data. Part 13 §5.16.
DurationGood,
/// Total time (ms) covered by Bad-quality data. Part 13 §5.16.
DurationBad,
/// Percent of the interval covered by Good-quality data (0-100). Part 13 §5.17.
PercentGood,
/// Percent of the interval covered by Bad-quality data (0-100). Part 13 §5.17.
PercentBad,
/// Worst (most-severe) quality code seen in the interval. Part 13 §5.20.
WorstQuality,
/// Worst-quality code using simple-bounds extrapolation. Part 13 §5.20.
WorstQuality2,
// ---- Statistical (Part 13 §5.13) ----
/// Sample-population standard deviation (n-1 divisor). Part 13 §5.13.
StandardDeviationSample,
/// Whole-population standard deviation (n divisor). Part 13 §5.13.
StandardDeviationPopulation,
/// Sample-population variance (n-1 divisor). Part 13 §5.13.
VarianceSample,
/// Whole-population variance (n divisor). Part 13 §5.13.
VariancePopulation,
// ---- State-based (Part 13 §5.12, §5.19) ----
/// Number of value transitions observed in the interval. Part 13 §5.12.
NumberOfTransitions,
/// Total time (ms) the value was 0 (state Zero). Part 13 §5.19.
DurationInStateZero,
/// Total time (ms) the value was non-zero (state NonZero). Part 13 §5.19.
DurationInStateNonZero,
// ---- Interval bounds and deltas (Part 13 §5.8–§5.9, §5.11) ----
/// First Good-quality sample at or after the interval start. Part 13 §5.8.
Start,
/// Last Good-quality sample at or before the interval end. Part 13 §5.9.
End,
/// End sample minus Start sample. Part 13 §5.11.
Delta,
/// Boundary value (extrapolated) at the interval start. Part 13 §5.8.
StartBound,
/// Boundary value (extrapolated) at the interval end. Part 13 §5.9.
EndBound,
}
///
/// One row returned by the fixed-field
///
/// overload — a historical alarm/event record, not the OPC UA live-event stream. Fields
/// match the minimum set the Server needs to populate a HistoryEventFieldList
/// for HistoryReadEvents responses.
///
/// Stable unique id for the event — driver-specific format.
/// Source object that emitted the event. May differ from the sourceName filter the caller passed (fuzzy matches).
/// Process-side timestamp — when the event actually occurred.
/// Historian-side timestamp — when the historian persisted the row; may lag by the historian's buffer flush cadence.
/// Human-readable message text.
/// OPC UA severity (1-1000). Drivers map their native priority scale onto this range.
public sealed record HistoricalEvent(
string EventId,
string? SourceName,
DateTime EventTimeUtc,
DateTime ReceivedTimeUtc,
string? Message,
ushort Severity);
/// Result of a call.
/// Events in chronological order by EventTimeUtc.
/// Opaque token for the next call when more events are available; null when complete.
public sealed record HistoricalEventsResult(
IReadOnlyList Events,
byte[]? ContinuationPoint);
///
/// One row returned by the filter-aware
///
/// overload. Carries an open-ended bag keyed by
/// (or a stable default name when no
/// SelectClauses were supplied) so the server-side dispatcher can re-align fields with
/// the client's requested order — without forcing every driver to honour the entire
/// OPC UA EventFilter shape verbatim.
///
///
/// SelectClause results. Keys match the FieldName on the corresponding
/// ; values are the raw .NET payload (string,
/// DateTime, severity int, etc.). null values are legitimate (the
/// upstream had a missing field).
///
///
/// Wall-clock event time — convenience for ordering / windowing without picking a key
/// out of . Drivers populate this from the underlying event row.
///
public sealed record HistoricalEventRow(
IReadOnlyDictionary Fields,
DateTimeOffset OccurrenceTime);
///
/// Result of the filter-aware
///
/// overload. Mirrors but carries
/// instead of the fixed-shape
/// — the server-side dispatcher unpacks the keyed fields
/// into a HistoryEventFieldList aligned with the client's SelectClauses.
///
/// Events in chronological order by .
/// Opaque token for the next call when more events are available; null when complete.
public sealed record HistoricalEventBatch(
IReadOnlyList Events,
byte[]? ContinuationPoint);