feat(alarms): wire NativeAlarmAckRouter to DriverHostActor in host DI [H6e]
This commit is contained in:
@@ -139,6 +139,45 @@ public sealed class OtOpcUaServerHostedService : IHostedService, IAsyncDisposabl
|
||||
}
|
||||
});
|
||||
|
||||
// Wire the reverse-path native-alarm-ack router (H6e): a client Acknowledge of a NATIVE (driver-fed,
|
||||
// e.g. Galaxy) Part 9 condition that passes the node manager's AlarmAck gate routes the acknowledge to
|
||||
// the owning driver child. The node manager maps the inbound ack to an OpcUaServer-local NativeAlarmAck;
|
||||
// HERE the host maps that to the Runtime DriverHostActor.RouteNativeAlarmAck (field-for-field) and Tells
|
||||
// it into the local DriverHostActor, which Primary-gates + resolves the owning DriverInstanceActor. The
|
||||
// mapping happens at this boundary so Runtime stays free of an OpcUaServer dependency. The Tell is
|
||||
// fire-and-forget so the handler — which runs under the SDK's Lock — never blocks. The DriverHostActor
|
||||
// ref is resolved LAZILY per ack (this StartAsync runs before the Akka DriverHostActor registers, so a
|
||||
// one-shot resolve here would always miss); by ack time the registry has it, mirroring the node-write
|
||||
// gateway below.
|
||||
_server.SetNativeAlarmAckRouter(nativeAck =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_actorRegistry.TryGet<DriverHostActorKey>(out var driverHost))
|
||||
{
|
||||
driverHost.Tell(new DriverHostActor.RouteNativeAlarmAck(
|
||||
nativeAck.ConditionNodeId, nativeAck.Comment, nativeAck.OperatorUser));
|
||||
}
|
||||
else
|
||||
{
|
||||
// No driver host registered yet (admin-only node, or pre-registration race). The Part 9 ack
|
||||
// already committed the local condition state; the missed routing surfaces as a non-applied
|
||||
// upstream acknowledge, not a client-visible error.
|
||||
_logger.LogWarning(
|
||||
"OtOpcUaServerHostedService: native alarm ack for {ConditionNodeId} dropped — no DriverHostActor registered",
|
||||
nativeAck.ConditionNodeId);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// The router runs under the SDK Lock on a server thread; a hiccup must not escape into the SDK's
|
||||
// Call path. Log + drop — the client still gets Good for the condition-state change.
|
||||
_logger.LogWarning(ex,
|
||||
"OtOpcUaServerHostedService: failed to route native alarm ack for {ConditionNodeId}",
|
||||
nativeAck.ConditionNodeId);
|
||||
}
|
||||
});
|
||||
|
||||
// Wire the reverse-path inbound operator-write gateway: a client write to a writable equipment-tag
|
||||
// node that passes the node manager's WriteOperate gate routes the write to the owning driver child
|
||||
// (RouteNodeWrite → NodeWriteResult) via the local DriverHostActor. The node manager calls the
|
||||
@@ -185,6 +224,8 @@ public sealed class OtOpcUaServerHostedService : IHostedService, IAsyncDisposabl
|
||||
_deferredServiceLevel.SetInner(null);
|
||||
// Restore the Null write gateway so a late client write doesn't Ask a stopping DriverHostActor.
|
||||
_server?.SetNodeWriteGateway(null);
|
||||
// Clear the native-alarm-ack router so a late ack doesn't Tell a stopping DriverHostActor.
|
||||
_server?.SetNativeAlarmAckRouter(null);
|
||||
// Restore the Null historian so a late HistoryRead doesn't hit a disposed read client.
|
||||
_server?.SetHistorianDataSource(null);
|
||||
return Task.CompletedTask;
|
||||
|
||||
@@ -37,6 +37,27 @@ public sealed class OtOpcUaSdkServer : StandardServer
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wire the reverse-path router for inbound Part 9 Acknowledge of a NATIVE (driver-fed, e.g. Galaxy)
|
||||
/// condition onto the created <see cref="OtOpcUaNodeManager"/>. The host calls this after start with a
|
||||
/// non-blocking router that Tells a <c>DriverHostActor.RouteNativeAlarmAck</c> into the local
|
||||
/// <c>DriverHostActor</c>, which (Primary-gated) routes the acknowledge to the owning driver child.
|
||||
/// Native conditions are not owned by the scripted-alarm engine, so the node manager branches their
|
||||
/// inbound Acknowledge to this seam instead of the scripted <see cref="SetAlarmCommandRouter"/> path.
|
||||
/// Passing <c>null</c> clears it. No-op (returns <c>false</c>) when the node manager has not been
|
||||
/// created yet, so the caller can detect a too-early call (mirrors <see cref="SetAlarmCommandRouter"/>).
|
||||
/// </summary>
|
||||
/// <param name="router">The router invoked by the native condition's Acknowledge handler once the
|
||||
/// <c>AlarmAck</c> gate passes; may be <c>null</c> to clear it.</param>
|
||||
/// <returns><c>true</c> when the router was set on a live node manager; <c>false</c> when no node
|
||||
/// manager exists yet.</returns>
|
||||
public bool SetNativeAlarmAckRouter(Action<NativeAlarmAck>? router)
|
||||
{
|
||||
if (_otOpcUaNodeManager is null) return false;
|
||||
_otOpcUaNodeManager.NativeAlarmAckRouter = router;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wire the reverse-path gateway for inbound operator writes to writable equipment-tag nodes onto the
|
||||
/// created <see cref="OtOpcUaNodeManager"/>. The host calls this after start with an
|
||||
|
||||
Reference in New Issue
Block a user