63289d377c
Wire the materialised AlarmConditionState method handlers so a client calling Acknowledge/Confirm/Shelve/AddComment is gated on the AlarmAck data-plane role and, when allowed, routed back to the scripted-alarm engine via a new `alarm-commands` DistributedPubSub topic. - Commons: new AlarmCommand DTO (AlarmId/Operation/User/Comment/UnshelveAtUtc). - ScriptedAlarmHostActor: add AlarmCommandsTopic const. - OtOpcUaNodeManager: settable AlarmCommandRouter + wire OnAcknowledge/OnConfirm/ OnAddComment/OnShelve/OnTimedUnshelve. Each resolves the principal off ISessionOperationContext.UserIdentity as RoleCarryingUserIdentity, fails closed (BadUserAccessDenied) when the AlarmAck role is absent or no identity, else maps + routes an AlarmCommand and returns Good. OnShelve discriminates OneShotShelve/ TimedShelve/Unshelve from the SDK flags; TimedShelve expiry = UtcNow + ms. No Akka/IActorRef handle — only the Action<AlarmCommand> delegate. T20 de-dup note left; WriteAlarmCondition untouched. - OpcUaServer.Security: OpcUaDataPlaneRoles.AlarmAck shared const (the role was a bare string everywhere; introduced one symbol for the gate + tests). - OtOpcUaSdkServer: SetAlarmCommandRouter pass-through. - Host: boot wiring publishes each command via mediator.Tell(Publish(...)) using a lazy ActorSystem accessor (mirrors DpsScriptLogPublisher). - Tests: 11 new gate + mapping tests (OpcUaServer.Tests 88->99, all green).
47 lines
2.2 KiB
C#
47 lines
2.2 KiB
C#
using Opc.Ua;
|
|
using Opc.Ua.Server;
|
|
using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.OpcUaServer;
|
|
|
|
/// <summary>
|
|
/// <see cref="StandardServer"/> subclass that wires in the v2 <see cref="OtOpcUaNodeManager"/>.
|
|
/// Exposes the live node manager after start so callers (<see cref="OpcUaApplicationHost"/>,
|
|
/// the fused Host's DI binding) can wrap it in a <see cref="SdkAddressSpaceSink"/> and hand
|
|
/// it to <c>OpcUaPublishActor</c>.
|
|
/// </summary>
|
|
public sealed class OtOpcUaSdkServer : StandardServer
|
|
{
|
|
private OtOpcUaNodeManager? _otOpcUaNodeManager;
|
|
|
|
/// <summary>The custom node manager once <c>StartAsync</c> has called
|
|
/// <see cref="CreateMasterNodeManager"/>. Null until the SDK has bootstrapped.</summary>
|
|
public OtOpcUaNodeManager? NodeManager => _otOpcUaNodeManager;
|
|
|
|
/// <summary>
|
|
/// Wire the reverse-path sink for inbound Part 9 alarm method calls onto the created
|
|
/// <see cref="OtOpcUaNodeManager"/>. The host calls this after start with a non-blocking
|
|
/// <c>mediator.Tell</c> that publishes each <see cref="AlarmCommand"/> onto the
|
|
/// <c>alarm-commands</c> DistributedPubSub topic. No-op (returns <c>false</c>) when the node
|
|
/// manager has not been created yet, so the caller can detect a too-early call.
|
|
/// </summary>
|
|
/// <param name="router">The router invoked by the condition handlers 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 SetAlarmCommandRouter(Action<AlarmCommand>? router)
|
|
{
|
|
if (_otOpcUaNodeManager is null) return false;
|
|
_otOpcUaNodeManager.AlarmCommandRouter = router;
|
|
return true;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override MasterNodeManager CreateMasterNodeManager(
|
|
IServerInternal server, ApplicationConfiguration configuration)
|
|
{
|
|
_otOpcUaNodeManager = new OtOpcUaNodeManager(server, configuration);
|
|
return new MasterNodeManager(server, configuration, dynamicNamespaceUri: null, _otOpcUaNodeManager);
|
|
}
|
|
}
|