Auto: s7-c2 — TSAP / Connection Type selector

Closes #295
This commit is contained in:
Joseph Doherty
2026-04-26 00:49:10 -04:00
parent bcf83bf39b
commit 3b98e4d366
7 changed files with 513 additions and 1 deletions

View File

@@ -81,6 +81,111 @@ public sealed class S7DriverOptions
/// opt out of merging regardless of this knob.
/// </remarks>
public int BlockCoalescingGapBytes { get; init; } = S7BlockCoalescingPlanner.DefaultGapMergeBytes;
/// <summary>
/// ISO-on-TCP / S7comm "connection class" selector. Hardened S7-1500 CPUs and some
/// ET 200SP / S7-1200 firmware variants reject the default <b>PG-class</b> TSAP that
/// S7netplus picks under <see cref="TsapMode.Auto"/> and require an <b>OP-class</b>
/// or <b>S7-Basic-class</b> TSAP instead. Picking the wrong class produces the same
/// failure shape as picking the wrong slot — connection refused at COTP handshake
/// time, before any S7comm PDU is sent. See <c>docs/v2/s7.md</c> "TSAP / Connection
/// Type" section for the raw-TSAP byte table and the hardened-CPU motivation.
/// </summary>
/// <remarks>
/// <para>
/// <see cref="TsapMode.Auto"/> preserves existing behaviour — S7netplus picks
/// the TSAP pair from the configured <see cref="CpuType"/> via its
/// <c>TsapPair.GetDefaultTsapPair</c>. Use <see cref="TsapMode.Pg"/> /
/// <see cref="TsapMode.Op"/> / <see cref="TsapMode.S7Basic"/> to force a specific
/// class, or <see cref="TsapMode.Other"/> together with <see cref="LocalTsap"/>
/// and <see cref="RemoteTsap"/> for a fully-manual escape hatch. Explicit
/// <see cref="LocalTsap"/> / <see cref="RemoteTsap"/> overrides win even under
/// <see cref="TsapMode.Pg"/> / <see cref="TsapMode.Op"/> / <see cref="TsapMode.S7Basic"/>.
/// </para>
/// </remarks>
public TsapMode TsapMode { get; init; } = TsapMode.Auto;
/// <summary>
/// Optional fully-manual local TSAP override (16-bit big-endian word — high byte =
/// class selector, low byte = caller-defined). Required (together with
/// <see cref="RemoteTsap"/>) when <see cref="TsapMode"/> is
/// <see cref="TsapMode.Other"/>. Wins over the class-derived default under any
/// non-<see cref="TsapMode.Auto"/> mode.
/// </summary>
public ushort? LocalTsap { get; init; }
/// <summary>
/// Optional fully-manual remote TSAP override (16-bit big-endian word — high byte =
/// class selector, low byte = <c>(rack &lt;&lt; 5) | slot</c> per the S7 spec).
/// Required (together with <see cref="LocalTsap"/>) when <see cref="TsapMode"/> is
/// <see cref="TsapMode.Other"/>. Wins over the class-derived default under any
/// non-<see cref="TsapMode.Auto"/> mode.
/// </summary>
public ushort? RemoteTsap { get; init; }
}
/// <summary>
/// ISO-on-TCP / S7comm connection class. Picks the high byte of the TSAP pair used
/// during COTP handshake. See <c>docs/v2/s7.md</c> "TSAP / Connection Type" section
/// for the raw-byte table and the hardened-CPU motivation.
/// </summary>
public enum TsapMode
{
/// <summary>S7netplus picks the TSAP pair from <see cref="S7DriverOptions.CpuType"/>. Existing behaviour.</summary>
Auto,
/// <summary>PG class — high byte 0x01. Default for development laptops / TIA Portal.</summary>
Pg,
/// <summary>OP class — high byte 0x02. Required by some hardened S7-1500 / ET 200SP deployments.</summary>
Op,
/// <summary>S7-Basic class — high byte 0x03. Used by S7-Basic clients (e.g. some HMI panels and the WinCC BasicPanel SDK).</summary>
S7Basic,
/// <summary>Caller-supplied <see cref="S7DriverOptions.LocalTsap"/> + <see cref="S7DriverOptions.RemoteTsap"/>. Both must be set or driver init throws.</summary>
Other,
}
/// <summary>
/// Raw-TSAP byte constants per ISO-on-TCP / S7comm connection class. The "high byte"
/// is the class selector documented in the Siemens function manual; the local TSAP's
/// low byte is conventionally 0x00 (caller / unprivileged) and the remote TSAP's low
/// byte is <c>(rack &lt;&lt; 5) | slot</c> per the spec. Mirrored in
/// <c>docs/v2/s7.md</c> "TSAP / Connection Type" table.
/// </summary>
public static class S7TsapDefaults
{
/// <summary>PG-class high byte = 0x01.</summary>
public const byte PgClassHighByte = 0x01;
/// <summary>OP-class high byte = 0x02.</summary>
public const byte OpClassHighByte = 0x02;
/// <summary>S7-Basic-class high byte = 0x03.</summary>
public const byte S7BasicClassHighByte = 0x03;
/// <summary>Build the local TSAP (16-bit BE): <c>class &lt;&lt; 8 | 0x00</c>.</summary>
public static ushort BuildLocalTsap(byte classHighByte) => (ushort)(classHighByte << 8);
/// <summary>
/// Build the remote TSAP (16-bit BE): <c>class &lt;&lt; 8 | ((rack &amp; 0x07) &lt;&lt; 5 | (slot &amp; 0x1F))</c>.
/// Matches the convention used by S7netplus's <c>TsapPair.GetDefaultTsapPair</c> for the remote endpoint.
/// </summary>
public static ushort BuildRemoteTsap(byte classHighByte, int rack, int slot)
{
if (rack < 0 || rack > 15)
throw new ArgumentOutOfRangeException(nameof(rack), rack, "rack must be 0..15");
if (slot < 0 || slot > 31)
throw new ArgumentOutOfRangeException(nameof(slot), slot, "slot must be 0..31");
return (ushort)((classHighByte << 8) | ((rack & 0x07) << 5) | (slot & 0x1F));
}
/// <summary>Pick the class high-byte for a non-<see cref="TsapMode.Auto"/> / non-<see cref="TsapMode.Other"/> mode.</summary>
public static byte HighByteFor(TsapMode mode) => mode switch
{
TsapMode.Pg => PgClassHighByte,
TsapMode.Op => OpClassHighByte,
TsapMode.S7Basic => S7BasicClassHighByte,
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode,
"HighByteFor only handles Pg / Op / S7Basic; Auto and Other are caller-handled"),
};
}
public sealed class S7ProbeOptions