Files
Marc Schier 4756e7c245 DI (OPC 10000-100) with samples (Pump, SW Update), fluent API gap closures and model dependency implementation (#3808)
This PR implements the **OPC UA Device Integration (DI, OPC 10000-100)**
companion-spec library trio (`Opc.Ua.Di` / `Opc.Ua.Di.Server` /
`Opc.Ua.Di.Client`) plus a worked example
(`Applications/MinimalPumpServer`) demonstrating the OPC 40223 Pumps
companion specification end-to-end with full simulation. It also closes
11 fluent-API gaps in `Opc.Ua.Server.Fluent`, three source-generator
bugs, integrates the DI layer into the unified `services.AddOpcUa()`
Microsoft.Extensions DI hosting pattern, and adds 250+ new tests.

**Worked example — `Applications/MinimalPumpServer/`**
- AOT-compatible console host using `Host.CreateApplicationBuilder` +
`AddOpcUa()`.
- `PumpNodeManager` inherits from `DiNodeManager`; multi-namespace (DI +
Machinery + Pumps) via runtime NodeSet2 loading and
`ModelLoaderBuilder`.
- Demonstrates declarative second-pump creation via
`.ConfigureDevicesFor<PumpNodeManager>(...)` and software-package
seeding from the same configurator.
- Full simulation: pressure, temperatures, power, flow, efficiency,
level, cavitation/overheat fault injection, alarm setup, NAMUR-style
supervision.

**Documentation**
- `Docs/Opc.Ua.Di/` — README, DeviceBuilder, Hosting, LockService,
SoftwareUpdate, ClientHelpers (6 new developer guides).
- `Docs/Opc.Ua.Di/ComplianceMatrix.md` — every DI declaration mapped to
In-v1 / Generated / Deferred / Excluded.
- `Docs/CompanionSpecLibraries.md` — packaging guide for new
companion-spec libraries.
- `Docs/FluentApiGaps.md` — historical record of G1–G11.
- `Docs/SourceGeneratedNodeManagers.md` updated for richer node
managers.
- `Docs/plans/StateMachineBuilder.md` — deferred state-machine builder
design.
2026-06-04 05:57:57 +02:00

247 lines
10 KiB
C#

/* ========================================================================
* Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/
namespace Opc.Ua.Server.Fluent
{
/// <summary>
/// Per-node fluent surface returned by
/// <see cref="INodeManagerBuilder.Node(string)"/>. All <c>On*</c>
/// methods return the same builder so calls can be chained.
/// </summary>
/// <remarks>
/// <para>
/// Variable-only callbacks (e.g. <c>OnRead</c>) and method-only
/// callbacks (e.g. <c>OnCall</c>) throw
/// <see cref="ServiceResultException"/> with
/// <see cref="StatusCodes.BadInvalidArgument"/> when invoked on a node
/// of an incompatible class. Use <see cref="As{TState}"/> to obtain a
/// strongly-typed view that surfaces the right callbacks at compile
/// time.
/// </para>
/// <para>
/// All callbacks replace any previously assigned handler on the
/// underlying <see cref="NodeState"/>; they do not add to a list.
/// Wiring the same node twice with the same callback category is an
/// error and throws.
/// </para>
/// </remarks>
public interface INodeBuilder : INodeStateBuilder
{
/// <summary>
/// The resolved <see cref="NodeState"/>. Exposed so callers can
/// reach attributes that are not surfaced by the builder (for
/// example, setting <c>UserAccessLevel</c> programmatically).
/// </summary>
new NodeState Node { get; }
/// <summary>
/// The owning builder. Useful when you want to terminate a chain
/// and resolve another node without storing the parent reference.
/// </summary>
INodeManagerBuilder Builder { get; }
/// <summary>
/// Returns a strongly-typed view of the same node. Throws if the
/// resolved node is not assignable to <typeparamref name="TState"/>.
/// </summary>
/// <typeparam name="TState">The expected node state type.</typeparam>
/// <exception cref="ServiceResultException">
/// The resolved node is not assignable to <typeparamref name="TState"/>.
/// </exception>
INodeBuilder<TState> As<TState>() where TState : NodeState;
/// <summary>
/// Wires <see cref="BaseVariableState.OnReadValue"/>.
/// </summary>
INodeBuilder OnRead(NodeValueEventHandler handler);
/// <summary>
/// Wires <see cref="BaseVariableState.OnSimpleReadValue"/>.
/// </summary>
INodeBuilder OnRead(NodeValueSimpleEventHandler handler);
/// <summary>
/// Wires <see cref="BaseVariableState.OnWriteValue"/>.
/// </summary>
INodeBuilder OnWrite(NodeValueEventHandler handler);
/// <summary>
/// Wires <see cref="BaseVariableState.OnSimpleWriteValue"/>.
/// </summary>
INodeBuilder OnWrite(NodeValueSimpleEventHandler handler);
/// <summary>
/// Wires <see cref="BaseVariableState.OnReadValueAsync"/>. Use this
/// overload when the read source performs I/O — the handler runs
/// without holding <c>lock(this)</c>, so the call can <c>await</c>
/// freely.
/// </summary>
INodeBuilder OnRead(NodeValueEventHandlerAsync handler);
/// <summary>
/// Wires <see cref="BaseVariableState.OnSimpleReadValueAsync"/>.
/// The framework still applies index-range / data-encoding /
/// copy-policy post-processing to the returned value.
/// </summary>
INodeBuilder OnRead(NodeValueSimpleEventHandlerAsync handler);
/// <summary>
/// Wires <see cref="BaseVariableState.OnWriteValueAsync"/>.
/// </summary>
INodeBuilder OnWrite(NodeValueWriteEventHandlerAsync handler);
/// <summary>
/// Wires <see cref="BaseVariableState.OnSimpleWriteValueAsync"/>.
/// </summary>
INodeBuilder OnWrite(NodeValueSimpleWriteEventHandlerAsync handler);
/// <summary>
/// Wires <see cref="MethodState.OnCallMethod2"/>.
/// </summary>
INodeBuilder OnCall(GenericMethodCalledEventHandler2 handler);
/// <summary>
/// Wires <see cref="MethodState.OnCallMethod2Async"/>.
/// </summary>
INodeBuilder OnCall(GenericMethodCalledEventHandler2Async handler);
/// <summary>
/// Invoked exactly once after the node has been added to the
/// address space and all sibling builder wiring has been applied.
/// </summary>
INodeBuilder OnNodeAdded(NodeLifecycleHandler handler);
/// <summary>
/// Invoked when the node manager removes the node from the address
/// space (including manager dispose).
/// </summary>
INodeBuilder OnNodeRemoved(NodeLifecycleHandler handler);
/// <summary>
/// Routes <c>CustomNodeManager2.HistoryRead</c> requests targeting
/// the resolved node to <paramref name="handler"/>.
/// </summary>
INodeBuilder OnHistoryRead(HistoryReadHandler handler);
/// <summary>
/// Routes <c>CustomNodeManager2.HistoryUpdate</c> requests targeting
/// the resolved node to <paramref name="handler"/>.
/// </summary>
INodeBuilder OnHistoryUpdate(HistoryUpdateHandler handler);
/// <summary>
/// Routes <c>CustomNodeManager2.ConditionRefresh</c> invocations
/// whose monitored item targets the resolved node to
/// <paramref name="handler"/>.
/// </summary>
INodeBuilder OnConditionRefresh(ConditionRefreshHandler handler);
/// <summary>
/// Invoked once a monitored item has been successfully created for
/// the resolved source node. Useful for attaching push-style
/// sampling sources.
/// </summary>
INodeBuilder OnMonitoredItemCreated(MonitoredItemCreatedHandler handler);
/// <summary>
/// Wires the resolved node's <see cref="NodeState.OnReportEvent"/>
/// hook to forward events to <paramref name="handler"/>.
/// </summary>
INodeBuilder OnEvent(EventNotificationHandler handler);
/// <summary>
/// Opts the resolved node into (or out of) dynamic scaling of event
/// consumer tasks. When enabled, the <see cref="MonitoredNode2"/>
/// for this node scales consumer tasks with the number of event
/// monitored items, improving throughput under high event loads.
/// The Server node (<see cref="ObjectIds.Server"/>) is always
/// auto-opted-in regardless of this call.
/// </summary>
/// <param name="enable">
/// <c>true</c> to opt in (default); <c>false</c> to opt out.
/// </param>
INodeBuilder AllowMultipleEventConsumers(bool enable = true);
/// <summary>
/// Resolves a child of the current node by browse name. Used by
/// source-generated typed traversal wrappers to walk one segment
/// at a time without re-resolving from the root.
/// </summary>
/// <param name="browseName">Browse name of the immediate child.</param>
/// <exception cref="ServiceResultException">
/// Thrown when the child cannot be found.
/// </exception>
new INodeBuilder Child(QualifiedName browseName);
/// <summary>
/// Strongly-typed sibling of <see cref="Child(QualifiedName)"/>.
/// </summary>
/// <typeparam name="TState">
/// CLR <see cref="NodeState"/> type the resolved child must be
/// assignable to.
/// </typeparam>
new INodeBuilder<TState> Child<TState>(QualifiedName browseName)
where TState : NodeState;
/// <summary>
/// Resolves a variable child of the current node and returns a
/// typed <see cref="IVariableBuilder{TValue}"/> view.
/// </summary>
/// <typeparam name="TValue">
/// CLR type carried by the child variable's <c>Value</c> attribute.
/// </typeparam>
/// <param name="browseName">Browse name of the immediate child.</param>
/// <exception cref="ServiceResultException">
/// Thrown when the child cannot be found, or when it is not a
/// <see cref="BaseVariableState"/>.
/// </exception>
IVariableBuilder<TValue> Variable<TValue>(QualifiedName browseName);
}
/// <summary>
/// Strongly-typed view of <see cref="INodeBuilder"/> that narrows the
/// <see cref="Node"/> property to <typeparamref name="TState"/>. All
/// inherited <c>On*</c> methods continue to return the non-generic
/// builder; use <see cref="INodeBuilder.As{TState}"/> chaining when
/// you need to restore the typed view.
/// </summary>
/// <typeparam name="TState">
/// The concrete <see cref="NodeState"/> derivative the resolved node
/// is narrowed to.
/// </typeparam>
public interface INodeBuilder<out TState> : INodeBuilder, INodeStateBuilder<TState>
where TState : NodeState
{
/// <summary>
/// The resolved node, narrowed to <typeparamref name="TState"/>.
/// </summary>
new TState Node { get; }
}
}