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; + } } ///