feat(runtime): HistorianAdapter + PeerOpcUaProbe + DbHealthProbe actors (engine wiring tracked as F11/F12)

This commit is contained in:
Joseph Doherty
2026-05-26 05:09:06 -04:00
parent e115f13104
commit 28639cb14d
4 changed files with 194 additions and 0 deletions
@@ -0,0 +1,54 @@
using Akka.Actor;
using Akka.Cluster.Tools.PublishSubscribe;
using Akka.Event;
using ZB.MOM.WW.OtOpcUa.Commons.Types;
namespace ZB.MOM.WW.OtOpcUa.Runtime.Health;
/// <summary>
/// Periodically pings a peer node's OPC UA endpoint (<c>opc.tcp://peer:4840</c>) and publishes
/// the result on the cluster's redundancy-state input topic so the admin <c>RedundancyStateActor</c>
/// can react. Real OPC UA probe call is staged for follow-up F12.
/// </summary>
public sealed class PeerOpcUaProbeActor : ReceiveActor, IWithTimers
{
// Owned by ControlPlane.Redundancy.RedundancyStateActor; duplicated here to avoid a
// Runtime → ControlPlane project reference. Keep both literals in lock-step.
public const string RedundancyStateTopic = "redundancy-state";
public static readonly TimeSpan DefaultProbeInterval = TimeSpan.FromSeconds(10);
public sealed record OpcUaProbeResult(NodeId NodeId, bool Ok);
public sealed class Tick { public static readonly Tick Instance = new(); private Tick() { } }
private readonly NodeId _peer;
private readonly TimeSpan _interval;
private readonly Action<object>? _broadcastOverride;
private readonly ILoggingAdapter _log = Context.GetLogger();
public ITimerScheduler Timers { get; set; } = null!;
public static Props Props(NodeId peer, TimeSpan? interval = null, Action<object>? broadcast = null) =>
Akka.Actor.Props.Create(() => new PeerOpcUaProbeActor(peer, interval ?? DefaultProbeInterval, broadcast));
public PeerOpcUaProbeActor(NodeId peer, TimeSpan interval, Action<object>? broadcastOverride)
{
_peer = peer;
_interval = interval;
_broadcastOverride = broadcastOverride;
Receive<Tick>(_ => RunProbe());
}
protected override void PreStart() =>
Timers.StartPeriodicTimer("probe", Tick.Instance, _interval);
private void RunProbe()
{
// F12: actual opc.tcp ping. Assume Ok=true until the probe is wired.
var msg = new OpcUaProbeResult(_peer, Ok: true);
if (_broadcastOverride is not null) _broadcastOverride(msg);
else DistributedPubSub.Get(Context.System).Mediator.Tell(new Publish(RedundancyStateTopic, msg));
_log.Debug("PeerOpcUaProbe: pinged {Peer} (probe staged for F12)", _peer);
}
}