@@ -181,6 +181,18 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId)
|
||||
// CPUs negotiate 240 bytes; CPUs running the extended PDU advertise 480 or 960.
|
||||
_negotiatedPduSize = plc.MaxPDUSize;
|
||||
|
||||
// PR-S7-C5 — pre-flight PUT/GET enablement probe. After a clean OpenAsync,
|
||||
// issue a tiny 2-byte read against Probe.ProbeAddress (default MW0). Hardened
|
||||
// S7-1200 / S7-1500 CPUs that have PUT/GET communication disabled in TIA
|
||||
// Portal accept the COTP/S7comm handshake but reject every read PDU with an
|
||||
// S7-level "function not allowed" error (D6 05 / 85 00 family); S7netplus
|
||||
// surfaces that as PlcException with ErrorCode in {WrongCPU_Type, ReadData}.
|
||||
// Surface the typed exception now so operators see the configuration-fix
|
||||
// hint at Init time, not on first per-tag read. Skipping the probe is opt-in
|
||||
// via SkipPreflight or by setting ProbeAddress = null for sites without a
|
||||
// wired fingerprint address.
|
||||
await RunPreflightAsync(plc, cts.Token).ConfigureAwait(false);
|
||||
|
||||
_health = new DriverHealth(DriverState.Healthy, DateTime.UtcNow, null, BuildDiagnostics());
|
||||
|
||||
// Kick off the probe loop once the connection is up. Initial HostState stays
|
||||
@@ -894,6 +906,61 @@ public sealed class S7Driver(S7DriverOptions options, string driverInstanceId)
|
||||
private global::S7.Net.Plc RequirePlc() =>
|
||||
Plc ?? throw new InvalidOperationException("S7Driver not initialized");
|
||||
|
||||
/// <summary>
|
||||
/// PR-S7-C5 — issue the post-<c>OpenAsync</c> pre-flight probe read against
|
||||
/// <see cref="S7ProbeOptions.ProbeAddress"/> and translate a "PUT/GET disabled"
|
||||
/// wire response into a typed <see cref="S7PutGetDisabledException"/>. Skips the
|
||||
/// probe entirely when <see cref="S7ProbeOptions.SkipPreflight"/> is set or when
|
||||
/// <see cref="S7ProbeOptions.ProbeAddress"/> is null/empty (sites that haven't
|
||||
/// wired a fingerprint address). Other <see cref="global::S7.Net.PlcException"/>
|
||||
/// surfaces (transport drop, IP unavailable, bad var format) rethrow unchanged so
|
||||
/// the caller doesn't lose the original failure shape.
|
||||
/// </summary>
|
||||
private async Task RunPreflightAsync(global::S7.Net.Plc plc, CancellationToken ct)
|
||||
{
|
||||
if (_options.Probe.SkipPreflight) return;
|
||||
var probeAddr = _options.Probe.ProbeAddress;
|
||||
if (string.IsNullOrWhiteSpace(probeAddr)) return;
|
||||
|
||||
S7ParsedAddress parsed;
|
||||
try
|
||||
{
|
||||
parsed = S7AddressParser.Parse(probeAddr, _options.CpuType);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
// Bad probe address is a config bug — let the FormatException bubble out so
|
||||
// the caller's DriverState.Faulted message names the offending address.
|
||||
throw;
|
||||
}
|
||||
|
||||
// 2-byte read covers MW0 (the default), DBn.DBW0 fingerprints, and any other
|
||||
// word-shaped probe address. Bit-addressed probes round up to 1 byte; bytes
|
||||
// float to 2 to match the typical "fingerprint" word convention.
|
||||
int byteCount = parsed.Size switch
|
||||
{
|
||||
S7Size.Bit => 1,
|
||||
S7Size.Byte => 1,
|
||||
S7Size.Word => 2,
|
||||
S7Size.DWord => 4,
|
||||
S7Size.LWord => 8,
|
||||
_ => 2,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
await plc.ReadBytesAsync(MapArea(parsed.Area), parsed.DbNumber, parsed.ByteOffset, byteCount, ct)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (global::S7.Net.PlcException pex) when (S7PreflightClassifier.IsPutGetDisabled(pex))
|
||||
{
|
||||
// Map the S7-level "function not allowed" rejection into our typed exception
|
||||
// so the operator sees the TIA-Portal fix path instead of a generic
|
||||
// PlcException stack trace. Inner exception preserved for diagnostics.
|
||||
throw new S7PutGetDisabledException(probeAddr!, pex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct the underlying S7netplus <see cref="Plc"/> honouring
|
||||
/// <see cref="S7DriverOptions.TsapMode"/>, <see cref="S7DriverOptions.LocalTsap"/>,
|
||||
|
||||
Reference in New Issue
Block a user