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"); } } }