84
src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7PutGetDisabledException.cs
Normal file
84
src/ZB.MOM.WW.OtOpcUa.Driver.S7/S7PutGetDisabledException.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using S7NetErrorCode = global::S7.Net.ErrorCode;
|
||||
using S7NetPlcException = global::S7.Net.PlcException;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.S7;
|
||||
|
||||
/// <summary>
|
||||
/// Thrown by <see cref="S7Driver.InitializeAsync"/> when the post-<c>OpenAsync</c>
|
||||
/// pre-flight probe receives a response that the driver classifies as
|
||||
/// "PUT/GET communication disabled on the PLC". Surfaces a typed exception so
|
||||
/// operators see the configuration-fix instructions immediately at init time
|
||||
/// instead of waiting for the first per-tag read to fail with
|
||||
/// <c>BadDeviceFailure</c>. See <c>docs/v2/s7.md</c> "Pre-flight PUT/GET enablement"
|
||||
/// section for the full rationale.
|
||||
/// </summary>
|
||||
public sealed class S7PutGetDisabledException : Exception
|
||||
{
|
||||
/// <summary>The probe address that triggered the typed classification (e.g. <c>MW0</c>).</summary>
|
||||
public string ProbeAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a typed exception. <paramref name="inner"/> is the raw
|
||||
/// <see cref="S7NetPlcException"/> from S7netplus that
|
||||
/// <see cref="S7PreflightClassifier.IsPutGetDisabled"/> classified positive.
|
||||
/// </summary>
|
||||
public S7PutGetDisabledException(string probeAddress, Exception? inner = null)
|
||||
: base(BuildMessage(probeAddress), inner)
|
||||
{
|
||||
ProbeAddress = probeAddress;
|
||||
}
|
||||
|
||||
internal static string BuildMessage(string probeAddress) =>
|
||||
"S7 pre-flight probe to '" + probeAddress + "' was rejected by the PLC. " +
|
||||
"PUT/GET communication is disabled on the PLC. " +
|
||||
"Enable it in TIA Portal: Device → Properties → Protection & Security → " +
|
||||
"Connection mechanisms → 'Permit access with PUT/GET communication from " +
|
||||
"remote partner'. Re-deploy the hardware config and restart the S7 driver. " +
|
||||
"Alternatively set 'Probe.SkipPreflight = true' to defer the check to runtime " +
|
||||
"(driver will still surface BadDeviceFailure on every read until PUT/GET is enabled).";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Classifies an <see cref="S7NetPlcException"/> coming back from the pre-flight
|
||||
/// probe read into "PUT/GET disabled" vs "everything else". Pulled out as a static
|
||||
/// helper so unit tests can drive every branch (matching ErrorCode, non-matching
|
||||
/// ErrorCode, null exception) without spinning up an S7 server. Mirrors the
|
||||
/// <c>ModbusPreflight</c> pattern from the Modbus driver.
|
||||
/// </summary>
|
||||
public static class S7PreflightClassifier
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns <c>true</c> when the exception's <c>ErrorCode</c> matches the
|
||||
/// S7.Net surface for "PLC refused the read" — which on hardened S7-1200 /
|
||||
/// S7-1500 firmware is the wire-level signal for PUT/GET disabled. We match
|
||||
/// <see cref="S7NetErrorCode.WrongCPU_Type"/> (S7.Net's primary classification
|
||||
/// when the response framing isn't a valid PDU because the CPU rejected the
|
||||
/// request) and <see cref="S7NetErrorCode.ReadData"/> (the generic
|
||||
/// "couldn't read" code S7.Net falls back to when the PLC sends an S7-level
|
||||
/// error byte instead of a normal response).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The S7-1200 / S7-1500 wire response when PUT/GET is disabled is an S7
|
||||
/// "Function not allowed in current protection level" header byte
|
||||
/// (0xD605 / 0x8500 family). S7netplus surfaces that as PlcException with
|
||||
/// <see cref="S7NetErrorCode.ReadData"/> on the response path or
|
||||
/// <see cref="S7NetErrorCode.WrongCPU_Type"/> when the CPU drops the
|
||||
/// connection before sending a valid response. Matching both ensures the
|
||||
/// driver flags the config bug regardless of which path the firmware takes.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// A non-matching <see cref="S7NetErrorCode"/> (e.g.
|
||||
/// <see cref="S7NetErrorCode.ConnectionError"/>,
|
||||
/// <see cref="S7NetErrorCode.IPAddressNotAvailable"/>) means "transport
|
||||
/// broke" — let the caller rethrow as-is so other failure shapes don't
|
||||
/// get silently masked as "PUT/GET disabled".
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static bool IsPutGetDisabled(S7NetPlcException? exception)
|
||||
{
|
||||
if (exception is null) return false;
|
||||
return exception.ErrorCode is S7NetErrorCode.WrongCPU_Type
|
||||
or S7NetErrorCode.ReadData;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user