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:
@@ -7,6 +7,9 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus;
|
||||
/// </summary>
|
||||
public interface IModbusTransport : IAsyncDisposable
|
||||
{
|
||||
/// <summary>Establishes a connection to the Modbus server.</summary>
|
||||
/// <param name="ct">A cancellation token to observe for cancellation.</param>
|
||||
/// <returns>A task representing the asynchronous connection operation.</returns>
|
||||
Task ConnectAsync(CancellationToken ct);
|
||||
|
||||
/// <summary>
|
||||
@@ -14,12 +17,20 @@ public interface IModbusTransport : IAsyncDisposable
|
||||
/// Throws <see cref="ModbusException"/> when the server returns an exception PDU
|
||||
/// (function code + 0x80 + exception code).
|
||||
/// </summary>
|
||||
/// <param name="unitId">The Modbus unit identifier (slave address).</param>
|
||||
/// <param name="pdu">The protocol data unit (function code and data) to send.</param>
|
||||
/// <param name="ct">A cancellation token to observe for cancellation.</param>
|
||||
/// <returns>A task representing the asynchronous send operation that returns the response PDU.</returns>
|
||||
Task<byte[]> SendAsync(byte unitId, byte[] pdu, CancellationToken ct);
|
||||
}
|
||||
|
||||
/// <summary>Represents a Modbus protocol exception.</summary>
|
||||
public sealed class ModbusException(byte functionCode, byte exceptionCode, string message)
|
||||
: Exception(message)
|
||||
{
|
||||
/// <summary>Gets the Modbus function code that caused the exception.</summary>
|
||||
public byte FunctionCode { get; } = functionCode;
|
||||
|
||||
/// <summary>Gets the Modbus exception code.</summary>
|
||||
public byte ExceptionCode { get; } = exceptionCode;
|
||||
}
|
||||
|
||||
@@ -77,7 +77,9 @@ public sealed class ModbusDriver
|
||||
// and the Volatile API is the documented portable form).
|
||||
private DriverHealth _health = new(DriverState.Unknown, null, null);
|
||||
|
||||
/// <summary>Occurs when a subscribed tag value changes.</summary>
|
||||
public event EventHandler<DataChangeEventArgs>? OnDataChange;
|
||||
/// <summary>Occurs when host connectivity status changes.</summary>
|
||||
public event EventHandler<HostStatusChangedEventArgs>? OnHostStatusChanged;
|
||||
|
||||
// ---- nested types ----
|
||||
@@ -96,6 +98,11 @@ public sealed class ModbusDriver
|
||||
|
||||
// ---- ctor + identity ----
|
||||
|
||||
/// <summary>Initializes a new Modbus TCP driver with the specified options and transport factory.</summary>
|
||||
/// <param name="options">Driver configuration options.</param>
|
||||
/// <param name="driverInstanceId">Unique identifier for this driver instance.</param>
|
||||
/// <param name="transportFactory">Factory to create the Modbus transport; defaults to ModbusTcpTransport.</param>
|
||||
/// <param name="logger">Logger instance; defaults to null logger if not provided.</param>
|
||||
public ModbusDriver(ModbusDriverOptions options, string driverInstanceId,
|
||||
Func<ModbusDriverOptions, IModbusTransport>? transportFactory = null,
|
||||
ILogger<ModbusDriver>? logger = null)
|
||||
@@ -127,6 +134,7 @@ public sealed class ModbusDriver
|
||||
/// returned host string, so a dead RTU slave behind an Ethernet gateway opens its own
|
||||
/// breaker without tripping siblings on the same TCP socket.
|
||||
/// </summary>
|
||||
/// <param name="fullReference">Tag reference to resolve the host for.</param>
|
||||
public string ResolveHost(string fullReference)
|
||||
{
|
||||
if (_tagsByName.TryGetValue(fullReference, out var tag))
|
||||
@@ -163,9 +171,14 @@ public sealed class ModbusDriver
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Gets the unique identifier of this driver instance.</summary>
|
||||
public string DriverInstanceId => _driverInstanceId;
|
||||
/// <summary>Gets the driver type name.</summary>
|
||||
public string DriverType => "Modbus";
|
||||
|
||||
/// <summary>Initializes the driver with the specified configuration JSON.</summary>
|
||||
/// <param name="driverConfigJson">JSON configuration string.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public async Task InitializeAsync(string driverConfigJson, CancellationToken cancellationToken)
|
||||
{
|
||||
WriteHealth(new DriverHealth(DriverState.Initializing, null, null));
|
||||
@@ -199,12 +212,17 @@ public sealed class ModbusDriver
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Reinitializes the driver with new configuration.</summary>
|
||||
/// <param name="driverConfigJson">New JSON configuration string.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public async Task ReinitializeAsync(string driverConfigJson, CancellationToken cancellationToken)
|
||||
{
|
||||
await ShutdownAsync(cancellationToken);
|
||||
await InitializeAsync(driverConfigJson, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>Shuts down the driver and releases resources.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public async Task ShutdownAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var lastRead = ReadHealth().LastSuccessfulRead;
|
||||
@@ -212,6 +230,7 @@ public sealed class ModbusDriver
|
||||
WriteHealth(new DriverHealth(DriverState.Unknown, lastRead, null));
|
||||
}
|
||||
|
||||
/// <summary>Gets the current driver health status.</summary>
|
||||
public DriverHealth GetHealth() => ReadHealth();
|
||||
|
||||
/// <summary>
|
||||
@@ -226,11 +245,17 @@ public sealed class ModbusDriver
|
||||
/// Driver.Modbus-003: barrier-protected publish of a new <c>_health</c> snapshot.
|
||||
/// </summary>
|
||||
private void WriteHealth(DriverHealth value) => Volatile.Write(ref _health, value);
|
||||
/// <summary>Gets the memory footprint of the driver.</summary>
|
||||
public long GetMemoryFootprint() => 0;
|
||||
/// <summary>Flushes optional caches to free memory.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public Task FlushOptionalCachesAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
// ---- 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>
|
||||
public Task DiscoverAsync(IAddressSpaceBuilder builder, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
@@ -252,6 +277,9 @@ public sealed class ModbusDriver
|
||||
|
||||
// ---- IReadable ----
|
||||
|
||||
/// <summary>Reads the specified tag references from the Modbus device.</summary>
|
||||
/// <param name="fullReferences">Tag references to read.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public async Task<IReadOnlyList<DataValueSnapshot>> ReadAsync(
|
||||
IReadOnlyList<string> fullReferences, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -558,6 +586,7 @@ public sealed class ModbusDriver
|
||||
/// each entry decides between bisection (multi-register + SplitPending) or straight
|
||||
/// retry (single-register or already-narrowed).
|
||||
/// </summary>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
internal async Task RunReprobeOnceForTestAsync(CancellationToken ct)
|
||||
{
|
||||
var transport = _transport ?? throw new InvalidOperationException("Transport not connected");
|
||||
@@ -891,6 +920,9 @@ public sealed class ModbusDriver
|
||||
|
||||
// ---- IWritable ----
|
||||
|
||||
/// <summary>Writes values to the specified tag references on the Modbus device.</summary>
|
||||
/// <param name="writes">Write requests to execute.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public async Task<IReadOnlyList<WriteResult>> WriteAsync(
|
||||
IReadOnlyList<WriteRequest> writes, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -1128,10 +1160,17 @@ public sealed class ModbusDriver
|
||||
|
||||
// ---- ISubscribable (polling overlay via shared engine) ----
|
||||
|
||||
/// <summary>Subscribes to value changes on the specified tag references.</summary>
|
||||
/// <param name="fullReferences">Tag references to subscribe to.</param>
|
||||
/// <param name="publishingInterval">Interval for publishing changes.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public Task<ISubscriptionHandle> SubscribeAsync(
|
||||
IReadOnlyList<string> fullReferences, TimeSpan publishingInterval, CancellationToken cancellationToken) =>
|
||||
Task.FromResult(_poll.Subscribe(fullReferences, publishingInterval));
|
||||
|
||||
/// <summary>Unsubscribes from value changes using the specified handle.</summary>
|
||||
/// <param name="handle">Subscription handle.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public Task UnsubscribeAsync(ISubscriptionHandle handle, CancellationToken cancellationToken)
|
||||
{
|
||||
_poll.Unsubscribe(handle);
|
||||
@@ -1140,6 +1179,7 @@ public sealed class ModbusDriver
|
||||
|
||||
// ---- IHostConnectivityProbe ----
|
||||
|
||||
/// <summary>Gets the current connectivity status for all hosts.</summary>
|
||||
public IReadOnlyList<HostConnectivityStatus> GetHostStatuses()
|
||||
{
|
||||
lock (_probeLock)
|
||||
@@ -1205,6 +1245,7 @@ public sealed class ModbusDriver
|
||||
/// types (Int32/Float32 = 2 regs, Int64/Float64 = 4 regs) and for strings (rounded up
|
||||
/// from 2 chars per register).
|
||||
/// </summary>
|
||||
/// <param name="tag">Tag definition to measure.</param>
|
||||
internal static ushort RegisterCount(ModbusTagDefinition tag) => tag.DataType switch
|
||||
{
|
||||
ModbusDataType.Int16 or ModbusDataType.UInt16 or ModbusDataType.BitInRegister or ModbusDataType.Bcd16 => 1,
|
||||
@@ -1262,6 +1303,9 @@ public sealed class ModbusDriver
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>Decodes a register value according to the tag's data type.</summary>
|
||||
/// <param name="data">Raw register bytes.</param>
|
||||
/// <param name="tag">Tag definition specifying the data type.</param>
|
||||
internal static object DecodeRegister(ReadOnlySpan<byte> data, ModbusTagDefinition tag)
|
||||
{
|
||||
switch (tag.DataType)
|
||||
@@ -1340,6 +1384,9 @@ public sealed class ModbusDriver
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Encodes a value into register bytes according to the tag's data type.</summary>
|
||||
/// <param name="value">Value to encode.</param>
|
||||
/// <param name="tag">Tag definition specifying the data type.</param>
|
||||
internal static byte[] EncodeRegister(object? value, ModbusTagDefinition tag)
|
||||
{
|
||||
switch (tag.DataType)
|
||||
@@ -1473,6 +1520,8 @@ public sealed class ModbusDriver
|
||||
/// the hardware sometimes produces garbage during transitions and silent non-BCD reads
|
||||
/// would quietly corrupt the caller's data.
|
||||
/// </summary>
|
||||
/// <param name="raw">Raw BCD value.</param>
|
||||
/// <param name="nibbles">Number of nibbles to decode.</param>
|
||||
internal static uint DecodeBcd(uint raw, int nibbles)
|
||||
{
|
||||
uint result = 0;
|
||||
@@ -1491,6 +1540,8 @@ public sealed class ModbusDriver
|
||||
/// Encode a decimal value as N-nibble BCD. Caller is responsible for range-checking
|
||||
/// against the nibble capacity (10^nibbles - 1).
|
||||
/// </summary>
|
||||
/// <param name="value">Decimal value to encode.</param>
|
||||
/// <param name="nibbles">Number of nibbles to encode.</param>
|
||||
internal static uint EncodeBcd(uint value, int nibbles)
|
||||
{
|
||||
uint result = 0;
|
||||
@@ -1524,6 +1575,7 @@ public sealed class ModbusDriver
|
||||
/// <c>docs/v2/dl205.md</c>, DL205/DL260 returns only codes 01-04 — no proprietary
|
||||
/// extensions.
|
||||
/// </summary>
|
||||
/// <param name="exceptionCode">Modbus exception code.</param>
|
||||
internal static uint MapModbusExceptionToStatus(byte exceptionCode) => exceptionCode switch
|
||||
{
|
||||
0x01 => StatusBadNotSupported, // Illegal Function — FC not in supported list
|
||||
@@ -1535,6 +1587,7 @@ public sealed class ModbusDriver
|
||||
_ => StatusBadInternalError,
|
||||
};
|
||||
|
||||
/// <summary>Releases resources used by the driver.</summary>
|
||||
public void Dispose() => DisposeAsync().AsTask().GetAwaiter().GetResult();
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -22,6 +22,8 @@ public static class ModbusDriverFactoryExtensions
|
||||
/// the driver runs with the null logger (existing tests and standalone callers stay
|
||||
/// unchanged).
|
||||
/// </summary>
|
||||
/// <param name="registry">The driver factory registry to register with.</param>
|
||||
/// <param name="loggerFactory">Optional logger factory for creating loggers per driver instance.</param>
|
||||
public static void Register(DriverFactoryRegistry registry, ILoggerFactory? loggerFactory = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(registry);
|
||||
@@ -29,10 +31,15 @@ public static class ModbusDriverFactoryExtensions
|
||||
}
|
||||
|
||||
/// <summary>Public for the Server-side bootstrapper + test consumers (Admin.Tests, etc.).</summary>
|
||||
/// <param name="driverInstanceId">The unique identifier for the driver instance.</param>
|
||||
/// <param name="driverConfigJson">The JSON configuration string for the driver.</param>
|
||||
public static ModbusDriver CreateInstance(string driverInstanceId, string driverConfigJson)
|
||||
=> CreateInstance(driverInstanceId, driverConfigJson, loggerFactory: null);
|
||||
|
||||
/// <summary>Logger-aware overload — used by <see cref="Register"/>'s closure when wired through DI.</summary>
|
||||
/// <param name="driverInstanceId">The unique identifier for the driver instance.</param>
|
||||
/// <param name="driverConfigJson">The JSON configuration string for the driver.</param>
|
||||
/// <param name="loggerFactory">Optional logger factory for creating loggers per driver instance.</param>
|
||||
public static ModbusDriver CreateInstance(string driverInstanceId, string driverConfigJson, ILoggerFactory? loggerFactory)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(driverInstanceId);
|
||||
@@ -205,48 +212,76 @@ public static class ModbusDriverFactoryExtensions
|
||||
|
||||
internal sealed class ModbusDriverConfigDto
|
||||
{
|
||||
/// <summary>Gets or sets the host address.</summary>
|
||||
public string? Host { get; init; }
|
||||
/// <summary>Gets or sets the port number.</summary>
|
||||
public int? Port { get; init; }
|
||||
/// <summary>Gets or sets the unit identifier.</summary>
|
||||
public byte? UnitId { get; init; }
|
||||
/// <summary>Gets or sets the timeout in milliseconds.</summary>
|
||||
public int? TimeoutMs { get; init; }
|
||||
/// <summary>Gets or sets the maximum number of registers per read operation.</summary>
|
||||
public ushort? MaxRegistersPerRead { get; init; }
|
||||
/// <summary>Gets or sets the maximum number of registers per write operation.</summary>
|
||||
public ushort? MaxRegistersPerWrite { get; init; }
|
||||
/// <summary>Gets or sets the maximum number of coils per read operation.</summary>
|
||||
public ushort? MaxCoilsPerRead { get; init; }
|
||||
/// <summary>Gets or sets a value indicating whether to use function code 15 for single coil writes.</summary>
|
||||
public bool? UseFC15ForSingleCoilWrites { get; init; }
|
||||
/// <summary>Gets or sets a value indicating whether to use function code 16 for single register writes.</summary>
|
||||
public bool? UseFC16ForSingleRegisterWrites { get; init; }
|
||||
/// <summary>Gets or sets a value indicating whether to disable function code 23.</summary>
|
||||
public bool? DisableFC23 { get; init; }
|
||||
/// <summary>Gets or sets a value indicating whether to write only on change.</summary>
|
||||
public bool? WriteOnChangeOnly { get; init; }
|
||||
/// <summary>Gets or sets the maximum gap between register addresses for coalescing.</summary>
|
||||
public ushort? MaxReadGap { get; init; }
|
||||
/// <summary>Gets or sets the Modbus family type.</summary>
|
||||
public string? Family { get; init; }
|
||||
/// <summary>Gets or sets the Melsec subfamily.</summary>
|
||||
public string? MelsecSubFamily { get; init; }
|
||||
/// <summary>Gets or sets the automatic prohibition reprobei interval in milliseconds.</summary>
|
||||
public int? AutoProhibitReprobeMs { get; init; }
|
||||
/// <summary>Gets or sets a value indicating whether automatic reconnection is enabled.</summary>
|
||||
public bool? AutoReconnect { get; init; }
|
||||
/// <summary>Gets or sets the collection of tag definitions.</summary>
|
||||
public List<ModbusTagDto>? Tags { get; init; }
|
||||
/// <summary>Gets or sets the probe configuration.</summary>
|
||||
public ModbusProbeDto? Probe { get; init; }
|
||||
|
||||
// #139 connection-layer knobs.
|
||||
/// <summary>Gets or sets the keep-alive configuration (connection-layer knob #139).</summary>
|
||||
public ModbusKeepAliveDto? KeepAlive { get; init; }
|
||||
/// <summary>Gets or sets the idle disconnect timeout in milliseconds.</summary>
|
||||
public int? IdleDisconnectMs { get; init; }
|
||||
/// <summary>Gets or sets the reconnect configuration.</summary>
|
||||
public ModbusReconnectDto? Reconnect { get; init; }
|
||||
}
|
||||
|
||||
internal sealed class ModbusKeepAliveDto
|
||||
{
|
||||
/// <summary>Gets or sets a value indicating whether keep-alive is enabled.</summary>
|
||||
public bool? Enabled { get; init; }
|
||||
/// <summary>Gets or sets the keep-alive time in milliseconds.</summary>
|
||||
public int? TimeMs { get; init; }
|
||||
/// <summary>Gets or sets the keep-alive interval in milliseconds.</summary>
|
||||
public int? IntervalMs { get; init; }
|
||||
/// <summary>Gets or sets the retry count.</summary>
|
||||
public int? RetryCount { get; init; }
|
||||
}
|
||||
|
||||
internal sealed class ModbusReconnectDto
|
||||
{
|
||||
/// <summary>Gets or sets the initial reconnect delay in milliseconds.</summary>
|
||||
public int? InitialDelayMs { get; init; }
|
||||
/// <summary>Gets or sets the maximum reconnect delay in milliseconds.</summary>
|
||||
public int? MaxDelayMs { get; init; }
|
||||
/// <summary>Gets or sets the backoff multiplier for exponential reconnect delays.</summary>
|
||||
public double? BackoffMultiplier { get; init; }
|
||||
}
|
||||
|
||||
internal sealed class ModbusTagDto
|
||||
{
|
||||
/// <summary>Gets or sets the tag name.</summary>
|
||||
public string? Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
@@ -257,25 +292,41 @@ public static class ModbusDriverFactoryExtensions
|
||||
/// </summary>
|
||||
public string? AddressString { get; init; }
|
||||
|
||||
/// <summary>Gets or sets the register region.</summary>
|
||||
public string? Region { get; init; }
|
||||
/// <summary>Gets or sets the starting address.</summary>
|
||||
public ushort? Address { get; init; }
|
||||
/// <summary>Gets or sets the data type.</summary>
|
||||
public string? DataType { get; init; }
|
||||
/// <summary>Gets or sets a value indicating whether the tag is writable.</summary>
|
||||
public bool? Writable { get; init; }
|
||||
/// <summary>Gets or sets the byte order.</summary>
|
||||
public string? ByteOrder { get; init; }
|
||||
/// <summary>Gets or sets the bit index for bit-level tags.</summary>
|
||||
public byte? BitIndex { get; init; }
|
||||
/// <summary>Gets or sets the string length for string-typed tags.</summary>
|
||||
public ushort? StringLength { get; init; }
|
||||
/// <summary>Gets or sets the byte order for string values.</summary>
|
||||
public string? StringByteOrder { get; init; }
|
||||
/// <summary>Gets or sets a value indicating whether writes are idempotent.</summary>
|
||||
public bool? WriteIdempotent { get; init; }
|
||||
/// <summary>Gets or sets the array count for array-typed tags.</summary>
|
||||
public int? ArrayCount { get; init; }
|
||||
/// <summary>Gets or sets the deadband for change detection.</summary>
|
||||
public double? Deadband { get; init; }
|
||||
/// <summary>Gets or sets the unit identifier for this tag.</summary>
|
||||
public byte? UnitId { get; init; }
|
||||
}
|
||||
|
||||
internal sealed class ModbusProbeDto
|
||||
{
|
||||
/// <summary>Gets or sets a value indicating whether probing is enabled.</summary>
|
||||
public bool? Enabled { get; init; }
|
||||
/// <summary>Gets or sets the probe interval in milliseconds.</summary>
|
||||
public int? IntervalMs { get; init; }
|
||||
/// <summary>Gets or sets the probe timeout in milliseconds.</summary>
|
||||
public int? TimeoutMs { get; init; }
|
||||
/// <summary>Gets or sets the probe register address.</summary>
|
||||
public ushort? ProbeAddress { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,13 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus;
|
||||
/// </summary>
|
||||
public sealed class ModbusDriverOptions
|
||||
{
|
||||
/// <summary>Gets the Modbus TCP server host address.</summary>
|
||||
public string Host { get; init; } = "127.0.0.1";
|
||||
/// <summary>Gets the Modbus TCP server port.</summary>
|
||||
public int Port { get; init; } = 502;
|
||||
/// <summary>Gets the Modbus unit ID (slave ID).</summary>
|
||||
public byte UnitId { get; init; } = 1;
|
||||
/// <summary>Gets the communication timeout duration.</summary>
|
||||
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2);
|
||||
|
||||
/// <summary>Pre-declared tag map. Modbus has no discovery protocol — the driver returns exactly these.</summary>
|
||||
@@ -177,6 +181,7 @@ public sealed class ModbusDriverOptions
|
||||
/// <summary>OS-level TCP keep-alive knobs. Set <see cref="Enabled"/>=false to skip entirely.</summary>
|
||||
public sealed class ModbusKeepAliveOptions
|
||||
{
|
||||
/// <summary>Gets a value indicating whether keep-alive is enabled.</summary>
|
||||
public bool Enabled { get; init; } = true;
|
||||
/// <summary>Idle time before the first probe (seconds, mapped to <c>TcpKeepAliveTime</c>).</summary>
|
||||
public TimeSpan Time { get; init; } = TimeSpan.FromSeconds(30);
|
||||
@@ -199,8 +204,11 @@ public sealed class ModbusReconnectOptions
|
||||
|
||||
public sealed class ModbusProbeOptions
|
||||
{
|
||||
/// <summary>Gets a value indicating whether probing is enabled.</summary>
|
||||
public bool Enabled { get; init; } = true;
|
||||
/// <summary>Gets the interval between probe requests.</summary>
|
||||
public TimeSpan Interval { get; init; } = TimeSpan.FromSeconds(5);
|
||||
/// <summary>Gets the probe request timeout.</summary>
|
||||
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2);
|
||||
/// <summary>Register to read for the probe. Zero is usually safe; override for PLCs that lock register 0.</summary>
|
||||
public ushort ProbeAddress { get; init; } = 0;
|
||||
|
||||
@@ -40,6 +40,14 @@ public sealed class ModbusTcpTransport : IModbusTransport
|
||||
private bool _disposed;
|
||||
private DateTime _lastSuccessUtc = DateTime.UtcNow;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="ModbusTcpTransport"/> class.</summary>
|
||||
/// <param name="host">The host address or hostname of the Modbus server.</param>
|
||||
/// <param name="port">The TCP port of the Modbus server.</param>
|
||||
/// <param name="timeout">The timeout for socket operations.</param>
|
||||
/// <param name="autoReconnect">Whether to automatically reconnect on socket failures.</param>
|
||||
/// <param name="keepAlive">Optional keep-alive configuration for the socket.</param>
|
||||
/// <param name="idleDisconnect">Optional duration after which an idle socket is disconnected.</param>
|
||||
/// <param name="reconnect">Optional reconnect backoff configuration.</param>
|
||||
public ModbusTcpTransport(
|
||||
string host, int port, TimeSpan timeout, bool autoReconnect = true,
|
||||
ModbusKeepAliveOptions? keepAlive = null,
|
||||
@@ -55,6 +63,8 @@ public sealed class ModbusTcpTransport : IModbusTransport
|
||||
_reconnect = reconnect ?? new ModbusReconnectOptions();
|
||||
}
|
||||
|
||||
/// <summary>Connects to the Modbus TCP server with IPv4 preference.</summary>
|
||||
/// <param name="ct">The cancellation token for the operation.</param>
|
||||
public async Task ConnectAsync(CancellationToken ct)
|
||||
{
|
||||
// Resolve the host explicitly + prefer IPv4. .NET's TcpClient default-constructor is
|
||||
@@ -110,12 +120,17 @@ public sealed class ModbusTcpTransport : IModbusTransport
|
||||
/// minimum of 1 — protects callers from the int-cast truncation that turned 500 ms
|
||||
/// keep-alive timing into "use the default" on most OSes.
|
||||
/// </summary>
|
||||
/// <param name="ts">The timespan to clamp to whole seconds.</param>
|
||||
internal static int ClampToWholeSeconds(TimeSpan ts)
|
||||
{
|
||||
var seconds = (int)Math.Ceiling(ts.TotalSeconds);
|
||||
return seconds < 1 ? 1 : seconds;
|
||||
}
|
||||
|
||||
/// <summary>Sends a Modbus PDU and returns the response, with automatic retry on socket failure.</summary>
|
||||
/// <param name="unitId">The Modbus unit/slave ID.</param>
|
||||
/// <param name="pdu">The protocol data unit to send.</param>
|
||||
/// <param name="ct">The cancellation token for the operation.</param>
|
||||
public async Task<byte[]> SendAsync(byte unitId, byte[] pdu, CancellationToken ct)
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(ModbusTcpTransport));
|
||||
@@ -268,6 +283,7 @@ public sealed class ModbusTcpTransport : IModbusTransport
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Asynchronously disposes the transport and underlying socket resources.</summary>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
Reference in New Issue
Block a user