Auto: s7-c5 — pre-flight PUT/GET enablement test

Closes #298
This commit is contained in:
Joseph Doherty
2026-04-26 01:31:48 -04:00
parent 4bc8aa2478
commit 64a11ef285
9 changed files with 625 additions and 3 deletions

View File

@@ -768,6 +768,98 @@ is satisfied.
whether to invoke `OnDataChange`. The mailbox / PDU / coalescing path
is untouched.
## Pre-flight PUT/GET enablement
S7-1200 / S7-1500 CPUs ship with **PUT/GET communication disabled by
default**. The COTP / S7comm handshake itself succeeds against these
locked-down CPUs (you can `OpenAsync` / negotiate PDU size cleanly), so
the failure surfaces only on the *first* `Plc.ReadAsync` — at which
point the driver is already past `InitializeAsync`, has flipped to
`DriverState.Healthy`, and dependent code (subscriptions, Admin UI) is
binding against a connection it can't actually use. Operators see
`BadDeviceFailure` per tag instead of a single, actionable
configuration error.
PR-S7-C5 adds a **post-`OpenAsync` pre-flight probe**: a tiny 2-byte
read against `Probe.ProbeAddress` (default `MW0`). If the PLC rejects
that read with the wire-level "function not allowed in current
operating state" response (S7 error family `D6 05` / `85 00`),
S7netplus surfaces the rejection as `PlcException` with one of
`ErrorCode.WrongCPU_Type` (CPU drops the connection mid-response) or
`ErrorCode.ReadData` (CPU sends an S7-level error byte). The driver
classifies that pair as "PUT/GET disabled" and throws a typed
`S7PutGetDisabledException` from `InitializeAsync` so the operator sees
the TIA-Portal fix path immediately:
> 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.
`S7PreflightClassifier.IsPutGetDisabled(PlcException)` is the pure
function that decides whether a given `PlcException` qualifies; it
matches **only** `WrongCPU_Type` and `ReadData`. Other error codes
(`ConnectionError`, `IPAddressNotAvailable`, `WrongVarFormat`, …)
indicate transport / framing faults rather than PUT/GET gating, so the
driver re-throws the original `PlcException` unchanged and the existing
`DriverState.Faulted` path takes over with the original message.
### Knobs
Two opt-out knobs on `S7ProbeOptions`:
- `ProbeAddress` (`string?`, default `"MW0"`) — address probed by both
the background liveness loop and the pre-flight read. Set to `null`
(or empty string in JSON) to skip the pre-flight entirely. Useful
for sites where no fingerprint address has been wired and an arbitrary
read at `MW0` would itself be misleading.
- `SkipPreflight` (`bool`, default `false`) — opt out of the pre-flight
read while keeping the background probe. Init succeeds against a
PUT/GET-disabled CPU; per-tag reads still surface `BadDeviceFailure`
at runtime. Useful for staged deployments where the operator hasn't
enabled PUT/GET yet but wants the driver visible in the Admin UI.
### Why `MW0`?
The convention from `Driver.S7.Cli.md`'s `probe` command. `MW0` exists
on every S7 CPU regardless of project — Merker memory is universal —
so it's a safe default that doesn't require a per-site DB to be wired.
Sites with a dedicated fingerprint DB can override to e.g.
`DB1.DBW0`.
### JSON config example
```json
{
"Host": "10.0.0.50",
"Probe": {
"Enabled": true,
"IntervalMs": 5000,
"TimeoutMs": 2000,
"ProbeAddress": "DB1.DBW0",
"SkipPreflight": false
}
}
```
To skip the pre-flight (defer the check to first read):
```json
{
"Host": "10.0.0.50",
"Probe": { "SkipPreflight": true }
}
```
To skip the probe entirely (no pre-flight, no liveness loop):
```json
{
"Host": "10.0.0.50",
"Probe": { "Enabled": false, "ProbeAddress": "" }
}
```
## TSAP / Connection Type
S7comm runs on top of ISO-on-TCP (RFC 1006), and the COTP connection-request