diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OtOpcUaSdkServer.cs b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OtOpcUaSdkServer.cs
index 507e9ec3..dff0415c 100644
--- a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OtOpcUaSdkServer.cs
+++ b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OtOpcUaSdkServer.cs
@@ -271,29 +271,36 @@ public sealed class OtOpcUaSdkServer : StandardServer
return true;
}
- // Keep the SDK's full set of calculator factories (live-data AggregateFilter subscriptions need
- // them, and ServerCapabilities/AggregateFunctions keeps listing all of them), then trim
- // HistoryServerCapabilities/AggregateFunctions to the node manager's current historian source. At
- // this point that is the Null default (no history aggregates); the Host's SetHistorianDataSource
- // re-syncs to the configured backend's set right after start.
+ // Register the same 37 standard aggregate IDs exactly once, using the versioned application
+ // calculators. Calling base first would create the diagnostic nodes twice: SDK 2.0.1's
+ // RegisterFactoryAsync replaces the factory but unconditionally adds another address-space node.
+ // HistoryServerCapabilities is then trimmed to the currently served historian aggregate set.
///
protected override async ValueTask CreateAggregateManagerAsync(
IServerInternal server, ApplicationConfiguration configuration, CancellationToken cancellationToken = default)
{
- var manager = await base.CreateAggregateManagerAsync(server, configuration, cancellationToken).ConfigureAwait(false);
- // Pin the SDK manager's default AggregateConfiguration (what live AggregateFilter subscriptions compute under
- // and what the server advertises) to OUR defaults. Opc.Ua.Server 1.5.378.182 flipped the SDK default of
- // TreatUncertainAsBad to true; history-read aggregates (Sql.Common evaluators) are fixed at false, so the live
- // and historical paths must agree and stay behaviour-neutral across the SDK hop (lmxopcua#782).
- var defaults = manager.GetDefaultConfiguration(NodeId.Null) ?? new AggregateConfiguration();
- defaults.TreatUncertainAsBad = ServerAggregateConfiguration.TreatUncertainAsBad;
- defaults.PercentDataBad = ServerAggregateConfiguration.PercentDataBad;
- defaults.PercentDataGood = ServerAggregateConfiguration.PercentDataGood;
- defaults.UseSlopedExtrapolation = ServerAggregateConfiguration.UseSlopedExtrapolation;
- manager.SetDefaultConfiguration(defaults);
- await Compatibility.Aggregates.CompatibilityAggregateFactory.RegisterAsync(manager, cancellationToken).ConfigureAwait(false);
- _otOpcUaNodeManager?.SyncAdvertisedAggregates();
- return manager;
+ var manager = new AggregateManager(server);
+ try
+ {
+ // Pin the SDK manager's default AggregateConfiguration (what live AggregateFilter subscriptions compute under
+ // and what the server advertises) to OUR defaults. Opc.Ua.Server 1.5.378.182 flipped the SDK default of
+ // TreatUncertainAsBad to true; history-read aggregates (Sql.Common evaluators) are fixed at false, so the live
+ // and historical paths must agree and stay behaviour-neutral across the SDK hop (lmxopcua#782).
+ var defaults = manager.GetDefaultConfiguration(NodeId.Null) ?? new AggregateConfiguration();
+ defaults.TreatUncertainAsBad = ServerAggregateConfiguration.TreatUncertainAsBad;
+ defaults.PercentDataBad = ServerAggregateConfiguration.PercentDataBad;
+ defaults.PercentDataGood = ServerAggregateConfiguration.PercentDataGood;
+ defaults.UseSlopedExtrapolation = ServerAggregateConfiguration.UseSlopedExtrapolation;
+ manager.SetDefaultConfiguration(defaults);
+ await Compatibility.Aggregates.CompatibilityAggregateFactory.RegisterAsync(manager, cancellationToken).ConfigureAwait(false);
+ _otOpcUaNodeManager?.SyncAdvertisedAggregates();
+ return manager;
+ }
+ catch
+ {
+ manager.Dispose();
+ throw;
+ }
}
///