using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Runtime.Drivers; /// /// An that RECORDS the streamed tree instead of creating OPC UA /// nodes — used to capture an driver's discovered hierarchy so the /// runtime can graft it under an equipment node. Folder nesting is tracked (each child builder /// carries its accumulated path), so every variable records its full . /// Value nodes only: is ignored and alarm marking returns a no-op sink /// (discovered alarms are out of scope — alarms come via the config path). /// Single-threaded: a driver's DiscoverAsync streams on one caller; the root and its child /// builders share one . Not thread-safe by design. /// public sealed class CapturingAddressSpaceBuilder : IAddressSpaceBuilder { private readonly List _nodes; private readonly IReadOnlyList _path; /// Create a root capturing builder with an empty folder path and a fresh node list. public CapturingAddressSpaceBuilder() : this([], []) { } private CapturingAddressSpaceBuilder(List nodes, IReadOnlyList path) { _nodes = nodes; _path = path; } /// All variables captured across the whole tree (shared by the root and every child scope). public IReadOnlyList Nodes => _nodes; /// public IAddressSpaceBuilder Folder(string browseName, string displayName) => new CapturingAddressSpaceBuilder(_nodes, [.. _path, browseName]); /// public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo attributeInfo) { _nodes.Add(new DiscoveredNode( FolderPathSegments: _path, BrowseName: browseName, DisplayName: displayName, FullReference: attributeInfo.FullName, DataType: attributeInfo.DriverDataType, IsArray: attributeInfo.IsArray, ArrayDim: attributeInfo.ArrayDim, Writable: attributeInfo.SecurityClass != SecurityClassification.ViewOnly, IsHistorized: attributeInfo.IsHistorized)); return new NullHandle(attributeInfo.FullName); } /// public void AddProperty(string browseName, DriverDataType dataType, object? value) { /* metadata only — ignored */ } /// A variable handle whose alarm marking is a no-op (discovered alarms are out of scope). private sealed class NullHandle(string fullRef) : IVariableHandle { public string FullReference => fullRef; public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info) => new NullSink(); } /// A null sink that ignores alarm condition transitions. private sealed class NullSink : IAlarmConditionSink { public void OnTransition(AlarmEventArgs args) { } } }