fix(s7): numeric TSAP loads in form, shared TSAP conflict error, Test Connect read detects PUT/GET disabled, pin Int32 ms rounding (#632 #638)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -2335,7 +2335,7 @@ public sealed class S7Driver
|
||||
/// no typed error code for it, so the inner message is the only discriminator
|
||||
/// S7.Net exposes.
|
||||
/// </summary>
|
||||
private static bool IsAccessDenied(PlcException pex)
|
||||
internal static bool IsAccessDenied(PlcException pex)
|
||||
{
|
||||
for (Exception? e = pex; e is not null; e = e.InnerException)
|
||||
{
|
||||
|
||||
@@ -96,8 +96,7 @@ public static class S7DriverFactoryExtensions
|
||||
: ParseEnum<S7ConnectionType>(dto.ConnectionType, driverInstanceId, "ConnectionType");
|
||||
if (remoteTsap is not null && connectionType is not null)
|
||||
throw new InvalidOperationException(
|
||||
$"S7 driver config for '{driverInstanceId}' sets both RemoteTsap and ConnectionType; " +
|
||||
"ConnectionType only fills the first byte of the slot-derived remote TSAP, so choose one.");
|
||||
$"S7 driver config for '{driverInstanceId}' {S7DriverOptions.TsapConflictDetail}");
|
||||
|
||||
return new S7DriverOptions
|
||||
{
|
||||
|
||||
@@ -80,7 +80,15 @@ public sealed class S7DriverProbe : IDriverProbe
|
||||
sw.Stop();
|
||||
|
||||
if (plc.IsConnected)
|
||||
{
|
||||
// A handshake alone passes on a CPU with PUT/GET disabled (S7-1200/1500 default) — every
|
||||
// later read would fail. One minimal read proves data access.
|
||||
if (await CheckDataAccessAsync(
|
||||
t => plc.ReadBytesAsync(global::S7.Net.DataType.Memory, 0, 0, 2, t), handshakeCts.Token)
|
||||
is { } denied)
|
||||
return denied;
|
||||
return new(true, $"S7 connected (CPU {opts.CpuType}, PDU {plc.MaxPDUSize} bytes)", sw.Elapsed);
|
||||
}
|
||||
|
||||
return new(false, $"Reachable at {host}:{port} but S7 handshake failed: not connected", null);
|
||||
}
|
||||
@@ -104,6 +112,38 @@ public sealed class S7DriverProbe : IDriverProbe
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs one minimal read after the handshake. Returns null when data access works — including when the
|
||||
/// CPU merely rejects that particular address (MB0 not present / out of range: the connection is fine).
|
||||
/// PUT/GET-disabled yields the actionable message; any other fault is a probe failure with its message.
|
||||
/// </summary>
|
||||
/// <param name="read">The minimal read.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>A failure result, or null on success.</returns>
|
||||
internal static async Task<DriverProbeResult?> CheckDataAccessAsync(Func<CancellationToken, Task<byte[]>> read, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
await read(ct).ConfigureAwait(false);
|
||||
return null;
|
||||
}
|
||||
catch (OperationCanceledException) { throw; }
|
||||
catch (PlcException pex) when (S7Driver.IsAccessDenied(pex))
|
||||
{
|
||||
return new(false,
|
||||
"S7 connected but data access is denied — enable PUT/GET communication in TIA Portal " +
|
||||
$"(Protection & Security) for this CPU. PLC reported: {pex.Message}", null);
|
||||
}
|
||||
catch (PlcException pex) when (pex.ErrorCode == ErrorCode.ReadData)
|
||||
{
|
||||
return null; // address not available on this CPU — not a connection problem
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new(false, $"S7 connected but a test read failed: {ex.Message}", null);
|
||||
}
|
||||
}
|
||||
|
||||
private static (string host, int port) ExtractTarget(S7DriverOptions opts)
|
||||
=> (opts.Host, opts.Port);
|
||||
}
|
||||
|
||||
@@ -287,6 +287,8 @@ internal static class S7ValueCodecs
|
||||
}
|
||||
}
|
||||
|
||||
// Fractional input is rounded (Convert.ToInt64, banker's), matching the driver's existing Int32 write
|
||||
// path (BoxValueForWrite → Convert.ToInt32). "Refused, not rounded" applies to S5TIME's time-base grid.
|
||||
private static long ToMilliseconds(object? value, string type)
|
||||
{
|
||||
try { return value is TimeSpan ts ? (long)ts.TotalMilliseconds : Convert.ToInt64(value, System.Globalization.CultureInfo.InvariantCulture); }
|
||||
|
||||
Reference in New Issue
Block a user