6ea3ba44da
IHistorianDataSource.SupportedAggregates (default: every HistoryAggregateType) is the one authority for both ServerCapabilities/AggregateFunctions (i=2997) and HistoryServerCapabilities/AggregateFunctions (i=11201) and for the up-front BadAggregateNotSupported in HistoryRead-Processed. Gateway declares AggregateModeMapper.Served (4), SqlServer/QuestDB AggregateFamilies.Served (17), Null none. Only the folder references are trimmed; the SDK's calculator factories stay so live AggregateFilter subscriptions keep working. Co-Authored-By: Claude <noreply@anthropic.com>
61 lines
3.3 KiB
C#
61 lines
3.3 KiB
C#
using System.Collections.Frozen;
|
|
using ZB.MOM.WW.HistorianGateway.Contracts.Grpc;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Mapping;
|
|
|
|
/// <summary>
|
|
/// Maps the driver-agnostic <see cref="HistoryAggregateType"/> (OPC UA Part 13 aggregate) onto the
|
|
/// gateway's native <see cref="RetrievalMode"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Served: <see cref="HistoryAggregateType.TimeAverage"/> → <see cref="RetrievalMode.TimeWeightedAverage"/>,
|
|
/// Minimum → <see cref="RetrievalMode.MinimumWithTime"/>, Maximum → <see cref="RetrievalMode.MaximumWithTime"/>,
|
|
/// Total → <see cref="RetrievalMode.Integral"/>.
|
|
/// </para>
|
|
/// <para>
|
|
/// Deliberately NOT served (#679): <see cref="HistoryAggregateType.Average"/> (OPC UA Average is the arithmetic mean;
|
|
/// AVEVA's modes include no arithmetic mean — only the time-weighted one, which is TimeAverage) and
|
|
/// <see cref="HistoryAggregateType.Count"/> (AVEVA <c>Counter</c> is a rollover-counter increase, not a sample count).
|
|
/// Both surface as <c>BadAggregateNotSupported</c> rather than returning a different statistic under the requested name.
|
|
/// </para>
|
|
/// </remarks>
|
|
internal static class AggregateModeMapper
|
|
{
|
|
/// <summary>
|
|
/// The aggregates the gateway serves — every defined member <see cref="TryMap"/> maps, derived from it so the
|
|
/// advertised set and the dispatch can never disagree. <see cref="GatewayHistorianDataSource.SupportedAggregates"/>
|
|
/// returns it (#682); it is the ONLY declaration of the gateway's served set.
|
|
/// </summary>
|
|
public static readonly IReadOnlySet<HistoryAggregateType> Served =
|
|
Enum.GetValues<HistoryAggregateType>().Where(a => TryMap(a) is not null).ToFrozenSet();
|
|
|
|
/// <summary>Maps an aggregate function to the gateway retrieval mode.</summary>
|
|
/// <param name="aggregate">The driver-agnostic aggregate function.</param>
|
|
/// <returns>The matching gateway <see cref="RetrievalMode"/>.</returns>
|
|
/// <exception cref="HistoryAggregateNotSupportedException">A defined member the gateway has no retrieval mode for.</exception>
|
|
/// <exception cref="ArgumentOutOfRangeException">An undefined enum value.</exception>
|
|
public static RetrievalMode ToRetrievalMode(HistoryAggregateType aggregate)
|
|
{
|
|
if (TryMap(aggregate) is { } mode) return mode;
|
|
|
|
// Part 13 aggregates the gateway has no retrieval mode for (#285) — an UNDEFINED enum value is a
|
|
// programming error, but a defined-yet-unserved member is a normal client request the server
|
|
// answers with BadAggregateNotSupported.
|
|
if (Enum.IsDefined(aggregate)) throw new HistoryAggregateNotSupportedException(aggregate);
|
|
throw new ArgumentOutOfRangeException(
|
|
nameof(aggregate), aggregate, "Unmapped HistoryAggregateType — add a RetrievalMode mapping.");
|
|
}
|
|
|
|
/// <summary>The single mapping table: the retrieval mode for a served aggregate, or null.</summary>
|
|
private static RetrievalMode? TryMap(HistoryAggregateType aggregate) => aggregate switch
|
|
{
|
|
HistoryAggregateType.TimeAverage => RetrievalMode.TimeWeightedAverage,
|
|
HistoryAggregateType.Minimum => RetrievalMode.MinimumWithTime,
|
|
HistoryAggregateType.Maximum => RetrievalMode.MaximumWithTime,
|
|
HistoryAggregateType.Total => RetrievalMode.Integral,
|
|
_ => null,
|
|
};
|
|
}
|