Auto: opcuaclient-9 — method node mirroring + Call passthrough
Adds the 9th capability interface (IMethodInvoker) so the OPC UA Client driver can mirror upstream OPC UA Method nodes into the local address space and forward Call invocations as Session.CallAsync. Method-bearing servers (e.g. ProgramStateMachine, Acknowledge / Confirm methods, custom control surfaces) now show up downstream instead of being silently filtered out. - Core.Abstractions: IMethodInvoker + MethodCallResult; default no-op IAddressSpaceBuilder.RegisterMethodNode + MirroredMethodNodeInfo + MethodArgumentInfo. Default impls keep tag-based drivers and existing builders compiling without forced overrides. - OpcUaClientDriver: BrowseRecursiveAsync now lifts the Method node-class filter; for each method it walks HasProperty to pick up InputArguments + OutputArguments and decodes the Argument extension objects into MethodArgumentInfo. Status-codes pass through verbatim (cascading quality), local NodeId-parse + lost-session faults surface as BadNodeIdInvalid / BadCommunicationError. - 7 new unit tests cover the capability surface, the DTO shapes, and the back-compat default no-op for RegisterMethodNode. Suite green at 160/160. Server-side OnCallMethod dispatch (wiring local MethodNode handlers to IMethodInvoker.CallMethodAsync inside DriverNodeManager) is deferred to a follow-up — the driver-side surface + browse mirror ship cleanly here. Closes #281 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -60,8 +60,90 @@ public interface IAddressSpaceBuilder
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
void RegisterTypeNode(MirroredTypeNodeInfo info) { /* default: no-op */ }
|
||||
|
||||
/// <summary>
|
||||
/// Register a method node mirrored from an upstream OPC UA server. The method is
|
||||
/// registered as a child of the current builder scope (i.e. the folder representing
|
||||
/// the upstream Object that owns the method). Optional surface — drivers that don't
|
||||
/// mirror methods simply never call it; address-space builders that don't materialise
|
||||
/// method nodes can leave the default no-op in place. Default implementation drops
|
||||
/// the call so adding this method doesn't break existing
|
||||
/// <see cref="IAddressSpaceBuilder"/> implementations.
|
||||
/// </summary>
|
||||
/// <param name="info">Metadata describing the method node, including input/output argument schemas.</param>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The OPC UA Client driver is the primary caller — it picks up
|
||||
/// <c>NodeClass.Method</c> nodes during the <c>HierarchicalReferences</c> browse
|
||||
/// pass, then walks each method's <c>HasProperty</c> references to harvest the
|
||||
/// <c>InputArguments</c> / <c>OutputArguments</c> property values.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The OPC UA server-side <c>DriverNodeManager</c> overrides this to materialize
|
||||
/// a real <c>MethodNode</c> in the local address space and wire its
|
||||
/// <c>OnCallMethod</c> handler to the driver's
|
||||
/// <see cref="IMethodInvoker.CallMethodAsync"/>. Other builders (Galaxy, Modbus,
|
||||
/// FOCAS, S7, TwinCAT, AB-CIP, AB-Legacy) ignore the projection because their
|
||||
/// backends don't expose method nodes.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
void RegisterMethodNode(MirroredMethodNodeInfo info) { /* default: no-op */ }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metadata describing a single method node mirrored from an upstream OPC UA server.
|
||||
/// Built by the OPC UA Client driver during the discovery browse pass and consumed by
|
||||
/// <see cref="IAddressSpaceBuilder.RegisterMethodNode"/>.
|
||||
/// </summary>
|
||||
/// <param name="BrowseName">OPC UA BrowseName segment from the upstream BrowseName.</param>
|
||||
/// <param name="DisplayName">Human-readable display name; falls back to <paramref name="BrowseName"/>.</param>
|
||||
/// <param name="ObjectNodeId">
|
||||
/// Stringified NodeId of the parent Object that owns this method — the <c>ObjectId</c>
|
||||
/// argument the dispatcher passes back to <see cref="IMethodInvoker.CallMethodAsync"/>.
|
||||
/// </param>
|
||||
/// <param name="MethodNodeId">
|
||||
/// Stringified NodeId of the method node itself — the <c>MethodId</c> argument.
|
||||
/// </param>
|
||||
/// <param name="InputArguments">
|
||||
/// Declaration of the method's input arguments, in order. <c>null</c> or empty when the
|
||||
/// method takes no inputs (or the upstream property couldn't be read).
|
||||
/// </param>
|
||||
/// <param name="OutputArguments">
|
||||
/// Declaration of the method's output arguments, in order. <c>null</c> or empty when the
|
||||
/// method returns no outputs (or the upstream property couldn't be read).
|
||||
/// </param>
|
||||
public sealed record MirroredMethodNodeInfo(
|
||||
string BrowseName,
|
||||
string DisplayName,
|
||||
string ObjectNodeId,
|
||||
string MethodNodeId,
|
||||
IReadOnlyList<MethodArgumentInfo>? InputArguments,
|
||||
IReadOnlyList<MethodArgumentInfo>? OutputArguments);
|
||||
|
||||
/// <summary>
|
||||
/// One row of an OPC UA Argument array — name + data type + array hint. Mirrors the
|
||||
/// <c>Opc.Ua.Argument</c> structure but without the SDK-only types so this DTO can live
|
||||
/// in <c>Core.Abstractions</c>.
|
||||
/// </summary>
|
||||
/// <param name="Name">Argument name from the upstream Argument structure.</param>
|
||||
/// <param name="DriverDataType">
|
||||
/// Mapped local <see cref="DriverDataType"/>. Unknown / structured upstream types fall
|
||||
/// through to <see cref="DriverDataType.String"/> — same convention as variable mirroring.
|
||||
/// </param>
|
||||
/// <param name="ValueRank">
|
||||
/// OPC UA ValueRank: <c>-1</c> = scalar, <c>0</c> = OneOrMoreDimensions, <c>1+</c> = array
|
||||
/// dimensions. Driven directly from the upstream Argument's ValueRank.
|
||||
/// </param>
|
||||
/// <param name="Description">
|
||||
/// Human-readable description from the upstream Argument structure; <c>null</c> when the
|
||||
/// upstream doesn't carry one.
|
||||
/// </param>
|
||||
public sealed record MethodArgumentInfo(
|
||||
string Name,
|
||||
DriverDataType DriverDataType,
|
||||
int ValueRank,
|
||||
string? Description);
|
||||
|
||||
/// <summary>
|
||||
/// Categorises a mirrored type-definition node so the receiving builder can route it into
|
||||
/// the right OPC UA standard subtree (<c>ObjectTypesFolder</c>, <c>VariableTypesFolder</c>,
|
||||
|
||||
Reference in New Issue
Block a user