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
@@ -68,22 +68,13 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
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.
/// </summary>
/// <inheritdoc />
public string DriverType => "AbLegacy";
/// <summary>
/// Initializes the driver asynchronously.
/// </summary>
/// <param name="driverConfigJson">The driver configuration JSON.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
/// <inheritdoc />
public Task InitializeAsync(string driverConfigJson, CancellationToken cancellationToken)
{
_health = new DriverHealth(DriverState.Initializing, null, null);
@@ -132,9 +123,6 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
catch (Exception ex)
{
_health = new DriverHealth(DriverState.Faulted, null, ex.Message);
// Driver.AbLegacy-005 — structured log of the init failure so a field operator sees
// the exception in the rolling Serilog file rather than only as a transient Detail
// string on DriverHealth.
_logger.LogError(ex,
"AbLegacy driver initialise failed. Driver={DriverInstanceId}", _driverInstanceId);
// Tear down any probe loops and cached state that were created before the failure so
@@ -157,23 +145,14 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
return Task.CompletedTask;
}
/// <summary>
/// Reinitializes the driver asynchronously.
/// </summary>
/// <param name="driverConfigJson">The driver configuration JSON.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous 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 asynchronously.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
/// <inheritdoc />
public async Task ShutdownAsync(CancellationToken cancellationToken)
{
await _poll.DisposeAsync().ConfigureAwait(false);
@@ -190,23 +169,13 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
_health = new DriverHealth(DriverState.Unknown, _health.LastSuccessfulRead, null);
}
/// <summary>
/// Gets the driver health status.
/// </summary>
/// <returns>The driver health status.</returns>
/// <inheritdoc />
public DriverHealth GetHealth() => _health;
/// <summary>
/// Gets the memory footprint of the driver.
/// </summary>
/// <returns>The memory footprint in bytes.</returns>
/// <inheritdoc />
public long GetMemoryFootprint() => 0;
/// <summary>
/// Flushes optional caches asynchronously.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
/// <inheritdoc />
public Task FlushOptionalCachesAsync(CancellationToken cancellationToken) => Task.CompletedTask;
/// <summary>
@@ -224,12 +193,7 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
// ---- IReadable ----
/// <summary>
/// Reads data values asynchronously.
/// </summary>
/// <param name="fullReferences">The full references to read.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A list of data value snapshots.</returns>
/// <inheritdoc />
public async Task<IReadOnlyList<DataValueSnapshot>> ReadAsync(
IReadOnlyList<string> fullReferences, CancellationToken cancellationToken)
{
@@ -267,11 +231,6 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
await runtime.ReadAsync(cancellationToken).ConfigureAwait(false);
status = runtime.GetStatus();
var parsed = AbLegacyAddress.TryParse(def.Address);
// Phase 4c #137 — an ARRAY tag (non-null ArrayLength, ≥1) decodes the whole
// contiguous read into a typed CLR array of that count, INCLUDING a 1-element
// array (review I-3); a SCALAR tag (null ArrayLength) decodes a single value.
// The runtime was created with a matching ElementCount in EnsureTagRuntimeAsync
// so its buffer holds all the elements.
if (status != 0)
value = null;
else if (IsArrayTag(def))
@@ -290,11 +249,6 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
AbLegacyStatusMapper.MapLibplctagStatus(status), null, now);
_health = new DriverHealth(DriverState.Degraded, _health.LastSuccessfulRead,
$"libplctag status {status} reading {reference}");
// Driver.AbLegacy-005 — log the FIRST non-zero libplctag status per device so
// a field operator can correlate a comms problem with a structured log
// entry. Detail on DriverHealth is overwritten by the very next read; the
// log entry persists. Subsequent occurrences on the same device stay quiet so
// a permanently-bad PLC doesn't flood the rolling file.
if (!device.FirstNonZeroStatusLogged)
{
device.FirstNonZeroStatusLogged = true;
@@ -325,12 +279,7 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
// ---- IWritable ----
/// <summary>
/// Writes data values asynchronously.
/// </summary>
/// <param name="writes">The write requests.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A list of write results.</returns>
/// <inheritdoc />
public async Task<IReadOnlyList<WriteResult>> WriteAsync(
IReadOnlyList<WriteRequest> writes, CancellationToken cancellationToken)
{
@@ -422,19 +371,10 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
// ---- ITagDiscovery ----
/// <summary>
/// Run-once: <see cref="DiscoverAsync"/> emits the complete node set synchronously from
/// the configured device/tag tables within a single pass — there is no shape that fills
/// in asynchronously after connect, so a single discovery pass is sufficient.
/// </summary>
/// <inheritdoc />
public DiscoveryRediscoverPolicy RediscoverPolicy => DiscoveryRediscoverPolicy.Once;
/// <summary>
/// Discovers tags and populates the address space asynchronously.
/// </summary>
/// <param name="builder">The address space builder.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
/// <inheritdoc />
public Task DiscoverAsync(IAddressSpaceBuilder builder, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(builder);
@@ -447,14 +387,6 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
string.Equals(t.DeviceHostAddress, device.HostAddress, StringComparison.OrdinalIgnoreCase));
foreach (var tag in tagsForDevice)
{
// Phase 4c #137 — PCCC data files are inherently arrays of elements (a single N7
// file is up to 256 words). The canonical contract: a tag is an ARRAY ⟺ its
// ArrayLength is non-null (≥1, set by the parser only when isArray:true). A tag
// with a non-null ArrayLength materialises a 1-D array OPC UA node, INCLUDING a
// 1-element array (ArrayLength:1 → a [1] node — review I-3). ArrayDim is clamped
// to the PCCC file maximum (AbLegacyArray.MaxElements = 256) so the declared
// dimension can never exceed what a single data file holds; ArrayLength == null
// (scalar) stays scalar.
var isArray = tag.ArrayLength is int len && len >= 1;
var arrayDim = isArray
? (uint)Math.Min(tag.ArrayLength!.Value, AbLegacyArray.MaxElements)
@@ -477,23 +409,12 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
// ---- ISubscribable (polling overlay via shared engine) ----
/// <summary>
/// Subscribes to data changes asynchronously.
/// </summary>
/// <param name="fullReferences">The full references to subscribe to.</param>
/// <param name="publishingInterval">The publishing interval.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A subscription handle.</returns>
/// <inheritdoc />
public Task<ISubscriptionHandle> SubscribeAsync(
IReadOnlyList<string> fullReferences, TimeSpan publishingInterval, CancellationToken cancellationToken) =>
Task.FromResult(_poll.Subscribe(fullReferences, publishingInterval));
/// <summary>
/// Unsubscribes from data changes asynchronously.
/// </summary>
/// <param name="handle">The subscription handle.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
/// <inheritdoc />
public Task UnsubscribeAsync(ISubscriptionHandle handle, CancellationToken cancellationToken)
{
_poll.Unsubscribe(handle);
@@ -502,10 +423,7 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
// ---- IHostConnectivityProbe ----
/// <summary>
/// Gets the host connectivity statuses.
/// </summary>
/// <returns>A list of host connectivity statuses.</returns>
/// <inheritdoc />
public IReadOnlyList<HostConnectivityStatus> GetHostStatuses() =>
[.. _devices.Values.Select(s => new HostConnectivityStatus(s.Options.HostAddress, s.HostState, s.HostStateChangedUtc))];
@@ -560,9 +478,6 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
state.HostState = newState;
state.HostStateChangedUtc = DateTime.UtcNow;
}
// Driver.AbLegacy-005 — structured log of every probe-driven transition. Operators can
// grep the rolling Serilog file for the device address to see when a PLC was last
// reachable. Downgrades to Stopped log as Warning; recoveries log as Information.
if (newState == HostState.Stopped)
_logger.LogWarning(
"AbLegacy probe transition. Driver={DriverInstanceId} Device={DeviceHostAddress} From={Old} To={New}",
@@ -577,23 +492,7 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
// ---- IPerCallHostResolver ----
/// <summary>
/// Map a full reference to the host string used as the resilience-pipeline breaker key.
/// Driver.AbLegacy-013 — the contract on <see cref="IPerCallHostResolver"/> requires that
/// implementations never throw on an unknown reference. The fallback chain is therefore:
/// <list type="number">
/// <item>Known tag → its <c>DeviceHostAddress</c>.</item>
/// <item>Unknown reference but devices configured → the first device's host address
/// (multi-device drivers degrade to single-host behaviour rather than failing).</item>
/// <item>Unknown reference and no devices configured → the driver instance id, which
/// the dispatch layer treats as the single-host key per the interface
/// documentation. Reaching this branch indicates a misconfigured driver (no
/// devices) so callers that want to surface that should validate
/// <see cref="DeviceCount"/> before relying on per-tag routing.</item>
/// </list>
/// </summary>
/// <param name="fullReference">The full reference to resolve.</param>
/// <returns>The host address for the reference.</returns>
/// <inheritdoc />
public string ResolveHost(string fullReference)
{
if (_tagsByName.TryGetValue(fullReference, out var def))
@@ -698,7 +597,7 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
}
/// <summary>
/// Phase 4c #137 — whether a tag definition is an ARRAY. The canonical contract: a tag is
/// Whether a tag definition is an ARRAY. The canonical contract: a tag is
/// an array ⟺ its <see cref="AbLegacyTagDefinition.ArrayLength"/> is non-null (the parser
/// sets it ≥1 only when isArray:true), so a 1-element array (ArrayLength:1) IS an array
/// (review I-3). <c>null</c> ArrayLength ⇒ scalar.
@@ -706,7 +605,7 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
private static bool IsArrayTag(AbLegacyTagDefinition def) => def.ArrayLength is int len && len >= 1;
/// <summary>
/// Phase 4c #137 — the effective libplctag element count for a tag definition: the tag's
/// The effective libplctag element count for a tag definition: the tag's
/// <see cref="AbLegacyTagDefinition.ArrayLength"/> clamped to the PCCC file maximum
/// (<see cref="AbLegacyArray.MaxElements"/> = 256) when it is an array (≥1, INCLUDING 1),
/// or <c>1</c> when the tag is scalar (null ArrayLength). Used both to size the runtime at
@@ -740,9 +639,6 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
LibplctagPlcAttribute: device.Profile.LibplctagPlcAttribute,
TagName: parsed.ToLibplctagName(),
Timeout: _options.Timeout,
// Phase 4c #137 — multi-element PCCC file read. A multi-element span (ArrayLength
// > 1) creates the libplctag tag with that element count so a single read fetches
// the whole array from the base address; scalar tags pass 1 and read unchanged.
ElementCount: EffectiveArrayLength(def)));
try
{
@@ -763,7 +659,7 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
}
/// <summary>
/// Driver.AbLegacy-011 — synchronous teardown. Mirrors the body of
/// Synchronous teardown. Mirrors the body of
/// <see cref="ShutdownAsync"/> but never wraps the async path in
/// <c>.AsTask().GetAwaiter().GetResult()</c>. The poll engine's <c>DisposeAsync</c> is
/// drained with a <c>ConfigureAwait(false)</c> awaiter so a captured single-threaded
@@ -841,7 +737,7 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
new(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Parent-word runtimes for bit-within-word RMW writes (task #181). Keyed by the
/// Parent-word runtimes for bit-within-word RMW writes. Keyed by the
/// parent address (bit suffix stripped) — e.g. writes to N7:0/3 + N7:0/5 share a
/// single parent runtime for N7:0.
/// </summary>
@@ -916,7 +812,7 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
public bool ProbeInitialized { get; set; }
/// <summary>
/// Driver.AbLegacy-005 — per-device latch for the structured "first non-zero
/// Per-device latch for the structured "first non-zero
/// libplctag status" log. Reset to <see langword="false"/> on a successful read so a
/// future outage re-fires the warning rather than being suppressed by a stale flag.
/// Concurrent readers on the same device may race the unlatched check + set, but the