feat(dcl): implement BrowseChildrenAsync on RealOpcUaClient
This commit is contained in:
@@ -327,10 +327,70 @@ public class RealOpcUaClient : IOpcUaClient
|
||||
: configured;
|
||||
|
||||
/// <inheritdoc />
|
||||
// Real implementation lands in Task 8 of the OPC UA tag browser plan.
|
||||
public Task<Commons.Interfaces.Protocol.BrowseChildrenResult> BrowseChildrenAsync(
|
||||
public async Task<Commons.Interfaces.Protocol.BrowseChildrenResult> BrowseChildrenAsync(
|
||||
string? parentNodeId, CancellationToken cancellationToken = default)
|
||||
=> throw new NotImplementedException();
|
||||
{
|
||||
// Mirror the SubscribeAsync/ReadAsync wrap idiom: snapshot the session
|
||||
// reference once, fail fast with a typed exception if the link is
|
||||
// down, then call the SDK's async API directly (no Task.Run wrap —
|
||||
// the OPC Foundation SDK already provides true async I/O).
|
||||
var session = _session;
|
||||
if (session is null || !session.Connected)
|
||||
{
|
||||
throw new Commons.Interfaces.Protocol.ConnectionNotConnectedException(
|
||||
"OPC UA session is not connected.");
|
||||
}
|
||||
|
||||
// ObjectsFolder = ns=0;i=85 — the OPC UA standard server root. Empty
|
||||
// / null input means "browse the root"; anything else is parsed as
|
||||
// an absolute NodeId expression.
|
||||
var nodeToBrowse = string.IsNullOrEmpty(parentNodeId)
|
||||
? ObjectIds.ObjectsFolder
|
||||
: NodeId.Parse(parentNodeId);
|
||||
|
||||
// NodeClassMask intentionally excludes ReferenceType, View, Variable-
|
||||
// Type, ObjectType, DataType. UI only needs Objects (navigable),
|
||||
// Variables (selectable), Methods (display-only).
|
||||
var nodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable | NodeClass.Method);
|
||||
|
||||
var (_, continuationPoint, references) = await session.BrowseAsync(
|
||||
null,
|
||||
null,
|
||||
nodeToBrowse,
|
||||
1000u,
|
||||
BrowseDirection.Forward,
|
||||
ReferenceTypeIds.HierarchicalReferences,
|
||||
true,
|
||||
nodeClassMask,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var refs = references ?? new ReferenceDescriptionCollection();
|
||||
var children = new List<Commons.Interfaces.Protocol.BrowseNode>(refs.Count);
|
||||
foreach (var r in refs)
|
||||
{
|
||||
children.Add(new Commons.Interfaces.Protocol.BrowseNode(
|
||||
NodeId: r.NodeId.ToString(),
|
||||
DisplayName: r.DisplayName?.Text ?? r.BrowseName?.Name ?? "(unnamed)",
|
||||
NodeClass: MapNodeClass(r.NodeClass),
|
||||
HasChildren: r.NodeClass == NodeClass.Object));
|
||||
}
|
||||
|
||||
// A non-empty continuation point means the server had more refs than
|
||||
// our requestedMaxReferencesPerNode cap. The UI surfaces a "more
|
||||
// children, type the node id manually" hint rather than auto-paging;
|
||||
// BrowseNext is not invoked here. Discarding the continuation point
|
||||
// is acceptable because the server expires it on session close.
|
||||
var truncated = continuationPoint != null && continuationPoint.Length > 0;
|
||||
return new Commons.Interfaces.Protocol.BrowseChildrenResult(children, truncated);
|
||||
}
|
||||
|
||||
private static Commons.Interfaces.Protocol.BrowseNodeClass MapNodeClass(NodeClass nc) => nc switch
|
||||
{
|
||||
NodeClass.Object => Commons.Interfaces.Protocol.BrowseNodeClass.Object,
|
||||
NodeClass.Variable => Commons.Interfaces.Protocol.BrowseNodeClass.Variable,
|
||||
NodeClass.Method => Commons.Interfaces.Protocol.BrowseNodeClass.Method,
|
||||
_ => Commons.Interfaces.Protocol.BrowseNodeClass.Other
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user