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

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:
Joseph Doherty
2026-05-28 08:10:17 -04:00
parent f9fc7dd2e1
commit 64e3fbe035
756 changed files with 9876 additions and 96 deletions
@@ -69,6 +69,9 @@ public static class S7AddressParser
/// the offending input echoed in the message so operators can correlate to the tag
/// config that produced the fault.
/// </summary>
/// <param name="address">The S7 address string to parse.</param>
/// <returns>The parsed address structure.</returns>
/// <exception cref="FormatException">Thrown if the address syntax is invalid.</exception>
public static S7ParsedAddress Parse(string address)
{
if (string.IsNullOrWhiteSpace(address))
@@ -102,6 +105,9 @@ public static class S7AddressParser
/// config validation pages in the Admin UI). Returns <c>false</c> for any input that
/// would throw from <see cref="Parse"/>.
/// </summary>
/// <param name="address">The S7 address string to parse.</param>
/// <param name="result">The parsed address structure (valid only if method returns true).</param>
/// <returns>True if parsing succeeded; false otherwise.</returns>
public static bool TryParse(string address, out S7ParsedAddress result)
{
try
@@ -55,7 +55,9 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId, I
/// </summary>
private static readonly TimeSpan DrainTimeout = TimeSpan.FromSeconds(5);
/// <summary>Occurs when a subscribed tag value or status code changes.</summary>
public event EventHandler<DataChangeEventArgs>? OnDataChange;
/// <summary>Occurs when host connectivity status changes.</summary>
public event EventHandler<HostStatusChangedEventArgs>? OnHostStatusChanged;
/// <summary>OPC UA StatusCode used when the tag name isn't in the driver's tag map.</summary>
@@ -101,9 +103,15 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId, I
private DriverHealth _health = new(DriverState.Unknown, null, null);
private bool _disposed;
/// <summary>Gets the unique driver instance identifier.</summary>
public string DriverInstanceId => driverInstanceId;
/// <summary>Gets the driver type name.</summary>
public string DriverType => "S7";
/// <summary>Initializes the driver with the provided configuration.</summary>
/// <param name="driverConfigJson">JSON configuration string.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task InitializeAsync(string driverConfigJson, CancellationToken cancellationToken)
{
_health = new DriverHealth(DriverState.Initializing, null, null);
@@ -184,6 +192,10 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId, I
}
}
/// <summary>Reinitializes the driver with a new configuration.</summary>
/// <param name="driverConfigJson">JSON configuration string.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task ReinitializeAsync(string driverConfigJson, CancellationToken cancellationToken)
{
// InitializeAsync re-parses driverConfigJson, so a config change delivered here is
@@ -192,6 +204,9 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId, I
await InitializeAsync(driverConfigJson, cancellationToken).ConfigureAwait(false);
}
/// <summary>Shuts down the driver and releases resources.</summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task ShutdownAsync(CancellationToken cancellationToken)
{
// Signal cancellation to the probe + poll loops first, collect their Task handles,
@@ -236,6 +251,15 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId, I
_health = new DriverHealth(DriverState.Unknown, _health.LastSuccessfulRead, null);
}
/// <summary>Gets the current driver health.</summary>
/// <returns>The current health state.</returns>
public DriverHealth GetHealth() => _health;
/// <summary>Flushes optional caches to free memory.</summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public Task FlushOptionalCachesAsync(CancellationToken cancellationToken) => Task.CompletedTask;
/// <summary>
/// True when <paramref name="driverConfigJson"/> carries a real config body. The
/// bootstrapper always passes a populated document; some unit tests pass <c>"{}"</c> or
@@ -309,8 +333,6 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId, I
S7DataType.DateTime,
};
public DriverHealth GetHealth() => _health;
/// <summary>
/// Approximate memory footprint. The Plc instance + one 240-960 byte PDU buffer is
/// under 4 KB; return 0 because the <see cref="IDriver"/> contract asks for a
@@ -318,10 +340,12 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId, I
/// </summary>
public long GetMemoryFootprint() => 0;
public Task FlushOptionalCachesAsync(CancellationToken cancellationToken) => Task.CompletedTask;
// ---- IReadable ----
/// <summary>Reads values from the specified tag references.</summary>
/// <param name="fullReferences">Tag references to read.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation returning a list of data value snapshots.</returns>
public async Task<IReadOnlyList<DataValueSnapshot>> ReadAsync(
IReadOnlyList<string> fullReferences, CancellationToken cancellationToken)
{
@@ -405,6 +429,10 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId, I
/// Factored out of <see cref="ReadOneAsync"/> so it can be exercised in unit tests
/// without a live PLC (Driver.S7-014).
/// </summary>
/// <param name="tag">Tag definition containing type information.</param>
/// <param name="addr">Parsed tag address.</param>
/// <param name="raw">Raw value from S7.Net.</param>
/// <returns>The reinterpreted value in the target semantic type.</returns>
internal static object ReinterpretRawValue(S7TagDefinition tag, S7ParsedAddress addr, object raw) =>
(tag.DataType, addr.Size, raw) switch
{
@@ -429,6 +457,10 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId, I
// ---- IWritable ----
/// <summary>Writes values to the specified tags.</summary>
/// <param name="writes">Write requests containing tag references and values.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation returning a list of write results.</returns>
public async Task<IReadOnlyList<WriteResult>> WriteAsync(
IReadOnlyList<WriteRequest> writes, CancellationToken cancellationToken)
{
@@ -517,6 +549,9 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId, I
/// Factored out of <see cref="WriteOneAsync"/> so it can be exercised in unit tests
/// without a live PLC (Driver.S7-014).
/// </summary>
/// <param name="dataType">Target S7 data type.</param>
/// <param name="value">Value to box.</param>
/// <returns>The boxed value in the wire type expected by S7.Net.</returns>
internal static object BoxValueForWrite(S7DataType dataType, object? value) => dataType switch
{
S7DataType.Bool => (object)Convert.ToBoolean(value),
@@ -562,6 +597,10 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId, I
// ---- ITagDiscovery ----
/// <summary>Discovers tags and builds the OPC UA address space.</summary>
/// <param name="builder">Address space builder.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public Task DiscoverAsync(IAddressSpaceBuilder builder, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(builder);
@@ -599,6 +638,11 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId, I
// ---- ISubscribable (polling overlay) ----
/// <summary>Subscribes to changes on the specified tag references.</summary>
/// <param name="fullReferences">Tag references to subscribe to.</param>
/// <param name="publishingInterval">Polling interval.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation returning a subscription handle.</returns>
public Task<ISubscriptionHandle> SubscribeAsync(
IReadOnlyList<string> fullReferences, TimeSpan publishingInterval, CancellationToken cancellationToken)
{
@@ -618,6 +662,10 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId, I
return Task.FromResult<ISubscriptionHandle>(handle);
}
/// <summary>Unsubscribes from a subscription.</summary>
/// <param name="handle">Subscription handle.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public Task UnsubscribeAsync(ISubscriptionHandle handle, CancellationToken cancellationToken)
{
if (handle is S7SubscriptionHandle h && _subscriptions.TryRemove(h.Id, out var state))
@@ -714,6 +762,9 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId, I
/// <paramref name="interval"/>; each subsequent failure doubles the wait up to
/// <see cref="PollBackoffCap"/>. Computed in ticks to avoid overflow at large counts.
/// </summary>
/// <param name="interval">Base polling interval.</param>
/// <param name="consecutiveFailures">Number of consecutive failures.</param>
/// <returns>The computed backoff delay.</returns>
internal static TimeSpan ComputeBackoffDelay(TimeSpan interval, int consecutiveFailures)
{
if (consecutiveFailures <= 0) return interval;
@@ -748,6 +799,7 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId, I
TimeSpan Interval,
CancellationTokenSource Cts)
{
/// <summary>Gets the last known values for subscribed tags.</summary>
public ConcurrentDictionary<string, DataValueSnapshot> LastValues { get; }
= new(StringComparer.OrdinalIgnoreCase);
@@ -760,6 +812,7 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId, I
private sealed record S7SubscriptionHandle(long Id) : ISubscriptionHandle
{
/// <summary>Gets the diagnostic identifier for this subscription.</summary>
public string DiagnosticId => $"s7-sub-{Id}";
}
@@ -772,6 +825,8 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId, I
/// </summary>
public string HostName => $"{_options.Host}:{_options.Port}";
/// <summary>Gets the host connectivity statuses.</summary>
/// <returns>A list containing the current host status.</returns>
public IReadOnlyList<HostConnectivityStatus> GetHostStatuses()
{
lock (_probeLock)
@@ -827,6 +882,7 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId, I
OnHostStatusChanged?.Invoke(this, new HostStatusChangedEventArgs(HostName, old, newState));
}
/// <summary>Disposes the driver and releases resources.</summary>
public void Dispose()
{
// Driver.S7-010: avoid the sync-over-async DisposeAsync().AsTask().GetAwaiter().GetResult()
@@ -840,6 +896,8 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId, I
_gate.Dispose();
}
/// <summary>Asynchronously disposes the driver and releases resources.</summary>
/// <returns>A task representing the asynchronous operation.</returns>
public async ValueTask DisposeAsync()
{
if (_disposed) return;
@@ -15,12 +15,18 @@ public static class S7DriverFactoryExtensions
{
public const string DriverTypeName = "S7";
/// <summary>Registers the S7 driver factory with the registry.</summary>
/// <param name="registry">The driver factory registry.</param>
public static void Register(DriverFactoryRegistry registry)
{
ArgumentNullException.ThrowIfNull(registry);
registry.Register(DriverTypeName, CreateInstance);
}
/// <summary>Creates a new S7 driver instance from configuration.</summary>
/// <param name="driverInstanceId">The unique identifier for the driver instance.</param>
/// <param name="driverConfigJson">The JSON configuration for the driver.</param>
/// <returns>A newly created S7 driver instance.</returns>
internal static S7Driver CreateInstance(string driverInstanceId, string driverConfigJson)
{
ArgumentException.ThrowIfNullOrWhiteSpace(driverInstanceId);
@@ -34,6 +40,9 @@ public static class S7DriverFactoryExtensions
/// / <see cref="S7Driver.ReinitializeAsync"/> so a config change delivered through the
/// <c>IDriver</c> contract is actually applied — see code-review finding Driver.S7-011.
/// </summary>
/// <param name="driverInstanceId">The unique identifier for the driver instance.</param>
/// <param name="driverConfigJson">The JSON configuration for the driver.</param>
/// <returns>Parsed S7 driver options.</returns>
internal static S7DriverOptions ParseOptions(string driverInstanceId, string driverConfigJson)
{
ArgumentException.ThrowIfNullOrWhiteSpace(driverInstanceId);
@@ -104,32 +113,52 @@ public static class S7DriverFactoryExtensions
AllowTrailingCommas = true,
};
/// <summary>Data transfer object for S7 driver configuration.</summary>
internal sealed class S7DriverConfigDto
{
/// <summary>Gets the PLC host address.</summary>
public string? Host { get; init; }
/// <summary>Gets the PLC port.</summary>
public int? Port { get; init; }
/// <summary>Gets the CPU type name.</summary>
public string? CpuType { get; init; }
/// <summary>Gets the rack number.</summary>
public short? Rack { get; init; }
/// <summary>Gets the slot number.</summary>
public short? Slot { get; init; }
/// <summary>Gets the connection timeout in milliseconds.</summary>
public int? TimeoutMs { get; init; }
/// <summary>Gets the list of tag definitions.</summary>
public List<S7TagDto>? Tags { get; init; }
/// <summary>Gets the probe configuration.</summary>
public S7ProbeDto? Probe { get; init; }
}
/// <summary>Data transfer object for S7 tag definition.</summary>
internal sealed class S7TagDto
{
/// <summary>Gets the tag name.</summary>
public string? Name { get; init; }
/// <summary>Gets the S7 address (e.g., DB1.DBD0).</summary>
public string? Address { get; init; }
/// <summary>Gets the data type name.</summary>
public string? DataType { get; init; }
/// <summary>Gets a value indicating whether the tag is writable.</summary>
public bool? Writable { get; init; }
/// <summary>Gets the string length for string types.</summary>
public int? StringLength { get; init; }
/// <summary>Gets a value indicating whether write is idempotent.</summary>
public bool? WriteIdempotent { get; init; }
}
/// <summary>Data transfer object for S7 probe configuration.</summary>
internal sealed class S7ProbeDto
{
/// <summary>Gets a value indicating whether probing is enabled.</summary>
public bool? Enabled { get; init; }
/// <summary>Gets the probe interval in milliseconds.</summary>
public int? IntervalMs { get; init; }
/// <summary>Gets the probe timeout in milliseconds.</summary>
public int? TimeoutMs { get; init; }
// Driver.S7-012: ProbeAddress removed from the configurable surface — the probe uses
// ReadStatusAsync (CPU status), not a tag-address read. Config documents that previously
@@ -67,8 +67,11 @@ public sealed class S7DriverOptions
public sealed class S7ProbeOptions
{
/// <summary>Gets or sets a value indicating whether probing is enabled.</summary>
public bool Enabled { get; init; } = true;
/// <summary>Gets or sets the probe interval.</summary>
public TimeSpan Interval { get; init; } = TimeSpan.FromSeconds(5);
/// <summary>Gets or sets the probe timeout.</summary>
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2);
// Driver.S7-012: ProbeAddress was configured and documented but was never read by the