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:
@@ -0,0 +1,82 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Driver capability for invoking OPC UA Methods on the upstream backend (the OPC UA
|
||||
/// <c>Call</c> service). Optional — only drivers whose backends carry method nodes
|
||||
/// implement it. Currently the OPC UA Client driver is the only implementer; tag-based
|
||||
/// drivers (Modbus, S7, FOCAS, Galaxy, AB-CIP, AB-Legacy, TwinCAT) don't expose method
|
||||
/// nodes so they don't need this surface.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Per <c>docs/v2/plan.md</c> decision #4 (composable capability interfaces) — the
|
||||
/// server-side <c>DriverNodeManager</c> discovers method-bearing drivers via an
|
||||
/// <c>is IMethodInvoker</c> check and routes <c>OnCallMethod</c> handlers to
|
||||
/// <see cref="CallMethodAsync"/>. Drivers that don't implement the interface simply
|
||||
/// never have method nodes registered for them.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The address-space mirror is driven by <see cref="IAddressSpaceBuilder.RegisterMethodNode"/>
|
||||
/// — drivers register the method node + its <c>InputArguments</c> /
|
||||
/// <c>OutputArguments</c> properties during discovery, then invocations land back on
|
||||
/// <see cref="CallMethodAsync"/> via the server-side dispatcher.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public interface IMethodInvoker
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoke an upstream OPC UA Method. The driver translates input arguments into the
|
||||
/// wire-level <c>CallMethodRequest</c>, dispatches via the active session, and packs
|
||||
/// the response back into a <see cref="MethodCallResult"/>. Per-argument validation
|
||||
/// errors flow through <see cref="MethodCallResult.InputArgumentResults"/>; method-level
|
||||
/// errors (<c>BadMethodInvalid</c>, <c>BadUserAccessDenied</c>, etc.) flow through
|
||||
/// <see cref="MethodCallResult.StatusCode"/>.
|
||||
/// </summary>
|
||||
/// <param name="objectNodeId">
|
||||
/// Stringified NodeId of the OPC UA Object that owns the method (the <c>ObjectId</c>
|
||||
/// field of <c>CallMethodRequest</c>). Same serialization as <c>IReadable</c>'s
|
||||
/// <c>fullReference</c> — <c>ns=2;s=…</c> / <c>i=…</c> / <c>nsu=…;…</c>.
|
||||
/// </param>
|
||||
/// <param name="methodNodeId">
|
||||
/// Stringified NodeId of the Method node itself (the <c>MethodId</c> field).
|
||||
/// </param>
|
||||
/// <param name="inputs">
|
||||
/// Input arguments in declaration order. The driver wraps each value as a
|
||||
/// <c>Variant</c>; callers pass CLR primitives (plus arrays) — the wire-level
|
||||
/// encoding is the driver's concern.
|
||||
/// </param>
|
||||
/// <param name="cancellationToken">Per-call cancellation.</param>
|
||||
/// <returns>
|
||||
/// Result of the call — see <see cref="MethodCallResult"/>. Never throws for a
|
||||
/// <c>Bad</c> upstream status; the bad code is surfaced via the result so the caller
|
||||
/// can map it onto an OPC UA service-result for downstream clients.
|
||||
/// </returns>
|
||||
Task<MethodCallResult> CallMethodAsync(
|
||||
string objectNodeId,
|
||||
string methodNodeId,
|
||||
object[] inputs,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of a single OPC UA <c>Call</c> service invocation.
|
||||
/// </summary>
|
||||
/// <param name="StatusCode">
|
||||
/// Method-level status. <c>0</c> = Good. Bad codes pass through verbatim from the
|
||||
/// upstream so downstream clients see the canonical OPC UA error (e.g.
|
||||
/// <c>BadMethodInvalid</c>, <c>BadUserAccessDenied</c>, <c>BadArgumentsMissing</c>).
|
||||
/// </param>
|
||||
/// <param name="Outputs">
|
||||
/// Output argument values in declaration order. <c>null</c> when the upstream returned
|
||||
/// no output arguments (or returned a Bad status before producing any).
|
||||
/// </param>
|
||||
/// <param name="InputArgumentResults">
|
||||
/// Per-input-argument status codes. <c>null</c> when the upstream didn't surface
|
||||
/// per-argument validation results (typical for Good calls). Each entry is the OPC UA
|
||||
/// status code for the matching input argument — drivers can use this to surface
|
||||
/// <c>BadTypeMismatch</c>, <c>BadOutOfRange</c>, etc. on a specific argument.
|
||||
/// </param>
|
||||
public sealed record MethodCallResult(
|
||||
uint StatusCode,
|
||||
object[]? Outputs,
|
||||
uint[]? InputArgumentResults);
|
||||
Reference in New Issue
Block a user