fix: register compatibility aggregates once during server startup

This commit is contained in:
Joseph Doherty
2026-11-10 10:31:18 -05:00
parent 9efd7667e1
commit 08354231f8
@@ -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.
/// <inheritdoc />
protected override async ValueTask<AggregateManager> 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;
}
}
/// <inheritdoc />