feat(runtime): F11 — HistorianAdapterActor wired to IAlarmHistorianSink
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been cancelled
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been cancelled
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been cancelled
v2-ci / build (push) Has been cancelled
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been cancelled
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been cancelled
v2-ci / integration (push) Has been cancelled
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been cancelled
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been cancelled
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been cancelled
v2-ci / build (push) Has been cancelled
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been cancelled
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been cancelled
v2-ci / integration (push) Has been cancelled
Reshapes the placeholder buffered-counter actor into a thin fire-and-forget bridge over the existing IAlarmHistorianSink contract. Default sink is NullAlarmHistorianSink; production deployments override the DI binding to SqliteStoreAndForwardSink wrapping WonderwareHistorianClient (the v1 components in src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware* are reused verbatim — actor is just a mailbox-friendly entry point). - HistorianAdapterActor.Props(IAlarmHistorianSink?) — null defaults to NullAlarmHistorianSink - Receive<AlarmHistorianEvent>: fire-and-forget sink.EnqueueAsync - Receive<GetStatus>: returns sink.GetStatus() (queue depth + drain state) - ServiceCollectionExtensions.AddOtOpcUaRuntime registers the default sink - WithOtOpcUaRuntimeActors spawns the actor + registers HistorianAdapterActorKey - Program.cs calls AddOtOpcUaRuntime when hasDriver Tests: 2 new (forward-to-sink + GetStatus). Runtime suite 17 → 18.
This commit is contained in:
@@ -1,32 +1,58 @@
|
||||
using Akka.Actor;
|
||||
using Akka.Event;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Runtime.Historian;
|
||||
|
||||
/// <summary>
|
||||
/// Wraps the named-pipe IPC to the Wonderware historian sidecar with a store-and-forward
|
||||
/// SQLite buffer for pipe outages. Engine wiring (named-pipe client + <c>SqliteStoreAndForwardSink</c>)
|
||||
/// is staged for follow-up F11.
|
||||
/// Thin actor wrapper around <see cref="IAlarmHistorianSink"/>. Engine code (ScriptedAlarmActor,
|
||||
/// Galaxy native alarm bridge, AB CIP ALMD reader) tells <see cref="AlarmHistorianEvent"/>s to this
|
||||
/// actor; the actor enqueues them on the sink fire-and-forget. Production deployments register
|
||||
/// <see cref="SqliteStoreAndForwardSink"/> against <c>IAlarmHistorianSink</c>; 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 <see cref="GetStatus"/>.
|
||||
/// </summary>
|
||||
public sealed class HistorianAdapterActor : ReceiveActor
|
||||
{
|
||||
public sealed record HistoryRow(string Source, string AttributeId, object? Value, DateTime TimestampUtc);
|
||||
|
||||
private readonly ILoggingAdapter _log = Context.GetLogger();
|
||||
private int _buffered;
|
||||
|
||||
public int BufferedCount => _buffered;
|
||||
|
||||
public static Props Props() => Akka.Actor.Props.Create(() => new HistorianAdapterActor());
|
||||
|
||||
public HistorianAdapterActor()
|
||||
public sealed record GetStatus
|
||||
{
|
||||
Receive<HistoryRow>(row =>
|
||||
public static readonly GetStatus Instance = new();
|
||||
}
|
||||
|
||||
private readonly IAlarmHistorianSink _sink;
|
||||
private readonly ILoggingAdapter _log = Context.GetLogger();
|
||||
|
||||
public static Props Props(IAlarmHistorianSink? sink = null) =>
|
||||
Akka.Actor.Props.Create(() => new HistorianAdapterActor(sink ?? NullAlarmHistorianSink.Instance));
|
||||
|
||||
public HistorianAdapterActor(IAlarmHistorianSink sink)
|
||||
{
|
||||
_sink = sink;
|
||||
|
||||
Receive<AlarmHistorianEvent>(evt =>
|
||||
{
|
||||
// F11: dispatch to named-pipe sink; on disconnect → buffer in SQLite.
|
||||
Interlocked.Increment(ref _buffered);
|
||||
_log.Debug("Historian: buffered row for {Source}/{Attr} (sink wiring staged for F11)",
|
||||
row.Source, row.AttributeId);
|
||||
// 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<GetStatus>(_ => 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user