using Akka.Actor; using Akka.Event; using ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian; namespace ZB.MOM.WW.OtOpcUa.Runtime.Historian; /// /// Thin actor wrapper around . Engine code (ScriptedAlarmActor, /// Galaxy native alarm bridge, AB CIP ALMD reader) tells s to this /// actor; the actor enqueues them on the sink fire-and-forget. Production deployments register /// against IAlarmHistorianSink; the sink owns the /// durable queue + drain-to-Wonderware-pipe loop. The actor here owns nothing operational beyond /// the message contract — its job is to keep the engine actors on Akka's mailbox without blocking /// them on disk I/O or pipe handshakes. /// /// Query queue depth + drain health via . /// public sealed class HistorianAdapterActor : ReceiveActor { public sealed record GetStatus { public static readonly GetStatus Instance = new(); } private readonly IAlarmHistorianSink _sink; private readonly ILoggingAdapter _log = Context.GetLogger(); /// Creates the props for a HistorianAdapterActor instance. /// The alarm historian sink implementation, or null to use a null sink. /// Props configured for creating a HistorianAdapterActor. public static Props Props(IAlarmHistorianSink? sink = null) => Akka.Actor.Props.Create(() => new HistorianAdapterActor(sink ?? NullAlarmHistorianSink.Instance)); /// Initializes a new instance of the HistorianAdapterActor class. /// The alarm historian sink to forward enqueued events to. public HistorianAdapterActor(IAlarmHistorianSink sink) { _sink = sink; Receive(evt => { // Fire-and-forget: SqliteStoreAndForwardSink persists to local SQLite synchronously // inside EnqueueAsync (it returns once the row is committed), so we don't block on // network/pipe latency. Failures are surfaced via GetStatus's LastError + drain state. _ = EnqueueAsync(evt); }); Receive(_ => Sender.Tell(_sink.GetStatus())); } private async Task EnqueueAsync(AlarmHistorianEvent evt) { try { await _sink.EnqueueAsync(evt, CancellationToken.None); } catch (Exception ex) { _log.Error(ex, "Historian sink rejected event for {AlarmId} at {Ts}", evt.AlarmId, evt.TimestampUtc); } } }