docs(xmldoc): fill missing XML docs + strip tracking-ID comments across src
v2-ci / build (push) Failing after 41s
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>/<returns>/<inheritdoc> where missing and removes
project bookkeeping IDs (task/tracking refs) from shipped code comments,
so the docs read cleanly and CommentChecker is quiet except for known
false positives (PLC/protocol terms, event/IEqualityComparer inheritdoc).
Doc/comment-only; no logic changed; solution builds clean.
This commit is contained in:
Joseph Doherty
2026-07-07 12:38:39 -04:00
parent 384dbd7d36
commit 9cad9ed0fc
375 changed files with 1899 additions and 2493 deletions
@@ -34,7 +34,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
private readonly EquipmentTagRefResolver<FocasTagDefinition> _resolver;
// Per-tag-name cache of the FocasAddress parsed once at InitializeAsync. ReadAsync /
// WriteAsync look up the pre-parsed value instead of re-parsing tag.Address on every hot
// call — resolves Driver.FOCAS-008. ConcurrentDictionary is required because the poll loop
// call. ConcurrentDictionary is required because the poll loop
// (ReadAsync) and the host write thread (WriteAsync) both mutate this cache when resolver-
// produced equipment tags are encountered for the first time.
private readonly ConcurrentDictionary<string, FocasAddress> _parsedAddressesByTagName =
@@ -75,15 +75,12 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
OnDataChange?.Invoke(this, new DataChangeEventArgs(handle, tagRef, snapshot)));
}
/// <summary>Gets the driver instance identifier.</summary>
/// <inheritdoc />
public string DriverInstanceId => _driverInstanceId;
/// <summary>Gets the driver type name.</summary>
/// <inheritdoc />
public string DriverType => "FOCAS";
/// <summary>Initializes the driver with configuration and prepares device connections and polling.</summary>
/// <param name="driverConfigJson">JSON configuration string for the driver.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A task representing the asynchronous initialization operation.</returns>
/// <inheritdoc />
public Task InitializeAsync(string driverConfigJson, CancellationToken cancellationToken)
{
Volatile.Write(ref _health, new DriverHealth(DriverState.Initializing, null, null));
@@ -91,7 +88,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
{
// Fail fast if the factory is a stub/unimplemented backend — the operator must
// see an actionable error at init rather than a phantom-Healthy driver that fails
// every read/write/subscribe silently (Driver.FOCAS-009).
// every read/write/subscribe silently.
_clientFactory.EnsureUsable();
foreach (var device in _options.Devices)
@@ -122,7 +119,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
}
_tagsByName[tag.Name] = tag;
// Cache the parsed FocasAddress so ReadAsync / WriteAsync don't re-parse on every
// hot-path call (Driver.FOCAS-008). The address string has already been validated
// hot-path call. The address string has already been validated
// by FocasAddress.TryParse above; reusing the parsed record avoids per-tick allocs
// on subscription pollers.
_parsedAddressesByTagName[tag.Name] = parsed;
@@ -171,19 +168,14 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
return Task.CompletedTask;
}
/// <summary>Reinitializes the driver by shutting down and restarting with new configuration.</summary>
/// <param name="driverConfigJson">JSON configuration string for the driver.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A task representing the asynchronous reinitialization operation.</returns>
/// <inheritdoc />
public async Task ReinitializeAsync(string driverConfigJson, CancellationToken cancellationToken)
{
await ShutdownAsync(cancellationToken).ConfigureAwait(false);
await InitializeAsync(driverConfigJson, cancellationToken).ConfigureAwait(false);
}
/// <summary>Shuts down the driver, cancelling all running operations and releasing resources.</summary>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A task representing the asynchronous shutdown operation.</returns>
/// <inheritdoc />
public async Task ShutdownAsync(CancellationToken cancellationToken)
{
await _poll.DisposeAsync().ConfigureAwait(false);
@@ -195,8 +187,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
foreach (var state in _devices.Values)
{
// Cancel-then-dispose can race in tight shutdown loops; swallowing is intentional
// but we now log the cause so a noisy shutdown leaves a Debug trace
// (Driver.FOCAS-007).
// but we now log the cause so a noisy shutdown leaves a Debug trace.
try { state.ProbeCts?.Cancel(); }
catch (Exception ex) { _logger.LogDebug(ex, "Cancelling probe CTS for {Host} failed", state.Options.HostAddress); }
state.ProbeCts?.Dispose();
@@ -218,13 +209,11 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
Volatile.Write(ref _health, new DriverHealth(DriverState.Unknown, Volatile.Read(ref _health).LastSuccessfulRead, null));
}
/// <summary>Gets the current health status of the driver.</summary>
/// <inheritdoc />
public DriverHealth GetHealth() => Volatile.Read(ref _health);
/// <summary>Gets the current memory footprint of the driver.</summary>
/// <inheritdoc />
public long GetMemoryFootprint() => 0;
/// <summary>Flushes optional internal caches.</summary>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A task representing the asynchronous cache flush operation.</returns>
/// <inheritdoc />
public Task FlushOptionalCachesAsync(CancellationToken cancellationToken) => Task.CompletedTask;
/// <summary>Gets the number of configured devices.</summary>
@@ -240,12 +229,14 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
/// <summary>Test seam — returns true when a parsed <see cref="FocasAddress"/> for the given
/// reference is present in the address cache (both authored and equipment-tag paths).</summary>
/// <param name="reference">The tag or equipment-tag reference to check.</param>
/// <returns><see langword="true"/> if a parsed address is cached for the reference; otherwise <see langword="false"/>.</returns>
internal bool IsParsedAddressCached(string reference) =>
_parsedAddressesByTagName.ContainsKey(reference);
// Resolves a tag definition to its parsed FocasAddress, caching the result so that
// equipment tags (resolver-produced, not seeded at InitializeAsync) don't re-parse the
// address string on every ReadAsync / WriteAsync hot-path call (Driver.FOCAS-008).
// address string on every ReadAsync / WriteAsync hot-path call.
// Throwing inside the GetOrAdd factory propagates the exception to the caller and does
// NOT store anything in the dictionary — consistent with the existing "fail fast on a
// malformed address" behaviour from the init-time validation of authored tags.
@@ -257,10 +248,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
// ---- IReadable ----
/// <summary>Reads values from one or more tags asynchronously.</summary>
/// <param name="fullReferences">A read-only list of tag references to read.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A task representing the asynchronous read operation.</returns>
/// <inheritdoc />
public async Task<IReadOnlyList<DataValueSnapshot>> ReadAsync(
IReadOnlyList<string> fullReferences, CancellationToken cancellationToken)
{
@@ -329,10 +317,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
// ---- IWritable ----
/// <summary>Writes values to one or more tags asynchronously.</summary>
/// <param name="writes">A read-only list of write requests.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A task representing the asynchronous write operation.</returns>
/// <inheritdoc />
public async Task<IReadOnlyList<WriteResult>> WriteAsync(
IReadOnlyList<WriteRequest> writes, CancellationToken cancellationToken)
{
@@ -401,18 +386,14 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
// ---- ITagDiscovery ----
/// <summary>
/// Retry-until-stable: the FixedTree subtree is filled in asynchronously by
/// <see cref="FixedTreeLoopAsync"/> a couple of seconds AFTER connect, so the first
/// post-connect <see cref="DiscoverAsync"/> pass would miss it — the host must re-run
/// discovery until the captured node set is non-empty and stable.
/// </summary>
/// <inheritdoc />
// Retry-until-stable: the FixedTree subtree is filled in asynchronously by
// FixedTreeLoopAsync a couple of seconds AFTER connect, so the first post-connect
// DiscoverAsync pass would miss it — the host must re-run discovery until the captured
// node set is non-empty and stable.
public DiscoveryRediscoverPolicy RediscoverPolicy => DiscoveryRediscoverPolicy.UntilStable;
/// <summary>Discovers tags and builds the OPC UA address space asynchronously.</summary>
/// <param name="builder">The address space builder for constructing the OPC UA namespace.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A task representing the asynchronous discovery operation.</returns>
/// <inheritdoc />
public Task DiscoverAsync(IAddressSpaceBuilder builder, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(builder);
@@ -589,19 +570,12 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
// ---- ISubscribable (polling overlay via shared engine) ----
/// <summary>Subscribes to data changes on one or more tags.</summary>
/// <param name="fullReferences">A read-only list of tag references to subscribe to.</param>
/// <param name="publishingInterval">The interval at which to publish data changes.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A task representing the asynchronous subscription operation.</returns>
/// <inheritdoc />
public Task<ISubscriptionHandle> SubscribeAsync(
IReadOnlyList<string> fullReferences, TimeSpan publishingInterval, CancellationToken cancellationToken) =>
Task.FromResult(_poll.Subscribe(fullReferences, publishingInterval));
/// <summary>Unsubscribes from a previous subscription.</summary>
/// <param name="handle">The subscription handle to unsubscribe from.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A task representing the asynchronous unsubscription operation.</returns>
/// <inheritdoc />
public Task UnsubscribeAsync(ISubscriptionHandle handle, CancellationToken cancellationToken)
{
_poll.Unsubscribe(handle);
@@ -610,7 +584,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
// ---- IHostConnectivityProbe ----
/// <summary>Gets the connectivity status of all configured devices.</summary>
/// <inheritdoc />
public IReadOnlyList<HostConnectivityStatus> GetHostStatuses() =>
[.. _devices.Values.Select(s => new HostConnectivityStatus(s.Options.HostAddress, s.HostState, s.HostStateChangedUtc))];
@@ -623,7 +597,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
{
var client = await EnsureConnectedAsync(state, ct).ConfigureAwait(false);
// Apply Probe.Timeout so a hung CNC socket gets cancelled at the configured
// budget rather than blocking until the OS TCP timeout (Driver.FOCAS-009).
// budget rather than blocking until the OS TCP timeout.
// TimeSpan.Zero / negative means "no per-probe timeout" — fall back to the loop
// cancellation token unmodified.
var probeTimeout = _options.Probe.Timeout;
@@ -1048,10 +1022,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
// ---- IAlarmSource ----
/// <summary>Subscribes to alarm events from the driver.</summary>
/// <param name="sourceNodeIds">A read-only list of source node IDs to subscribe to.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A task representing the asynchronous subscription operation.</returns>
/// <inheritdoc />
public Task<IAlarmSubscriptionHandle> SubscribeAlarmsAsync(
IReadOnlyList<string> sourceNodeIds, CancellationToken cancellationToken)
{
@@ -1061,17 +1032,11 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
return _alarmProjection.SubscribeAsync(sourceNodeIds, cancellationToken);
}
/// <summary>Unsubscribes from a previous alarm subscription.</summary>
/// <param name="handle">The alarm subscription handle to unsubscribe from.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A task representing the asynchronous unsubscription operation.</returns>
/// <inheritdoc />
public Task UnsubscribeAlarmsAsync(IAlarmSubscriptionHandle handle, CancellationToken cancellationToken) =>
_alarmProjection is { } p ? p.UnsubscribeAsync(handle, cancellationToken) : Task.CompletedTask;
/// <summary>Acknowledges one or more alarms.</summary>
/// <param name="acknowledgements">A read-only list of alarm acknowledgement requests.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A task representing the asynchronous acknowledgement operation.</returns>
/// <inheritdoc />
public Task AcknowledgeAsync(
IReadOnlyList<AlarmAcknowledgeRequest> acknowledgements, CancellationToken cancellationToken) =>
_alarmProjection is { } p ? p.AcknowledgeAsync(acknowledgements, cancellationToken) : Task.CompletedTask;
@@ -1113,9 +1078,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
// ---- IPerCallHostResolver ----
/// <summary>Resolves the host address for a given tag reference.</summary>
/// <param name="fullReference">The full reference of the tag.</param>
/// <returns>The host address for the tag reference.</returns>
/// <inheritdoc />
public string ResolveHost(string fullReference)
{
if (_tagsByName.TryGetValue(fullReference, out var def))
@@ -1159,6 +1122,7 @@ public sealed class FocasDriver : IDriver, IReadable, IWritable, ITagDiscovery,
/// <summary>Disposes the driver and releases all resources synchronously.</summary>
public void Dispose() => DisposeAsync().AsTask().GetAwaiter().GetResult();
/// <summary>Disposes the driver and releases all resources asynchronously.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
public async ValueTask DisposeAsync() => await ShutdownAsync(CancellationToken.None).ConfigureAwait(false);
/// <summary>