Some checks failed
v2-ci / build (push) Failing after 42s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (push) Has been skipped
Phase7Composer now carries UnsAreaProjection + UnsLineProjection lists so the applier can materialise the full UNS topology in the OPC UA address space. New IOpcUaAddressSpaceSink.EnsureFolder(folderNodeId, parentNodeId, displayName) seam (no-op default, recorded in tests, forwarded by DeferredAddressSpaceSink, implemented by SdkAddressSpaceSink). The SDK- side OtOpcUaNodeManager gains an EnsureFolder API that creates FolderState nodes with proper parent linkage; RebuildAddressSpace now clears folders too so re-applies don't accumulate stale topology. Phase7Applier.MaterialiseHierarchy walks composition.UnsAreas → composition.UnsLines → composition.EquipmentNodes, calling EnsureFolder with the correct parent at each level. Idempotent — calling twice with the same composition is a no-op. OpcUaPublishActor.HandleRebuild invokes it after Phase7Applier.Apply so OPC UA clients browsing the server now see Area/Line/Equipment as proper folders rather than flat tag ids. DeploymentArtifact.ParseComposition reads UnsAreas + UnsLines from the JSON snapshot the ControlPlane emits, populating the new fields when present. Phase7Composer.Compose now accepts UnsAreas + UnsLines; a 3-arg overload preserves the old signature for legacy callers + existing tests. The Phase7CompositionResult convenience ctor likewise keeps the planner tests working without UNS data. 3 new hierarchy tests (pure unit + boot-verify against a real OtOpcUaSdkServer); OpcUaServer suite is 48/48 green (was 45, +3), Runtime 74/74 unchanged. Closes #85.
35 lines
1.9 KiB
C#
35 lines
1.9 KiB
C#
namespace ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
|
|
|
|
/// <summary>
|
|
/// Wrapper <see cref="IOpcUaAddressSpaceSink"/> that defers to an inner sink swapped in at
|
|
/// runtime. Needed because the production sink (<c>SdkAddressSpaceSink</c>) wraps an
|
|
/// <c>OtOpcUaNodeManager</c> that only exists after the SDK <c>StandardServer</c> has
|
|
/// started — but Akka actors resolve their sink dependency at construction time, before
|
|
/// the hosted service has booted the SDK.
|
|
///
|
|
/// Bound as a singleton in DI on driver-role hosts; the OPC UA hosted service calls
|
|
/// <see cref="SetSink"/> once the server is up. Until that swap happens, every call is a
|
|
/// no-op against <see cref="NullOpcUaAddressSpaceSink"/>, so the actor stays safe to
|
|
/// receive messages from the moment it boots.
|
|
/// </summary>
|
|
public sealed class DeferredAddressSpaceSink : IOpcUaAddressSpaceSink
|
|
{
|
|
private volatile IOpcUaAddressSpaceSink _inner = NullOpcUaAddressSpaceSink.Instance;
|
|
|
|
/// <summary>Swap in the production sink. Pass <c>null</c> to revert to the null sink
|
|
/// (used during graceful shutdown so post-stop writes don't hit a half-disposed manager).</summary>
|
|
public void SetSink(IOpcUaAddressSpaceSink? sink) =>
|
|
_inner = sink ?? NullOpcUaAddressSpaceSink.Instance;
|
|
|
|
public void WriteValue(string nodeId, object? value, OpcUaQuality quality, DateTime sourceTimestampUtc)
|
|
=> _inner.WriteValue(nodeId, value, quality, sourceTimestampUtc);
|
|
|
|
public void WriteAlarmState(string alarmNodeId, bool active, bool acknowledged, DateTime sourceTimestampUtc)
|
|
=> _inner.WriteAlarmState(alarmNodeId, active, acknowledged, sourceTimestampUtc);
|
|
|
|
public void EnsureFolder(string folderNodeId, string? parentNodeId, string displayName)
|
|
=> _inner.EnsureFolder(folderNodeId, parentNodeId, displayName);
|
|
|
|
public void RebuildAddressSpace() => _inner.RebuildAddressSpace();
|
|
}
|