From b217ca61ce73e4c62432c13c0b49460bb2c8d701 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sun, 26 Apr 2026 11:22:40 -0400 Subject: [PATCH] =?UTF-8?q?Auto:=20s7-f=20=E2=80=94=20Optimized=20DB=20/?= =?UTF-8?q?=20S7Plus=20decision=20(Track=201+3=20docs-only)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #304 --- docs/drivers/S7-Test-Fixture.md | 43 ++++++- docs/featuregaps.md | 20 ++++ docs/v2/s7.md | 191 +++++++++++++++++++++++++++++++- 3 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 docs/featuregaps.md diff --git a/docs/drivers/S7-Test-Fixture.md b/docs/drivers/S7-Test-Fixture.md index 914fdd6..d88825f 100644 --- a/docs/drivers/S7-Test-Fixture.md +++ b/docs/drivers/S7-Test-Fixture.md @@ -88,6 +88,18 @@ real PLC latency is not exercised. S7-1200 vs S7-1500 vs S7-300/400 connection semantics (PG vs OP vs S7-Basic) not differentiated at test time. +**Optimized DB / S7Plus** is the variant-shaped gap with the biggest field +impact. snap7 happens to behave like a classic-S7comm-only PLC, so the +integration suite cannot reproduce the shape that an S7-1500 with default +"Optimized block access" checked would return (`BadDeviceFailure` on every +absolute-offset read). The decision is documented at +[`docs/v2/s7.md` § Optimized DB constraint (S7Plus)](../v2/s7.md#optimized-db-constraint-s7plus) +and tracked in [`docs/featuregaps.md`](../featuregaps.md) row #1; the +project ships **Track 1** (operator unchecks Optimized block access in TIA +Portal) and **Track 3** (bridge via the `OpcUaClient` driver against the +CPU's onboard OPC UA server). A custom S7Plus implementation is out of +scope. + ### 5. Data types beyond the scalars `STRING` with length-prefix quirks, `DTL` / `DATE_AND_TIME`, arrays of @@ -162,7 +174,36 @@ configured, which is parked as a follow-up. runner with the lab rig executes. The classifier branch (`S7PreflightClassifier.IsPutGetDisabled`) is unit-tested without a network in `S7PreflightTests.Classifier_matches_only_PUT_GET_disabled_error_codes`. -5. **PR-S7-E1 — live SZL test against a real S7-1500.** snap7 doesn't +5. **Live-firmware Optimized-block-access toggle (PR-S7-F / [#304](https://github.com/dohertj2/lmxopcua/issues/304)).** + snap7 happens to behave like a classic-S7comm CPU, so the integration + profile cannot reproduce the failure that a default new TIA Portal V14+ + project produces (`BadDeviceFailure` on `DB1.DBW0` against an Optimized + DB). A manual smoke test on the lab rig, gated behind `--with-real-plc`, + would close that loop. Suggested checklist on a real S7-1500 V2.5+: + 1. Create `DB1` in TIA Portal with three INT members at offsets 0, 2, 4. + Leave **Optimized block access checked** (the default). + 2. Compile + download to the PLC. + 3. Drive the OtOpcUa S7 driver against `DB1.DBW0` — assert that the read + returns `BadDeviceFailure` (the Track-1-not-applied symptom). This is + the failure shape the docs warn about. + 4. Open `DB1`'s properties → **uncheck Optimized block access** → + compile → download. Re-run the read; assert it returns the seeded + INT value at offset 0. (Track 1 verified end-to-end.) + 5. **Track 3 verification (separate run on the same rig):** with + Optimized access re-enabled on `DB1`, activate the CPU's onboard + OPC UA server in TIA Portal, expose `DB1.` through a + Server interface, register an `OpcUaClient` driver against + `opc.tcp://:4840`, and assert the symbolic read returns the + same seeded value. This proves the bridge path against a real + Optimized DB without the operator having to disable Optimized + access. + + The test must stay manual: TIA Portal compile + download cannot be + automated from CI without a Siemens engineering toolchain license, and + download-with-CPU-stop is destructive on a shared lab rig. Document + results inline in PR descriptions when the rig is available. + +6. **PR-S7-E1 — live SZL test against a real S7-1500.** snap7 doesn't implement SZL at all, and S7netplus 0.20 doesn't expose a public `ReadSzlAsync`, so the `@System.*` virtual address surface currently answers `BadNotSupported` against every backend. The parser diff --git a/docs/featuregaps.md b/docs/featuregaps.md new file mode 100644 index 0000000..d7312ab --- /dev/null +++ b/docs/featuregaps.md @@ -0,0 +1,20 @@ +# Feature gaps — driver-side limitations and decisions + +Cross-driver registry of known capability gaps, the workaround we ship, and +whether the gap is on the roadmap. Each row links to the driver-specific +deep-dive document. Closed entries stay in the table for traceability — they +are not deleted, only marked. + +| # | Driver | Gap | Status | Workaround / decision | Roadmap | Reference | +|---|--------|-----|--------|-----------------------|---------|-----------| +| 1 | S7 | **Optimized DB / S7Plus** — S7netplus speaks classic S7comm only and cannot read S7-1200 / S7-1500 DBs that have "Optimized block access" checked (the TIA Portal V14+ default). Absolute-offset reads against an Optimized DB return `BadDeviceFailure`. | **Decided — Track 1 + Track 3 (closed by [#304](https://github.com/dohertj2/lmxopcua/issues/304))** | **Track 1 (docs):** operators uncheck "Optimized block access" in TIA Portal on every DB the driver reads, recompile, and download. **Track 3 (bridge):** for shops that won't or can't disable Optimized access, run an `OpcUaClient` driver instance against the S7-1500 V2.5+ CPU's onboard OPC UA server (Siemens runtime OPC UA license required). | **Track 2 (custom S7Plus library) is out of scope** unless a customer funds the ≥4-week initial implementation plus ongoing protocol-revision maintenance. Sharp7 / Snap7Net don't help — they are also classic-S7comm-only. | [`docs/v2/s7.md` § Optimized DB constraint](v2/s7.md#optimized-db-constraint-s7plus) · [`docs/drivers/OpcUaClient.md`](drivers/OpcUaClient.md) | + +## How to read this table + +- **Status** is one of `Open` (work pending), `Decided` (architectural + decision made; docs reflect it; no code change planned), or + `Closed` (delivered). +- **Roadmap** captures whether the gap is funded for the next phase. A blank + cell means "no roadmap; doc-only outcome." +- The numeric **#** is stable — new rows append at the bottom and keep their + number across deletions/edits so cross-references survive. diff --git a/docs/v2/s7.md b/docs/v2/s7.md index 448439f..cb65d0a 100644 --- a/docs/v2/s7.md +++ b/docs/v2/s7.md @@ -1,5 +1,190 @@ # Siemens SIMATIC S7 (S7-1200 / S7-1500 / S7-300 / S7-400 / ET 200SP) — Modbus TCP quirks +> **Read first: [Optimized DB constraint (S7Plus)](#optimized-db-constraint-s7plus).** +> S7netplus, the wire library this driver is built on, speaks classic S7comm +> only — it cannot read Optimized-block-access DBs on S7-1200 / S7-1500. That +> is the default in TIA Portal V14+ for new projects. If you skip the section +> below, every absolute-offset read against a freshly-created S7-1500 project +> will return `BadDeviceFailure`. + +## Optimized DB constraint (S7Plus) + +### Symptom + +Against a default new S7-1500 TIA Portal project, an absolute-offset read like +`DB1.DBW0` issued by the OtOpcUa S7 driver returns `BadDeviceFailure` (the +S7netplus `PlcException` surfaces as `ErrorCode.WrongVarFormat` / +`ErrorCode.ReadData` depending on firmware revision). No bytes are returned; +the read never reaches the user data; the failure is identical whether +PUT/GET is enabled or not. + +### Why + +The OtOpcUa S7 driver is built on +[**S7netplus**](https://github.com/S7NetPlus/s7netplus), which implements +**classic S7comm** only — the protocol historically used by S7-300 / S7-400 +and the legacy "compatibility" path on S7-1200 / S7-1500. Classic S7comm +addresses DB contents by **absolute byte offset**: `DB1.DBW0` literally means +"give me 2 bytes starting at byte 0 of DB number 1". This works as long as +the byte offsets in the program match the byte offsets on the wire. + +S7-1200 V4 and S7-1500 introduced **Optimized block access**. When checked, +the TIA Portal compiler is free to **reorder DB members**, insert padding for +alignment, and store members in CPU-internal memory that the absolute-offset +read protocol cannot reach. There are no fixed byte offsets to address — the +only way to read an Optimized DB is by **symbolic name**, which requires +**S7Plus** (the post-2014 protocol Siemens uses for TIA-Portal-aware tooling +and OPC UA gateways). + +S7Plus is undocumented by Siemens. A community Wireshark dissector exists +(`s7comm-plus`), but no production-ready open-source library implements the +write/subscribe surface end-to-end. **S7netplus does not, and is not on a +roadmap to, support S7Plus.** Snap7 v2 / Snap7Net and the various Sharp7 +forks are also classic-S7comm-only. + +### Default to know about + +In **TIA Portal V14 and newer, "Optimized block access" is checked by default +on every newly-created DB**. A customer who clicks "Add new block → Data +block → OK" on a fresh S7-1500 project gets an Optimized DB. The driver +cannot read it. + +### Supported workarounds + +The OtOpcUa project supports two workarounds. Pick one per deployment. + +#### Track 1 — Disable Optimized block access in TIA Portal + +Per DB the driver reads: + +1. In TIA Portal, open the project tree → `` → **Program blocks** → + right-click the DB → **Properties**. +2. In the **Attributes** tab, **uncheck "Optimized block access"**. +3. **Compile** the program. +4. **Download** to the PLC (download the changed block; the CPU will go into + STOP if the DB layout changed and download-without-reinitialize is + refused — schedule a maintenance window). + +After this, `DB1.DBW0` and friends address absolute byte offsets again and +the OtOpcUa S7 driver reads through unmodified. + +**Trade-off:** Optimized DBs are slightly faster for *the PLC program +itself* to access (better alignment, sometimes better cache behaviour) and +let the compiler add/remove DB members without renumbering offsets in user +code. Disabling Optimized access trades a tiny amount of CPU-side +performance and a layout-stability guarantee for absolute-offset wire +addressability. For DBs that exist only as a Modbus / S7comm gateway buffer +(common pattern), there is no real downside. + +This is the same prerequisite called out in +["Optimized block access — must be off"](#optimized-block-access--must-be-off) +and ["Address / DB Mapping → MB_HOLD_REG"](#address--db-mapping) for the +Modbus-TCP path; the constraint is the same and stems from the same +absolute-offset-only assumption. + +#### Track 3 — Bridge via the OpcUaClient driver against the CPU's onboard OPC UA server + +S7-1500 firmware **V2.5 and later** ship with an **integrated OPC UA +server** running on the CPU's PROFINET port (default port 4840). Once +enabled in TIA Portal it exposes the entire symbol table — including +Optimized DBs — through standard OPC UA, by symbolic name. There is no S7 +protocol involved at all from the OtOpcUa side. + +Configure the bridge once: + +1. **TIA Portal side**: + - Open the CPU's properties → **OPC UA** → **General** → check + **Activate OPC UA server**. + - Set the server port (default 4840) and security policy. For a quick + bring-up, allow `None` + `UserName` and create a server certificate; + for production, use Basic256Sha256 with a CA-issued cert. + - Under **OPC UA** → **Server interfaces**, expose the symbols/tags the + OtOpcUa side should see. (Whole-symbol-table exposure works; a + curated server interface is more secure and faster.) + - Compile and download. + - Note: this requires a **runtime OPC UA license on the CPU** + (Siemens SIMATIC NET OPC UA server license, typically activated via + SIMATIC SUM). The license is per CPU, not per client. + +2. **OtOpcUa side** — register an `OpcUaClient` driver instance pointing + at the CPU. Minimal `DriverConfig` JSON: + + ```json + { + "Driver": "OpcUaClient", + "Name": "PLC1500_Onboard", + "Options": { + "EndpointUrl": "opc.tcp://10.0.0.42:4840", + "SecurityMode": "SignAndEncrypt", + "SecurityPolicy": "Basic256Sha256", + "UserName": "OtOpcUa", + "Password": "", + "WatchModelChanges": true + } + } + ``` + + The driver handles browse, read, write, and subscriptions through the + CPU's symbolic name space. Optimized DBs Just Work — the CPU resolves + names internally, so the wire never sees a byte offset. + +See [`docs/drivers/OpcUaClient.md`](../drivers/OpcUaClient.md) for the full +configuration surface (reverse connect, model-change re-import, failover, +aggregate functions, redundancy via `ServerUriArray`, etc.). + +**When to use Track 3 over Track 1**: + +- The DB layout is owned by an upstream Siemens engineering team that won't + disable Optimized access (legitimate concern: shared-DB constraints, + compile-time member-renumbering, application notes that mandate optimized + blocks). +- The customer already licenses OPC UA on the CPU. +- Symbolic addressing is preferred end-to-end (no byte-offset bookkeeping + in the OtOpcUa tag list; tags survive DB-member additions). +- S7-300 / S7-400 are out of scope on this CPU (the onboard OPC UA server + is S7-1500 V2.5+ only — see V2.5 firmware change list). + +**When to use Track 1 over Track 3**: + +- The CPU is S7-1200 (no onboard OPC UA server even on the V4 firmware + line) or older S7-1500 firmware (< V2.5). +- The customer won't pay for the SIMATIC NET OPC UA server license on the + CPU. +- The DBs in question exist purely as gateway buffers and have no + significant CPU-program access pattern that would benefit from + Optimized access. + +### Track 2 — out of scope + +For completeness, the **Track 2** option that was evaluated and rejected: +migrate the OtOpcUa S7 driver off S7netplus to a library that speaks +S7Plus. The candidates were: + +- **Snap7 v2 / Snap7Net** — classic S7comm only. Same Optimized-DB + limitation. Not a step forward. +- **Sharp7 community forks** — partial S7-1200 / S7-1500 PUT/GET semantics + but still classic-S7comm wire format. Not a step forward. +- **Custom S7Plus implementation** — possible in principle (the Wireshark + `s7comm-plus` dissector covers the wire format), but estimated **≥4 + weeks** of engineering for a minimal read/write/subscribe surface, plus + ongoing maintenance every time Siemens revs the protocol version + (which they do silently with each TIA Portal release). + +**Track 2 is not on the OtOpcUa roadmap** unless a specific customer funds +the engineering and ongoing maintenance. Track 1 + Track 3 together cover +every shipping S7 deployment we have visibility into. + +### Pre-flight diagnostics + +The driver does not currently auto-detect Optimized DBs from the +`BadDeviceFailure` shape (the same error code is returned for "DB doesn't +exist", "DB exists but is too short", etc.). On first encounter of a +device-failure error, check the suspect DB's properties in TIA Portal +**before** chasing wire-level theories. The auto-detect would require an +SZL probe or a symbolic round-trip; tracked but not a v2 deliverable. + +--- + Siemens S7 PLCs do *not* speak Modbus TCP natively at the OS/firmware level. Every S7 Modbus-TCP-server deployment is either (a) the **`MB_SERVER`** library block running on the CPU's PROFINET port (S7-1200 / S7-1500 / CPU 1510SP-series @@ -1030,7 +1215,11 @@ addresses by absolute offset, including UDT-typed DBs. If a customer can't disable Optimized access (e.g., shared-DB constraints), the workaround is to expose the UDT through the symbolic-tag path once that -ships — not in PR-S7-D2. +ships — not in PR-S7-D2. See +[Optimized DB constraint (S7Plus)](#optimized-db-constraint-s7plus) at the +top of this document for the project-wide decision (Track 1 disable in TIA +Portal, or Track 3 bridge via the OpcUaClient driver against the CPU's +onboard OPC UA server). ### Validation