diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway/Mapping/AggregateModeMapper.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway/Mapping/AggregateModeMapper.cs new file mode 100644 index 00000000..f3d8b3a0 --- /dev/null +++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway/Mapping/AggregateModeMapper.cs @@ -0,0 +1,39 @@ +using ZB.MOM.WW.HistorianGateway.Contracts.Grpc; +using ZB.MOM.WW.OtOpcUa.Core.Abstractions; + +namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Mapping; + +/// +/// Maps the driver-agnostic (OPC UA Part 13 aggregate) onto the +/// gateway's native . +/// +/// +/// +/// Average/Minimum/Maximum line up with the legacy Wonderware client's aggregate mapping. The two +/// remaining members are now served by native gateway retrieval modes: +/// and +/// . +/// +/// +/// This replaces the Wonderware-era client-side workarounds (Total derived as Average × interval, +/// Count approximated from a value count): no client-side scaling is performed any more, so the +/// gateway path is a strict improvement. +/// +/// +internal static class AggregateModeMapper +{ + /// Maps an aggregate function to the gateway retrieval mode. + /// The driver-agnostic aggregate function. + /// The matching gateway . + /// A future, unmapped enum member (fails the matrix guard). + public static RetrievalMode ToRetrievalMode(HistoryAggregateType aggregate) => aggregate switch + { + HistoryAggregateType.Average => RetrievalMode.TimeWeightedAverage, + HistoryAggregateType.Minimum => RetrievalMode.MinimumWithTime, + HistoryAggregateType.Maximum => RetrievalMode.MaximumWithTime, + HistoryAggregateType.Total => RetrievalMode.Integral, + HistoryAggregateType.Count => RetrievalMode.Counter, + _ => throw new ArgumentOutOfRangeException( + nameof(aggregate), aggregate, "Unmapped HistoryAggregateType — add a RetrievalMode mapping."), + }; +} diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/Mapping/AggregateModeMapperTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/Mapping/AggregateModeMapperTests.cs new file mode 100644 index 00000000..871b513f --- /dev/null +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/Mapping/AggregateModeMapperTests.cs @@ -0,0 +1,25 @@ +using Xunit; +using ZB.MOM.WW.OtOpcUa.Core.Abstractions; // HistoryAggregateType +using ZB.MOM.WW.HistorianGateway.Contracts.Grpc; // RetrievalMode +using ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Mapping; + +namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests.Mapping; + +public sealed class AggregateModeMapperTests +{ + [Theory] + [InlineData(HistoryAggregateType.Average, RetrievalMode.TimeWeightedAverage)] + [InlineData(HistoryAggregateType.Minimum, RetrievalMode.MinimumWithTime)] + [InlineData(HistoryAggregateType.Maximum, RetrievalMode.MaximumWithTime)] + [InlineData(HistoryAggregateType.Total, RetrievalMode.Integral)] + [InlineData(HistoryAggregateType.Count, RetrievalMode.Counter)] + public void Maps_each_aggregate(HistoryAggregateType a, RetrievalMode expected) + => Assert.Equal(expected, AggregateModeMapper.ToRetrievalMode(a)); + + [Fact] // matrix guard: a new HistoryAggregateType member must fail here + public void Every_aggregate_member_is_mapped() + { + foreach (var a in Enum.GetValues()) + _ = AggregateModeMapper.ToRetrievalMode(a); // must not throw for any defined member + } +}