Adds an opt-in pass-3 walk of the upstream TypesFolder (i=86) so the OPC UA Client driver can mirror upstream type definitions into the local address space. Honours the curation rules from PR-7 (#359). Structural mirror only — binary-encoding priming via LoadDataTypeSystem is tracked as a follow-up because that helper was removed from the public ISession surface in OPCFoundation.NetStandard 1.5.378+. - IAddressSpaceBuilder.RegisterTypeNode (default no-op for back-compat) - MirroredTypeNodeInfo + MirroredTypeKind in Core.Abstractions - OpcUaClientDriverOptions.MirrorTypeDefinitions (default false) - DiscoverAsync pass-3: FetchTypeTreeAsync + recursive HasSubtype walk per branch (ObjectTypes/VariableTypes/DataTypes/ReferenceTypes), best-effort IsAbstract read, IncludePaths/ExcludePaths still applied - 6 new unit tests; all 153 OpcUaClient unit tests pass Closes #280
150 lines
7.7 KiB
C#
150 lines
7.7 KiB
C#
namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
|
|
/// <summary>
|
|
/// Streaming builder API a driver uses to register OPC UA nodes during discovery.
|
|
/// Core owns the tree; driver streams <c>AddFolder</c> / <c>AddVariable</c> calls
|
|
/// as it discovers nodes — no buffering of the whole tree.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Per <c>docs/v2/plan.md</c> decision #52 — drivers register nodes via this builder
|
|
/// rather than returning a tree object. Supports incremental / large address spaces
|
|
/// without forcing the driver to buffer the whole tree.
|
|
/// </remarks>
|
|
public interface IAddressSpaceBuilder
|
|
{
|
|
/// <summary>
|
|
/// Add a folder node. Returns a child builder scoped to inside this folder, so subsequent
|
|
/// calls on the child place nodes under it.
|
|
/// </summary>
|
|
/// <param name="browseName">OPC UA browse name (the segment of the path under the parent).</param>
|
|
/// <param name="displayName">Human-readable display name. May equal <paramref name="browseName"/>.</param>
|
|
IAddressSpaceBuilder Folder(string browseName, string displayName);
|
|
|
|
/// <summary>
|
|
/// Add a variable node corresponding to a tag. Driver-side full reference + data-type
|
|
/// metadata come from the <see cref="DriverAttributeInfo"/> DTO.
|
|
/// </summary>
|
|
/// <param name="browseName">OPC UA browse name (the segment of the path under the parent folder).</param>
|
|
/// <param name="displayName">Human-readable display name. May equal <paramref name="browseName"/>.</param>
|
|
/// <param name="attributeInfo">Driver-side metadata for the variable.</param>
|
|
IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo attributeInfo);
|
|
|
|
/// <summary>
|
|
/// Add a property to the current node (folder or variable). Properties are static metadata
|
|
/// read once at build time (e.g. OPC 40010 Identification fields per the schemas-repo
|
|
/// <c>_base</c> equipment-class template).
|
|
/// </summary>
|
|
void AddProperty(string browseName, DriverDataType dataType, object? value);
|
|
|
|
/// <summary>
|
|
/// Register a type-definition node (ObjectType / VariableType / DataType / ReferenceType)
|
|
/// mirrored from an upstream OPC UA server. Optional surface — drivers that don't mirror
|
|
/// types simply never call it; address-space builders that don't materialise upstream
|
|
/// types 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 type-definition node to mirror.</param>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// The OPC UA Client driver is the primary caller — it walks <c>i=86</c>
|
|
/// (TypesFolder) during <c>DiscoverAsync</c> when
|
|
/// <c>OpcUaClientDriverOptions.MirrorTypeDefinitions</c> is set so downstream clients
|
|
/// see the upstream type system instead of rendering structured-type values as opaque
|
|
/// strings.
|
|
/// </para>
|
|
/// <para>
|
|
/// The default no-op is intentional — most builders (Galaxy, Modbus, FOCAS, S7,
|
|
/// TwinCAT, AB-CIP) don't have a meaningful type folder to project into and would
|
|
/// otherwise need empty-stub overrides.
|
|
/// </para>
|
|
/// </remarks>
|
|
void RegisterTypeNode(MirroredTypeNodeInfo info) { /* default: no-op */ }
|
|
}
|
|
|
|
/// <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>,
|
|
/// <c>DataTypesFolder</c>, <c>ReferenceTypesFolder</c>) when projecting upstream types into
|
|
/// the local address space.
|
|
/// </summary>
|
|
public enum MirroredTypeKind
|
|
{
|
|
ObjectType,
|
|
VariableType,
|
|
DataType,
|
|
ReferenceType,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Metadata describing a single type-definition node mirrored from an upstream OPC UA
|
|
/// server. Built by the OPC UA Client driver during type-mirror pass and consumed by
|
|
/// <see cref="IAddressSpaceBuilder.RegisterTypeNode"/>.
|
|
/// </summary>
|
|
/// <param name="Kind">Type category — drives which standard sub-folder the node lives under.</param>
|
|
/// <param name="UpstreamNodeId">
|
|
/// Stringified upstream NodeId (e.g. <c>"ns=2;i=1234"</c>) — preserves the original identity
|
|
/// so a builder that wants to project the type with a stable cross-namespace reference can do
|
|
/// so. The driver applies any configured namespace remap before stamping this field.
|
|
/// </param>
|
|
/// <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="SuperTypeNodeId">
|
|
/// Stringified upstream NodeId of the super-type (parent type), or <c>null</c> when the node
|
|
/// sits directly under the root (e.g. <c>BaseObjectType</c>, <c>BaseVariableType</c>). Lets
|
|
/// the builder reconstruct the inheritance chain.
|
|
/// </param>
|
|
/// <param name="IsAbstract">
|
|
/// <c>true</c> when the upstream node has the <c>IsAbstract</c> flag set (Object / Variable /
|
|
/// ReferenceType). DataTypes also expose this — the driver passes it through verbatim.
|
|
/// </param>
|
|
public sealed record MirroredTypeNodeInfo(
|
|
MirroredTypeKind Kind,
|
|
string UpstreamNodeId,
|
|
string BrowseName,
|
|
string DisplayName,
|
|
string? SuperTypeNodeId,
|
|
bool IsAbstract);
|
|
|
|
/// <summary>Opaque handle for a registered variable. Used by Core for subscription routing.</summary>
|
|
public interface IVariableHandle
|
|
{
|
|
/// <summary>Driver-side full reference for read/write addressing.</summary>
|
|
string FullReference { get; }
|
|
|
|
/// <summary>
|
|
/// Annotate this variable with an OPC UA <c>AlarmConditionState</c>. Drivers with
|
|
/// <see cref="DriverAttributeInfo.IsAlarm"/> = true call this during discovery so the
|
|
/// concrete address-space builder can materialize a sibling condition node. The returned
|
|
/// sink receives lifecycle transitions raised through <see cref="IAlarmSource.OnAlarmEvent"/>
|
|
/// — the generic node manager wires the subscription; the concrete builder decides how
|
|
/// to surface the state (e.g. OPC UA <c>AlarmConditionState.Activate</c>,
|
|
/// <c>Acknowledge</c>, <c>Deactivate</c>).
|
|
/// </summary>
|
|
IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Metadata used to materialize an OPC UA <c>AlarmConditionState</c> sibling for a variable.
|
|
/// Populated by the driver's discovery step; concrete builders decide how to surface it.
|
|
/// </summary>
|
|
/// <param name="SourceName">Human-readable alarm name used for the <c>SourceName</c> event field.</param>
|
|
/// <param name="InitialSeverity">Severity at address-space build time; updates arrive via <see cref="IAlarmConditionSink"/>.</param>
|
|
/// <param name="InitialDescription">Initial description; updates arrive via <see cref="IAlarmConditionSink"/>.</param>
|
|
public sealed record AlarmConditionInfo(
|
|
string SourceName,
|
|
AlarmSeverity InitialSeverity,
|
|
string? InitialDescription);
|
|
|
|
/// <summary>
|
|
/// Sink a concrete address-space builder returns from <see cref="IVariableHandle.MarkAsAlarmCondition"/>.
|
|
/// The generic node manager routes per-alarm <see cref="IAlarmSource.OnAlarmEvent"/> payloads here —
|
|
/// the sink translates the transition into an OPC UA condition state change or whatever the
|
|
/// concrete builder's backing address space supports.
|
|
/// </summary>
|
|
public interface IAlarmConditionSink
|
|
{
|
|
/// <summary>Push an alarm transition (Active / Acknowledged / Inactive) for this condition.</summary>
|
|
void OnTransition(AlarmEventArgs args);
|
|
}
|