Auto: s7-f — Optimized DB / S7Plus decision (Track 1+3 docs-only)

Closes #304
This commit is contained in:
Joseph Doherty
2026-04-26 11:22:40 -04:00
parent 26708b6609
commit b217ca61ce
3 changed files with 252 additions and 2 deletions

View File

@@ -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.<MemberName>` through a
Server interface, register an `OpcUaClient` driver against
`opc.tcp://<plc-ip>: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

20
docs/featuregaps.md Normal file
View File

@@ -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.

View File

@@ -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 → `<PLC>`**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": "<from secret store>",
"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