diff --git a/src/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OpcUaApplicationHost.cs b/src/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OpcUaApplicationHost.cs index a1a7abf..7fe3046 100644 --- a/src/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OpcUaApplicationHost.cs +++ b/src/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OpcUaApplicationHost.cs @@ -5,6 +5,8 @@ using ZB.MOM.WW.OtOpcUa.Configuration.LocalCache; using ZB.MOM.WW.OtOpcUa.Core.Hosting; using ZB.MOM.WW.OtOpcUa.Core.OpcUa; using ZB.MOM.WW.OtOpcUa.Core.Resilience; +using ZB.MOM.WW.OtOpcUa.Server.Alarms; +using ZB.MOM.WW.OtOpcUa.Server.History; using ZB.MOM.WW.OtOpcUa.Server.Observability; using ZB.MOM.WW.OtOpcUa.Server.Security; @@ -40,6 +42,12 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable private ZB.MOM.WW.OtOpcUa.Core.Abstractions.IReadable? _virtualReadable; private ZB.MOM.WW.OtOpcUa.Core.Abstractions.IReadable? _scriptedAlarmReadable; + // PR 1+2.W — server-level singletons. Threaded through to OtOpcUaServer + every + // DriverNodeManager. Default null preserves existing test construction sites that + // don't opt into the new server-side history routing or alarm-condition state machine. + private readonly IHistoryRouter? _historyRouter; + private readonly AlarmConditionService? _alarmConditionService; + private readonly ILoggerFactory _loggerFactory; private readonly ILogger _logger; private ApplicationInstance? _application; @@ -57,7 +65,9 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable Func? resilienceConfigLookup = null, Func? equipmentContentLookup = null, ZB.MOM.WW.OtOpcUa.Core.Abstractions.IReadable? virtualReadable = null, - ZB.MOM.WW.OtOpcUa.Core.Abstractions.IReadable? scriptedAlarmReadable = null) + ZB.MOM.WW.OtOpcUa.Core.Abstractions.IReadable? scriptedAlarmReadable = null, + IHistoryRouter? historyRouter = null, + AlarmConditionService? alarmConditionService = null) { _options = options; _driverHost = driverHost; @@ -71,6 +81,8 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable _equipmentContentLookup = equipmentContentLookup; _virtualReadable = virtualReadable; _scriptedAlarmReadable = scriptedAlarmReadable; + _historyRouter = historyRouter; + _alarmConditionService = alarmConditionService; _loggerFactory = loggerFactory; _logger = logger; } @@ -136,7 +148,8 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable authzGate: _authzGate, scopeResolver: _scopeResolver, tierLookup: _tierLookup, resilienceConfigLookup: _resilienceConfigLookup, virtualReadable: _virtualReadable, scriptedAlarmReadable: _scriptedAlarmReadable, - anonymousRoles: _options.AnonymousRoles); + anonymousRoles: _options.AnonymousRoles, + historyRouter: _historyRouter, alarmConditionService: _alarmConditionService); await _application.Start(_server).ConfigureAwait(false); _logger.LogInformation("OPC UA server started — endpoint={Endpoint} driverCount={Count}", diff --git a/src/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OtOpcUaServer.cs b/src/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OtOpcUaServer.cs index f2154eb..ed181e0 100644 --- a/src/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OtOpcUaServer.cs +++ b/src/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OtOpcUaServer.cs @@ -6,6 +6,8 @@ using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Core.Hosting; using ZB.MOM.WW.OtOpcUa.Core.OpcUa; using ZB.MOM.WW.OtOpcUa.Core.Resilience; +using ZB.MOM.WW.OtOpcUa.Server.Alarms; +using ZB.MOM.WW.OtOpcUa.Server.History; using ZB.MOM.WW.OtOpcUa.Server.Security; namespace ZB.MOM.WW.OtOpcUa.Server.OpcUa; @@ -34,6 +36,13 @@ public sealed class OtOpcUaServer : StandardServer private readonly IReadable? _virtualReadable; private readonly IReadable? _scriptedAlarmReadable; + // PR 1+2.W — server-level singletons shared across every DriverNodeManager. + // Null when the deployment hasn't opted into the new server-side history routing / + // server-side alarm-condition state machine; DriverNodeManager falls back to the + // legacy per-driver IHistoryProvider + IAlarmSource paths in that case. + private readonly IHistoryRouter? _historyRouter; + private readonly AlarmConditionService? _alarmConditionService; + /// /// Roles granted to anonymous sessions. When non-empty, /// wraps AnonymousIdentityToken in a carrying @@ -57,7 +66,9 @@ public sealed class OtOpcUaServer : StandardServer Func? resilienceConfigLookup = null, IReadable? virtualReadable = null, IReadable? scriptedAlarmReadable = null, - IReadOnlyList? anonymousRoles = null) + IReadOnlyList? anonymousRoles = null, + IHistoryRouter? historyRouter = null, + AlarmConditionService? alarmConditionService = null) { _driverHost = driverHost; _authenticator = authenticator; @@ -69,6 +80,8 @@ public sealed class OtOpcUaServer : StandardServer _virtualReadable = virtualReadable; _scriptedAlarmReadable = scriptedAlarmReadable; _anonymousRoles = anonymousRoles ?? []; + _historyRouter = historyRouter; + _alarmConditionService = alarmConditionService; _loggerFactory = loggerFactory; } @@ -102,7 +115,14 @@ public sealed class OtOpcUaServer : StandardServer var invoker = new CapabilityInvoker(_pipelineBuilder, driver.DriverInstanceId, () => options, driver.DriverType); var manager = new DriverNodeManager(server, configuration, driver, invoker, logger, authzGate: _authzGate, scopeResolver: _scopeResolver, - virtualReadable: _virtualReadable, scriptedAlarmReadable: _scriptedAlarmReadable); + virtualReadable: _virtualReadable, scriptedAlarmReadable: _scriptedAlarmReadable, + historyRouter: _historyRouter, alarmService: _alarmConditionService); + + // The router stays empty after PR 1+2.W — DriverNodeManager's internal + // LegacyDriverHistoryAdapter handles every driver that still implements + // IHistoryProvider. PR 3.W will register the Wonderware sidecar as a router + // source; PR 7.2 retires the legacy fallback entirely. + _driverNodeManagers.Add(manager); } diff --git a/src/ZB.MOM.WW.OtOpcUa.Server/Program.cs b/src/ZB.MOM.WW.OtOpcUa.Server/Program.cs index 17d7943..c55d5e6 100644 --- a/src/ZB.MOM.WW.OtOpcUa.Server/Program.cs +++ b/src/ZB.MOM.WW.OtOpcUa.Server/Program.cs @@ -17,6 +17,8 @@ using ZB.MOM.WW.OtOpcUa.Driver.Modbus; using ZB.MOM.WW.OtOpcUa.Driver.S7; using ZB.MOM.WW.OtOpcUa.Driver.TwinCAT; using ZB.MOM.WW.OtOpcUa.Server; +using ZB.MOM.WW.OtOpcUa.Server.Alarms; +using ZB.MOM.WW.OtOpcUa.Server.History; using ZB.MOM.WW.OtOpcUa.Server.Hosting; using ZB.MOM.WW.OtOpcUa.Server.OpcUa; using ZB.MOM.WW.OtOpcUa.Server.Phase7; @@ -137,6 +139,14 @@ builder.Services.AddScoped(); // to ACL enforcement accidentally on upgrade. builder.Services.AddSingleton(); +// PR 1+2.W — server-level history routing + alarm-condition state machine. Singletons +// shared across every DriverNodeManager. The router stays empty after this PR; +// PR 3.W registers the Wonderware historian sidecar as a router source. The alarm +// service runs the Active/Acknowledged/Inactive state machine for any driver that +// declares alarms via AlarmConditionInfo's sub-attribute refs. +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + builder.Services.AddSingleton(sp => { var registry = sp.GetRequiredService(); @@ -146,7 +156,9 @@ builder.Services.AddSingleton(sp => sp.GetRequiredService(), sp.GetRequiredService(), sp.GetRequiredService>(), - equipmentContentLookup: registry.Get); + equipmentContentLookup: registry.Get, + historyRouter: sp.GetRequiredService(), + alarmConditionService: sp.GetRequiredService()); }); builder.Services.AddHostedService();