docs: backfill XML documentation across 756 files
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public members surfaced by commentchecker — resolves 5,847 of 5,869 issues (99.6%) across three /fixdocs passes.
This commit is contained in:
@@ -61,8 +61,17 @@ internal sealed class EventPump : IAsyncDisposable
|
||||
private Task? _dispatchLoop;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>Occurs when a data change event is received from the Galaxy subscriber.</summary>
|
||||
public event EventHandler<DataChangeEventArgs>? OnDataChange;
|
||||
|
||||
/// <summary>Initializes a new instance of the EventPump class.</summary>
|
||||
/// <param name="subscriber">The Galaxy subscriber to consume events from.</param>
|
||||
/// <param name="registry">The subscription registry for resolving subscribers.</param>
|
||||
/// <param name="logger">The logger instance; if null, uses NullLogger.</param>
|
||||
/// <param name="handleFactory">The factory for creating subscription handles; if null, uses GalaxySubscriptionHandle.</param>
|
||||
/// <param name="channelCapacity">The bounded channel capacity for buffering events.</param>
|
||||
/// <param name="clientName">The client name for metric tagging; if null, uses "<unknown>".</param>
|
||||
/// <param name="onStreamFault">Optional callback invoked when the stream faults.</param>
|
||||
public EventPump(
|
||||
IGalaxySubscriber subscriber,
|
||||
SubscriptionRegistry registry,
|
||||
@@ -234,6 +243,8 @@ internal sealed class EventPump : IAsyncDisposable
|
||||
ServerTimestampUtc: DateTime.UtcNow);
|
||||
}
|
||||
|
||||
/// <summary>Disposes the event pump and cancels all running tasks.</summary>
|
||||
/// <returns>A value task representing the asynchronous disposal operation.</returns>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
|
||||
/// </summary>
|
||||
internal sealed class GalaxyAlarmSubscriptionHandle : IAlarmSubscriptionHandle
|
||||
{
|
||||
/// <summary>Initializes a new instance of the GalaxyAlarmSubscriptionHandle class.</summary>
|
||||
/// <param name="diagnosticId">The diagnostic ID for the subscription.</param>
|
||||
public GalaxyAlarmSubscriptionHandle(string diagnosticId)
|
||||
{
|
||||
DiagnosticId = diagnosticId;
|
||||
|
||||
@@ -30,12 +30,16 @@ public sealed class GalaxyMxSession : IAsyncDisposable
|
||||
private int _serverHandle;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>Initializes a new instance of the GalaxyMxSession class.</summary>
|
||||
/// <param name="options">MX Access configuration options.</param>
|
||||
/// <param name="logger">Optional logger instance; uses NullLogger if not provided.</param>
|
||||
public GalaxyMxSession(GalaxyMxAccessOptions options, ILogger? logger = null)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_logger = logger ?? NullLogger.Instance;
|
||||
}
|
||||
|
||||
/// <summary>Gets a value indicating whether the session is connected.</summary>
|
||||
public bool IsConnected => _session is not null;
|
||||
|
||||
/// <summary>
|
||||
@@ -49,6 +53,8 @@ public sealed class GalaxyMxSession : IAsyncDisposable
|
||||
/// configured client name. Idempotent — second calls are no-ops while
|
||||
/// <see cref="IsConnected"/> is true.
|
||||
/// </summary>
|
||||
/// <param name="clientOptions">The MX gateway client options.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public async Task ConnectAsync(MxGatewayClientOptions clientOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
@@ -67,6 +73,8 @@ public sealed class GalaxyMxSession : IAsyncDisposable
|
||||
/// fake). Skips the gateway-client construction so tests can drive the session
|
||||
/// surface without spinning a real gRPC channel. Caller retains client ownership.
|
||||
/// </summary>
|
||||
/// <param name="session">The MX gateway session to attach.</param>
|
||||
/// <param name="serverHandle">The server handle value to use.</param>
|
||||
internal void AttachForTests(MxGatewaySession session, int serverHandle)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
@@ -80,6 +88,7 @@ public sealed class GalaxyMxSession : IAsyncDisposable
|
||||
/// </summary>
|
||||
public MxGatewaySession? Session => _session;
|
||||
|
||||
/// <summary>Disposes the session and underlying gateway client resources.</summary>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
@@ -8,5 +8,6 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
|
||||
/// </summary>
|
||||
internal sealed record GalaxySubscriptionHandle(long SubscriptionId) : ISubscriptionHandle
|
||||
{
|
||||
/// <summary>Gets the diagnostic identifier for the subscription.</summary>
|
||||
public string DiagnosticId => $"galaxy-sub-{SubscriptionId}";
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ internal static class GalaxyTelemetry
|
||||
/// Tag a span with a failure reason and set its status to <c>Error</c>. Helper
|
||||
/// so the decorators don't repeat the four-line idiom on every catch block.
|
||||
/// </summary>
|
||||
/// <param name="activity">The activity to tag with error information.</param>
|
||||
/// <param name="ex">The exception to record.</param>
|
||||
public static void RecordError(this Activity? activity, Exception ex)
|
||||
{
|
||||
if (activity is null) return;
|
||||
|
||||
@@ -23,12 +23,20 @@ internal sealed class GatewayGalaxyAlarmAcknowledger : IGalaxyAlarmAcknowledger
|
||||
private readonly MxGatewayClient _client;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="GatewayGalaxyAlarmAcknowledger"/> class.</summary>
|
||||
/// <param name="client">The MX gateway client used to send acknowledgments.</param>
|
||||
/// <param name="logger">A logger for diagnostic output.</param>
|
||||
public GatewayGalaxyAlarmAcknowledger(MxGatewayClient client, ILogger logger)
|
||||
{
|
||||
_client = client ?? throw new ArgumentNullException(nameof(client));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>Acknowledges an alarm via the gateway.</summary>
|
||||
/// <param name="alarmFullReference">The full reference path of the alarm to acknowledge.</param>
|
||||
/// <param name="comment">An operator-supplied comment attached to the acknowledgement.</param>
|
||||
/// <param name="operatorUser">The name of the operator performing the acknowledgement.</param>
|
||||
/// <param name="cancellationToken">A token to cancel the asynchronous operation.</param>
|
||||
public async Task AcknowledgeAsync(
|
||||
string alarmFullReference,
|
||||
string comment,
|
||||
|
||||
@@ -37,6 +37,8 @@ internal sealed class GatewayGalaxyAlarmFeed : IGalaxyAlarmFeed
|
||||
/// Opens a <c>StreamAlarms</c> feed. Matches the method group
|
||||
/// <c>MxGatewayClient.StreamAlarmsAsync</c>.
|
||||
/// </summary>
|
||||
/// <param name="request">The stream request parameters.</param>
|
||||
/// <param name="cancellationToken">A cancellation token.</param>
|
||||
internal delegate IAsyncEnumerable<AlarmFeedMessage> AlarmStreamFactory(
|
||||
StreamAlarmsRequest request, CancellationToken cancellationToken);
|
||||
|
||||
@@ -65,8 +67,15 @@ internal sealed class GatewayGalaxyAlarmFeed : IGalaxyAlarmFeed
|
||||
private Task? _loop;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>Occurs when an alarm transition (raise, acknowledge, clear) is received from the Galaxy feed.</summary>
|
||||
public event EventHandler<GalaxyAlarmTransition>? OnAlarmTransition;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="GatewayGalaxyAlarmFeed"/> class.</summary>
|
||||
/// <param name="streamFactory">A factory delegate that opens the alarm stream.</param>
|
||||
/// <param name="logger">An optional logger for diagnostic output.</param>
|
||||
/// <param name="clientName">An optional client name for tagging log entries.</param>
|
||||
/// <param name="alarmFilterPrefix">An optional prefix to filter alarms in the stream.</param>
|
||||
/// <param name="reconnectDelay">An optional delay before reconnecting after a stream fault.</param>
|
||||
public GatewayGalaxyAlarmFeed(
|
||||
AlarmStreamFactory streamFactory,
|
||||
ILogger? logger = null,
|
||||
@@ -81,6 +90,7 @@ internal sealed class GatewayGalaxyAlarmFeed : IGalaxyAlarmFeed
|
||||
_clientTag = new KeyValuePair<string, object?>("galaxy.client", clientName ?? "<unknown>");
|
||||
}
|
||||
|
||||
/// <summary>Starts the alarm feed by opening the stream and processing messages in a background task.</summary>
|
||||
public void Start()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
@@ -250,6 +260,7 @@ internal sealed class GatewayGalaxyAlarmFeed : IGalaxyAlarmFeed
|
||||
_ => GalaxyAlarmTransitionKind.Unspecified,
|
||||
};
|
||||
|
||||
/// <summary>Releases the alarm feed resources and stops the background stream task.</summary>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
@@ -28,6 +28,10 @@ public sealed class GatewayGalaxyDataWriter : IGalaxyDataWriter
|
||||
private readonly ConcurrentDictionary<string, int> _itemHandles =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>Initializes a new Galaxy data writer.</summary>
|
||||
/// <param name="session">The MXAccess gateway session.</param>
|
||||
/// <param name="writeUserId">The user ID for write operations.</param>
|
||||
/// <param name="logger">Optional logger for tracing.</param>
|
||||
public GatewayGalaxyDataWriter(GalaxyMxSession session, int writeUserId, ILogger? logger = null)
|
||||
{
|
||||
_session = session ?? throw new ArgumentNullException(nameof(session));
|
||||
@@ -35,6 +39,11 @@ public sealed class GatewayGalaxyDataWriter : IGalaxyDataWriter
|
||||
_logger = logger ?? NullLogger.Instance;
|
||||
}
|
||||
|
||||
/// <summary>Writes values to Galaxy tags through the gateway.</summary>
|
||||
/// <param name="writes">The write requests.</param>
|
||||
/// <param name="securityResolver">Function to resolve security classification per tag.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The write results per request.</returns>
|
||||
public async Task<IReadOnlyList<WriteResult>> WriteAsync(
|
||||
IReadOnlyList<WriteRequest> writes,
|
||||
Func<string, SecurityClassification> securityResolver,
|
||||
|
||||
@@ -21,11 +21,18 @@ public sealed class GatewayGalaxySubscriber : IGalaxySubscriber
|
||||
private readonly Lock _intervalLock = new();
|
||||
private int _lastAppliedIntervalMs = -1; // -1 = never applied; 0 = explicit "use gw default"
|
||||
|
||||
/// <summary>Initializes a new instance of GatewayGalaxySubscriber.</summary>
|
||||
/// <param name="session">The Galaxy MX session to use for subscription operations.</param>
|
||||
public GatewayGalaxySubscriber(GalaxyMxSession session)
|
||||
{
|
||||
_session = session ?? throw new ArgumentNullException(nameof(session));
|
||||
}
|
||||
|
||||
/// <summary>Subscribes to a bulk list of Galaxy references with optional buffered update interval.</summary>
|
||||
/// <param name="fullReferences">The full Galaxy tag references to subscribe to.</param>
|
||||
/// <param name="bufferedUpdateIntervalMs">The buffered update interval in milliseconds.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task that returns a list of subscribe results.</returns>
|
||||
public async Task<IReadOnlyList<SubscribeResult>> SubscribeBulkAsync(
|
||||
IReadOnlyList<string> fullReferences, int bufferedUpdateIntervalMs, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -93,6 +100,10 @@ public sealed class GatewayGalaxySubscriber : IGalaxySubscriber
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Unsubscribes from a bulk list of item handles.</summary>
|
||||
/// <param name="itemHandles">The item handles to unsubscribe from.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task representing the unsubscribe operation.</returns>
|
||||
public async Task UnsubscribeBulkAsync(IReadOnlyList<int> itemHandles, CancellationToken cancellationToken)
|
||||
{
|
||||
if (itemHandles.Count == 0) return;
|
||||
@@ -106,6 +117,9 @@ public sealed class GatewayGalaxySubscriber : IGalaxySubscriber
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>Streams Galaxy MX events asynchronously.</summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>An async enumerable of MX events.</returns>
|
||||
public IAsyncEnumerable<MxEvent> StreamEventsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var session = _session.Session
|
||||
|
||||
@@ -22,6 +22,9 @@ public interface IGalaxyDataReader
|
||||
/// Implementations MUST return the same length as the input — partial-tag
|
||||
/// failures are encoded as Bad-quality snapshots, not omitted.
|
||||
/// </summary>
|
||||
/// <param name="fullReferences">The list of fully-qualified tag references to read.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the read operation.</param>
|
||||
/// <returns>A list of data value snapshots, one per input reference in the same order.</returns>
|
||||
Task<IReadOnlyList<DataValueSnapshot>> ReadAsync(
|
||||
IReadOnlyList<string> fullReferences, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -17,10 +17,15 @@ public interface IGalaxySubscriber
|
||||
/// negative — the caller treats those as per-tag failures rather than a whole-call
|
||||
/// failure.
|
||||
/// </summary>
|
||||
/// <param name="fullReferences">The list of tag references to subscribe to.</param>
|
||||
/// <param name="bufferedUpdateIntervalMs">The buffered update interval in milliseconds.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
Task<IReadOnlyList<SubscribeResult>> SubscribeBulkAsync(
|
||||
IReadOnlyList<string> fullReferences, int bufferedUpdateIntervalMs, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>Unsubscribe a batch of item handles obtained from <see cref="SubscribeBulkAsync"/>.</summary>
|
||||
/// <param name="itemHandles">The item handles to unsubscribe.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
Task UnsubscribeBulkAsync(IReadOnlyList<int> itemHandles, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
@@ -28,5 +33,6 @@ public interface IGalaxySubscriber
|
||||
/// <see cref="MxEvent"/> carries the gw item handle the caller correlates against
|
||||
/// its <see cref="SubscriptionRegistry"/>.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation token for the stream.</param>
|
||||
IAsyncEnumerable<MxEvent> StreamEventsAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ internal static class MxAccessSeverityMapper
|
||||
/// Translate a raw MXAccess severity into the four-bucket
|
||||
/// <see cref="AlarmSeverity"/> + OPC UA Part 9 numeric severity tuple.
|
||||
/// </summary>
|
||||
/// <param name="rawMxAccessSeverity">The raw MXAccess severity value (0-999 range, clamped if out of range).</param>
|
||||
public static (AlarmSeverity Bucket, int OpcUaSeverity) Map(int rawMxAccessSeverity)
|
||||
{
|
||||
if (rawMxAccessSeverity < 250)
|
||||
|
||||
@@ -13,6 +13,8 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
|
||||
/// </summary>
|
||||
internal static class MxValueDecoder
|
||||
{
|
||||
/// <summary>Decodes a gateway MxValue into a boxed CLR object.</summary>
|
||||
/// <param name="value">The MxValue to decode, or null.</param>
|
||||
public static object? Decode(MxValue? value)
|
||||
{
|
||||
if (value is null) return null;
|
||||
|
||||
@@ -13,6 +13,10 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
|
||||
/// </summary>
|
||||
internal static class MxValueEncoder
|
||||
{
|
||||
/// <summary>Encodes a CLR value as an MxValue for transmission to the gateway.</summary>
|
||||
/// <param name="value">The value to encode, or null for a null MxValue.</param>
|
||||
/// <returns>An MxValue instance representing the encoded value.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if the value type is not supported.</exception>
|
||||
public static MxValue Encode(object? value)
|
||||
{
|
||||
if (value is null) return new MxValue { IsNull = true };
|
||||
|
||||
@@ -66,6 +66,14 @@ public sealed class ReconnectSupervisor : IDisposable
|
||||
/// <summary>Fires after every state transition.</summary>
|
||||
public event EventHandler<StateTransition>? StateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new ReconnectSupervisor with the specified recovery handlers and options.
|
||||
/// </summary>
|
||||
/// <param name="reopen">Callback to reopen the transport connection.</param>
|
||||
/// <param name="replay">Callback to replay subscriptions and state after reopening.</param>
|
||||
/// <param name="options">Optional reconnection behavior configuration.</param>
|
||||
/// <param name="logger">Optional logger instance; defaults to null logger if not provided.</param>
|
||||
/// <param name="backoffDelay">Optional custom backoff delay calculator; uses default if not provided.</param>
|
||||
public ReconnectSupervisor(
|
||||
Func<CancellationToken, Task> reopen,
|
||||
Func<CancellationToken, Task> replay,
|
||||
@@ -92,11 +100,13 @@ public sealed class ReconnectSupervisor : IDisposable
|
||||
get { lock (_stateLock) return _state != State.Healthy; }
|
||||
}
|
||||
|
||||
/// <summary>Gets the message of the last reported transport error, if any.</summary>
|
||||
public string? LastError
|
||||
{
|
||||
get { lock (_stateLock) return _lastError; }
|
||||
}
|
||||
|
||||
/// <summary>Gets the UTC timestamp of the most recent state transition.</summary>
|
||||
public DateTime? LastTransitionUtc
|
||||
{
|
||||
get { lock (_stateLock) return _lastTransitionUtc; }
|
||||
@@ -108,6 +118,7 @@ public sealed class ReconnectSupervisor : IDisposable
|
||||
/// first call spawns a background task that drives reopen → replay until it
|
||||
/// succeeds or <see cref="Dispose"/> cancels it.
|
||||
/// </summary>
|
||||
/// <param name="cause">The exception that triggered the failure notification.</param>
|
||||
public void ReportTransportFailure(Exception cause)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(cause);
|
||||
@@ -135,6 +146,7 @@ public sealed class ReconnectSupervisor : IDisposable
|
||||
/// is cancelled. Returns immediately when already Healthy. Useful for tests
|
||||
/// and for orchestration that wants to gate calls on recovery completing.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Token to cancel the wait operation.</param>
|
||||
public async Task WaitForHealthyAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested && IsDegraded)
|
||||
@@ -227,6 +239,7 @@ public sealed class ReconnectSupervisor : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Disposes the supervisor and cancels any in-flight recovery loop.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
@@ -263,6 +276,9 @@ public sealed record ReconnectOptions(
|
||||
TimeSpan? InitialBackoffOverride = null,
|
||||
TimeSpan? MaxBackoffOverride = null)
|
||||
{
|
||||
/// <summary>Gets the initial backoff delay for reconnection attempts.</summary>
|
||||
public TimeSpan InitialBackoff => InitialBackoffOverride ?? TimeSpan.FromMilliseconds(500);
|
||||
|
||||
/// <summary>Gets the maximum backoff delay for reconnection attempts.</summary>
|
||||
public TimeSpan MaxBackoff => MaxBackoffOverride ?? TimeSpan.FromSeconds(30);
|
||||
}
|
||||
|
||||
@@ -49,6 +49,9 @@ internal static class StatusCodeMap
|
||||
/// which is what Wonderware Historian + MXAccess surface as <c>OPCITEMSTATE.qLong</c>'s
|
||||
/// low byte) to the OPC UA StatusCode uint.
|
||||
/// </summary>
|
||||
/// <param name="q">The OPC DA quality byte to convert.</param>
|
||||
/// <param name="logger">Optional logger for diagnostics on unknown quality bytes.</param>
|
||||
/// <returns>The OPC UA status code.</returns>
|
||||
public static uint FromQualityByte(byte q, ILogger? logger = null) => q switch
|
||||
{
|
||||
// Good family — top two bits 11b (192-255).
|
||||
@@ -83,6 +86,9 @@ internal static class StatusCodeMap
|
||||
/// the authoritative indicator. On failure, the detail byte (OPC DA quality substatus)
|
||||
/// drives the specific code, with a transport-error fallback for pre-MXAccess failures.
|
||||
/// </summary>
|
||||
/// <param name="status">The MX status proxy to convert, or null for Good status.</param>
|
||||
/// <param name="logger">Optional logger for diagnostics on unknown status codes.</param>
|
||||
/// <returns>The OPC UA status code.</returns>
|
||||
public static uint FromMxStatus(MxStatusProxy? status, ILogger? logger = null)
|
||||
{
|
||||
if (status is null) return Good;
|
||||
@@ -112,6 +118,8 @@ internal static class StatusCodeMap
|
||||
/// the round-trip in one place means a future change to the OPC UA bit layout cannot
|
||||
/// silently desync the probe-health decode.
|
||||
/// </summary>
|
||||
/// <param name="statusCode">The OPC UA status code to convert.</param>
|
||||
/// <returns>The OPC DA quality category byte.</returns>
|
||||
public static byte ToQualityCategoryByte(uint statusCode) =>
|
||||
(byte)(((statusCode >> 30) & 0x3u) switch
|
||||
{
|
||||
|
||||
@@ -26,7 +26,9 @@ internal sealed class SubscriptionRegistry
|
||||
private readonly ConcurrentDictionary<int, ImmutableHashSet<long>> _subscribersByItemHandle = new();
|
||||
private long _nextSubscriptionId;
|
||||
|
||||
/// <summary>Gets the number of tracked subscriptions.</summary>
|
||||
public int TrackedSubscriptionCount => _bySubscriptionId.Count;
|
||||
/// <summary>Gets the number of tracked item handles.</summary>
|
||||
public int TrackedItemHandleCount => _subscribersByItemHandle.Count;
|
||||
|
||||
/// <summary>Allocate a fresh subscription id. Monotonic; unique per registry lifetime.</summary>
|
||||
@@ -37,6 +39,8 @@ internal sealed class SubscriptionRegistry
|
||||
/// Failed tags (item handle = 0 or negative) are stored anyway so unsubscribe can
|
||||
/// emit per-tag UnsubscribeBulk for the ones that did succeed.
|
||||
/// </summary>
|
||||
/// <param name="subscriptionId">The subscription identifier.</param>
|
||||
/// <param name="bindings">The tag bindings for the subscription.</param>
|
||||
public void Register(long subscriptionId, IReadOnlyList<TagBinding> bindings)
|
||||
{
|
||||
var entry = new SubscriptionEntry(subscriptionId, bindings);
|
||||
@@ -55,6 +59,8 @@ internal sealed class SubscriptionRegistry
|
||||
/// Remove a subscription. Returns the bindings the caller should pass to
|
||||
/// <c>UnsubscribeBulkAsync</c>; null when the id was never registered.
|
||||
/// </summary>
|
||||
/// <param name="subscriptionId">The subscription identifier.</param>
|
||||
/// <returns>The bindings for the subscription, or null if not found.</returns>
|
||||
public IReadOnlyList<TagBinding>? Remove(long subscriptionId)
|
||||
{
|
||||
if (!_bySubscriptionId.TryRemove(subscriptionId, out var entry)) return null;
|
||||
@@ -83,6 +89,8 @@ internal sealed class SubscriptionRegistry
|
||||
/// scan of the binding list. At 50k tags / 1Hz this turns each dispatch from a
|
||||
/// 50k-element scan into a single dictionary lookup.
|
||||
/// </remarks>
|
||||
/// <param name="itemHandle">The gateway item handle.</param>
|
||||
/// <returns>A list of subscription and reference pairs for the item handle.</returns>
|
||||
public IReadOnlyList<(long SubscriptionId, string FullReference)> ResolveSubscribers(int itemHandle)
|
||||
{
|
||||
if (!_subscribersByItemHandle.TryGetValue(itemHandle, out var bag)) return [];
|
||||
@@ -117,6 +125,8 @@ internal sealed class SubscriptionRegistry
|
||||
/// handles dispatch and the now-dead pre-reconnect handles are dropped. No-op when the
|
||||
/// subscription id is unknown (it was unsubscribed during the reconnect window).
|
||||
/// </summary>
|
||||
/// <param name="subscriptionId">The subscription identifier.</param>
|
||||
/// <param name="newBindings">The new tag bindings after reconnection.</param>
|
||||
public void Rebind(long subscriptionId, IReadOnlyList<TagBinding> newBindings)
|
||||
{
|
||||
if (!_bySubscriptionId.TryGetValue(subscriptionId, out var oldEntry)) return;
|
||||
@@ -151,12 +161,19 @@ internal sealed class SubscriptionRegistry
|
||||
/// (Driver.Galaxy-012). Failed bindings (item handle ≤ 0) are excluded from the
|
||||
/// index because the EventPump only dispatches for positive handles.
|
||||
/// </summary>
|
||||
/// <summary>Per-subscription bookkeeping entry.</summary>
|
||||
private sealed class SubscriptionEntry
|
||||
{
|
||||
/// <summary>Gets the subscription identifier.</summary>
|
||||
public long SubscriptionId { get; }
|
||||
/// <summary>Gets the tag bindings for the subscription.</summary>
|
||||
public IReadOnlyList<TagBinding> Bindings { get; }
|
||||
/// <summary>Gets the index of full references by item handle.</summary>
|
||||
public IReadOnlyDictionary<int, string> FullRefByItemHandle { get; }
|
||||
|
||||
/// <summary>Initializes a new subscription entry.</summary>
|
||||
/// <param name="subscriptionId">The subscription identifier.</param>
|
||||
/// <param name="bindings">The tag bindings for the subscription.</param>
|
||||
public SubscriptionEntry(long subscriptionId, IReadOnlyList<TagBinding> bindings)
|
||||
{
|
||||
SubscriptionId = subscriptionId;
|
||||
|
||||
@@ -10,6 +10,10 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
|
||||
/// </summary>
|
||||
internal sealed class TracedGalaxyDataWriter(IGalaxyDataWriter inner, string clientName) : IGalaxyDataWriter
|
||||
{
|
||||
/// <summary>Writes data to Galaxy while recording telemetry span.</summary>
|
||||
/// <param name="writes">The list of write requests to process.</param>
|
||||
/// <param name="securityResolver">Function to resolve security classification for tag references.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
public async Task<IReadOnlyList<WriteResult>> WriteAsync(
|
||||
IReadOnlyList<WriteRequest> writes,
|
||||
Func<string, SecurityClassification> securityResolver,
|
||||
|
||||
@@ -10,6 +10,10 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
|
||||
/// </summary>
|
||||
internal sealed class TracedGalaxySubscriber(IGalaxySubscriber inner, string clientName) : IGalaxySubscriber
|
||||
{
|
||||
/// <summary>Subscribes to multiple Galaxy tags in bulk with tracing.</summary>
|
||||
/// <param name="fullReferences">The full tag references to subscribe to.</param>
|
||||
/// <param name="bufferedUpdateIntervalMs">The buffered update interval in milliseconds.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public async Task<IReadOnlyList<SubscribeResult>> SubscribeBulkAsync(
|
||||
IReadOnlyList<string> fullReferences, int bufferedUpdateIntervalMs, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -31,6 +35,9 @@ internal sealed class TracedGalaxySubscriber(IGalaxySubscriber inner, string cli
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Unsubscribes from multiple Galaxy tags in bulk with tracing.</summary>
|
||||
/// <param name="itemHandles">The item handles to unsubscribe from.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public async Task UnsubscribeBulkAsync(IReadOnlyList<int> itemHandles, CancellationToken cancellationToken)
|
||||
{
|
||||
using var activity = GalaxyTelemetry.ActivitySource.StartActivity("galaxy.unsubscribe_bulk");
|
||||
@@ -52,6 +59,7 @@ internal sealed class TracedGalaxySubscriber(IGalaxySubscriber inner, string cli
|
||||
/// spans would dominate the trace volume at 50k tags / 1Hz; ops gets per-event
|
||||
/// visibility through <see cref="EventPump"/>'s metrics in PR 6.2 instead.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public async IAsyncEnumerable<MxEvent> StreamEventsAsync(
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user