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) { }
}
}