using Akka.Actor; using Akka.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using ZB.MOM.WW.OtOpcUa.Commons.Interfaces; using ZB.MOM.WW.OtOpcUa.Configuration; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian; using ZB.MOM.WW.OtOpcUa.Runtime.Drivers; using ZB.MOM.WW.OtOpcUa.Runtime.Health; using ZB.MOM.WW.OtOpcUa.Runtime.Historian; namespace ZB.MOM.WW.OtOpcUa.Runtime; public static class ServiceCollectionExtensions { public const string DriverRole = "driver"; public const string DriverHostActorName = "driver-host"; public const string DbHealthProbeActorName = "db-health"; public const string HistorianAdapterActorName = "historian-adapter"; /// /// Registers shared runtime services. Currently binds /// to as the default; production deployments /// override this with SqliteStoreAndForwardSink wrapping WonderwareHistorianClient. /// Call this BEFORE AddAkka. /// public static IServiceCollection AddOtOpcUaRuntime(this IServiceCollection services) { services.TryAddSingleton(NullAlarmHistorianSink.Instance); services.TryAddSingleton(NullDriverFactory.Instance); return services; } /// /// Spawns the per-node driver-role actors on the host's : /// (one per node), /// (consumed by the health endpoint + redundancy calc), and /// wrapping the registered . /// /// Mirror of WithOtOpcUaControlPlaneSingletons for the driver role. Both must /// be registered on the same as the cluster /// bootstrap so the actors share the host's ActorSystem. /// /// Wire from the fused Host's Program.cs when the node carries the driver role: /// /// services.AddOtOpcUaRuntime(); /// services.AddAkka("otopcua", (ab, sp) => { ab.WithOtOpcUaClusterBootstrap(sp); if (hasDriver) ab.WithOtOpcUaRuntimeActors(); }); /// /// public static AkkaConfigurationBuilder WithOtOpcUaRuntimeActors(this AkkaConfigurationBuilder builder) { builder.WithActors((system, registry, resolver) => { var dbFactory = resolver.GetService>(); var roleInfo = resolver.GetService(); // Fallback to Null* if AddOtOpcUaRuntime wasn't called (e.g., test harnesses). var historianSink = resolver.GetService() ?? NullAlarmHistorianSink.Instance; var driverFactory = resolver.GetService() ?? NullDriverFactory.Instance; var dbHealth = system.ActorOf( DbHealthProbeActor.Props(dbFactory), DbHealthProbeActorName); registry.Register(dbHealth); var driverHost = system.ActorOf( DriverHostActor.Props(dbFactory, roleInfo.LocalNode, coordinator: null, driverFactory: driverFactory, localRoles: roleInfo.LocalRoles), DriverHostActorName); registry.Register(driverHost); var historian = system.ActorOf( HistorianAdapterActor.Props(historianSink), HistorianAdapterActorName); registry.Register(historian); }); return builder; } } /// Marker key types used by Akka.Hosting to resolve runtime actors from the registry. public sealed class DriverHostActorKey { } public sealed class DbHealthProbeActorKey { } public sealed class HistorianAdapterActorKey { }