Merge branch 'feat/mqtt-sparkplug-driver'
# Conflicts: # src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/DriverTypeNames.cs # src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Drivers/DriverConfigModal.razor # src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/EndpointRouteBuilderExtensions.cs # src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/TagEditors/TagConfigEditorMap.cs # src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/TagEditors/TagConfigValidator.cs # src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/ZB.MOM.WW.OtOpcUa.AdminUI.csproj # src/Server/ZB.MOM.WW.OtOpcUa.Host/Drivers/DriverFactoryBootstrap.cs
This commit is contained in:
@@ -29,9 +29,31 @@ public enum BrowseNodeKind
|
||||
/// <c>AlarmExtension</c> primitive). The picker pre-fills a default native-alarm <c>alarm</c> object
|
||||
/// into the TagConfig when an alarm attribute is selected. Defaults to false so non-alarm-aware
|
||||
/// drivers (e.g. the OPC UA client browser) aren't forced to flow a flag they don't produce.</param>
|
||||
/// <param name="AddressFields">
|
||||
/// The <b>structured</b> address this leaf binds by, as <c>TagConfig</c> key → value in the
|
||||
/// driver's own key vocabulary, for a driver whose address is a <i>tuple</i> rather than a single
|
||||
/// reference string. Null (the default) for every driver whose address IS the
|
||||
/// <see cref="BrowseNode.NodeId"/> — nothing changes for them.
|
||||
/// <para>
|
||||
/// It exists because a tuple cannot be recovered from an id string in general. MQTT/Sparkplug
|
||||
/// is the case in point: a browse node id is
|
||||
/// <c>{group}/{node}[/{device}]::{metric}</c>, and a metric name may itself contain <c>/</c>
|
||||
/// (<c>Node Control/Rebirth</c>) — so the session keeps the decomposition it already has and
|
||||
/// <b>states</b> it here, rather than the AdminUI's browse-commit mapper re-deriving it by
|
||||
/// splitting the id and hoping. Same discipline as the v3 address space carrying
|
||||
/// <c>AddressSpaceRealm</c> explicitly instead of parsing it out of a NodeId.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The producer states the binding <em>shape</em> too, by which keys it emits — so a consumer
|
||||
/// never has to infer a driver's sub-mode from the tree's geometry. Consumers must read the
|
||||
/// keys they know and ignore the rest; this is a description, not a config blob to splat into
|
||||
/// a persisted TagConfig.
|
||||
/// </para>
|
||||
/// </param>
|
||||
public sealed record AttributeInfo(
|
||||
string Name,
|
||||
string DriverDataType,
|
||||
bool IsArray,
|
||||
string SecurityClass,
|
||||
bool IsAlarm = false);
|
||||
bool IsAlarm = false,
|
||||
IReadOnlyDictionary<string, string>? AddressFields = null);
|
||||
|
||||
@@ -35,3 +35,66 @@ public interface IBrowseSession : IAsyncDisposable
|
||||
/// <returns>The attributes of the node.</returns>
|
||||
Task<IReadOnlyList<AttributeInfo>> AttributesAsync(string nodeId, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="IBrowseSession"/> whose protocol offers an explicit, operator-triggered
|
||||
/// <b>re-announce</b> action: a request that the remote peer republish its self-description so the
|
||||
/// observation window can fill without waiting for the next natural announcement. Implemented
|
||||
/// today only by the MQTT/Sparkplug browser, whose tree is built from observed NBIRTH/DBIRTH
|
||||
/// certificates.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>This is the only interface in the browse contract that causes an outbound message.</b>
|
||||
/// Every other browse member is strictly read-only, and for the MQTT browser that read-only
|
||||
/// property is load-bearing: a picker opened by an operator runs against a live production
|
||||
/// broker. It is a separate interface, rather than an optional member on
|
||||
/// <see cref="IBrowseSession"/>, precisely so "does this session write?" stays a type
|
||||
/// question a caller cannot forget to ask.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Callers must authorize before invoking it.</b> The AdminUI routes it through
|
||||
/// <c>BrowserSessionService.RequestRebirthAsync</c>, which enforces the same
|
||||
/// <c>DriverOperator</c> policy that gates the picker's Browse affordance — an implementation
|
||||
/// cannot check that itself, since it holds no user identity.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public interface IRebirthCapableBrowseSession : IBrowseSession
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether this <i>particular</i> session can actually re-announce — the runtime half of the
|
||||
/// type question, and the one a UI must ask before offering the affordance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// One session class may serve several protocol shapes: the MQTT browser opens the same session
|
||||
/// type for Plain and for Sparkplug B, and a Plain MQTT window publishes <b>nothing, ever</b> —
|
||||
/// there is no plain-MQTT re-announce to offer. Implementing the interface therefore means "this
|
||||
/// session type may re-announce"; this property means "this instance will". A UI gating on the
|
||||
/// type alone would draw a button that can only ever throw.
|
||||
/// <para>
|
||||
/// This is <b>not</b> an authorization signal — see the interface remarks. False here hides
|
||||
/// the affordance; the caller still authorizes before invoking
|
||||
/// <see cref="RequestRebirthAsync"/>, and the implementation still refuses a call it cannot
|
||||
/// serve.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
bool RebirthAvailable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Asks the addressed remote peer(s) to re-announce themselves.
|
||||
/// </summary>
|
||||
/// <param name="scope">
|
||||
/// The protocol-specific target. For Sparkplug: a browse <c>NodeId</c> from this session's own
|
||||
/// tree (group, edge node, device or metric — resolved up to the owning edge node), or a bare
|
||||
/// <c>{group}/{edgeNode}</c> pair for a node that has not been observed yet.
|
||||
/// </param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>The number of request messages published.</returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="scope"/> is empty or unusable.</exception>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// The scope resolves to no target, or to more targets than the implementation will fan out to
|
||||
/// in one action. Nothing is published in either case.
|
||||
/// </exception>
|
||||
/// <exception cref="NotSupportedException">This session's mode has no re-announce action.</exception>
|
||||
Task<int> RequestRebirthAsync(string scope, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@ public static class DriverTypeNames
|
||||
|
||||
/// <summary>Read-only SQL Server table/view poller — publishes columns as OPC UA variables.</summary>
|
||||
public const string Sql = "Sql";
|
||||
/// <summary>MQTT / Sparkplug B broker-subscription driver.</summary>
|
||||
public const string Mqtt = "Mqtt";
|
||||
|
||||
/// <summary>
|
||||
/// Every driver-type string declared above, for callers that need to enumerate
|
||||
@@ -72,5 +74,6 @@ public static class DriverTypeNames
|
||||
Galaxy,
|
||||
Calculation,
|
||||
Sql,
|
||||
Mqtt,
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user