using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using ZB.MOM.WW.OtOpcUa.Core.Hosting; using ZB.MOM.WW.OtOpcUa.Server.OpcUa; namespace ZB.MOM.WW.OtOpcUa.Server; /// /// BackgroundService that owns the OPC UA server lifecycle (decision #30, replacing TopShelf). /// Bootstraps config, starts the , starts the OPC UA server via /// , drives each driver's discovery into the address space, /// runs until stopped. /// public sealed class OpcUaServerService( NodeBootstrap bootstrap, DriverHost driverHost, OpcUaApplicationHost applicationHost, ILogger logger) : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { logger.LogInformation("OtOpcUa.Server starting"); var result = await bootstrap.LoadCurrentGenerationAsync(stoppingToken); logger.LogInformation("Bootstrap complete: source={Source} generation={Gen}", result.Source, result.GenerationId); // PR 17: stand up the OPC UA server + drive discovery per registered driver. Driver // registration itself (RegisterAsync on DriverHost) happens during an earlier DI // extension once the central config DB query + per-driver factory land; for now the // server comes up with whatever drivers are in DriverHost at start time. await applicationHost.StartAsync(stoppingToken); logger.LogInformation("OtOpcUa.Server running. Hosted drivers: {Count}", driverHost.RegisteredDriverIds.Count); try { await Task.Delay(Timeout.InfiniteTimeSpan, stoppingToken); } catch (OperationCanceledException) { logger.LogInformation("OtOpcUa.Server stopping"); } } public override async Task StopAsync(CancellationToken cancellationToken) { await base.StopAsync(cancellationToken); await applicationHost.DisposeAsync(); await driverHost.DisposeAsync(); } }