using S7NetErrorCode = global::S7.Net.ErrorCode; using S7NetPlcException = global::S7.Net.PlcException; namespace ZB.MOM.WW.OtOpcUa.Driver.S7; /// /// Thrown by when the post-OpenAsync /// 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 /// BadDeviceFailure. See docs/v2/s7.md "Pre-flight PUT/GET enablement" /// section for the full rationale. /// public sealed class S7PutGetDisabledException : Exception { /// The probe address that triggered the typed classification (e.g. MW0). public string ProbeAddress { get; } /// /// Construct a typed exception. is the raw /// from S7netplus that /// classified positive. /// 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)."; } /// /// Classifies an 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 /// ModbusPreflight pattern from the Modbus driver. /// public static class S7PreflightClassifier { /// /// Returns true when the exception's ErrorCode 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 /// (S7.Net's primary classification /// when the response framing isn't a valid PDU because the CPU rejected the /// request) and (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). /// /// /// /// 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 /// on the response path or /// 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. /// /// /// A non-matching (e.g. /// , /// ) means "transport /// broke" — let the caller rethrow as-is so other failure shapes don't /// get silently masked as "PUT/GET disabled". /// /// public static bool IsPutGetDisabled(S7NetPlcException? exception) { if (exception is null) return false; return exception.ErrorCode is S7NetErrorCode.WrongCPU_Type or S7NetErrorCode.ReadData; } }