using Akka.Actor;
using Microsoft.Extensions.Logging;
using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
namespace ZB.MOM.WW.OtOpcUa.Runtime.Drivers;
///
/// Akka adapter for the Commons : routes an inbound OPC UA
/// operator write to the local by Asking it a
/// and translating the reply
/// into a .
///
///
/// The node manager calls fire-and-forget from its OnWriteValue handler,
/// which runs under the node-manager Lock, so the method does no blocking work before its first
/// await — resolving the actor and building the Ask returns a Task promptly. The
/// reference is resolved lazily per write via
/// resolveDriverHost: the host wires this gateway during StartAsync, before the Akka
/// registers, so a one-shot resolve at construction would always miss
/// and leave every write unavailable. By write time (long after startup) the registry has it.
///
///
public sealed class ActorNodeWriteGateway : IOpcUaNodeWriteGateway
{
/// Default Ask timeout — matches the legacy inline lambda in the hosted service.
private static readonly TimeSpan DefaultAskTimeout = TimeSpan.FromSeconds(10);
private readonly Func _resolveDriverHost;
private readonly TimeSpan _askTimeout;
private readonly ILogger _logger;
/// Creates the gateway.
/// Lazy per-write resolver for the local ;
/// returns null until the actor has registered (StartAsync ordering — the actor registers AFTER the host
/// wires this gateway).
/// Logger for dropped/rejected/timed-out writes.
/// Ask timeout; defaults to 10s (the legacy lambda's value).
public ActorNodeWriteGateway(Func resolveDriverHost, ILogger logger, TimeSpan? askTimeout = null)
{
_resolveDriverHost = resolveDriverHost ?? throw new ArgumentNullException(nameof(resolveDriverHost));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_askTimeout = askTimeout ?? DefaultAskTimeout;
}
///
public async Task WriteAsync(string nodeId, object? value, AddressSpaceRealm realm, CancellationToken ct)
{
var driverHost = _resolveDriverHost();
if (driverHost is null)
{
_logger.LogWarning("Inbound write to {NodeId} dropped: no DriverHostActor registered", nodeId);
return new NodeWriteOutcome(false, "writes unavailable");
}
try
{
var result = await driverHost.Ask(
new DriverHostActor.RouteNodeWrite(nodeId, value, realm), _askTimeout, ct).ConfigureAwait(false);
if (!result.Success)
_logger.LogWarning("Operator write to {NodeId} rejected: {Reason}", nodeId, result.Reason);
return new NodeWriteOutcome(result.Success, result.Reason);
}
catch (OperationCanceledException ex)
{
_logger.LogWarning(ex, "Operator write to {NodeId} cancelled", nodeId);
return new NodeWriteOutcome(false, "write cancelled");
}
catch (AskTimeoutException ex)
{
_logger.LogWarning(ex, "Operator write to {NodeId} timed out", nodeId);
return new NodeWriteOutcome(false, "write timeout");
}
catch (Exception ex)
{
_logger.LogError(ex, "Operator write to {NodeId} failed unexpectedly", nodeId);
return new NodeWriteOutcome(false, "write error");
}
}
}