Auto: s7-e1 — CPU diagnostic buffer / SZL reads

Closes #302
This commit is contained in:
Joseph Doherty
2026-04-26 10:30:43 -04:00
parent f7e0d9a9e7
commit 108f69d198
14 changed files with 1701 additions and 3 deletions

View File

@@ -95,6 +95,17 @@ otopcua-s7-cli read -h 192.168.1.30 -a M0.0 -t Bool
# 80-char S7 string
otopcua-s7-cli read -h 192.168.1.30 -a DB10.STRING[0] -t String --string-length 80
# CPU diagnostics (SZL) — virtual @System.* addresses (PR-S7-E1).
# Requires ExposeSystemTags = true on the driver instance; surfaces as
# BadNotSupported until S7netplus exposes a public ReadSzlAsync (or we ship
# a raw-PDU helper). See docs/v2/s7.md "CPU diagnostics (SZL)" for the full
# table and the snap7 / S7netplus 0.20 caveat.
otopcua-s7-cli read -h 192.168.1.30 -a @System.CpuType -t String
otopcua-s7-cli read -h 192.168.1.30 -a @System.Firmware -t String
otopcua-s7-cli read -h 192.168.1.30 -a @System.OrderNo -t String
otopcua-s7-cli read -h 192.168.1.30 -a @System.CycleMs.Min -t Float64
otopcua-s7-cli read -h 192.168.1.30 -a "@System.DiagBuffer.Entry[0]" -t String
```
### `write`

View File

@@ -95,6 +95,20 @@ structs — not covered. UDT fan-out IS covered (PR-S7-D2 / #300) via the
`udt_layout` meta-seed in `Docker/profiles/s7_1500.json` and the
`Driver_fans_out_udt_into_member_tags` integration test.
### 6. SZL (System Status List) — `@System.*` virtual addresses
PR-S7-E1 / [#302](https://github.com/dohertj2/dohertj2/lmxopcua/issues/302)
adds a virtual `@System.*` address surface (CPU type, firmware, scan-cycle
stats, diagnostic-buffer ring) backed by SZL reads. **snap7 does not
implement SZL** — the simulator answers every SZL request with a function-
not-supported error, so the integration profile exercises only the
not-supported semantics (`@System.CpuType` against snap7 returns
`BadNotSupported`). Live-firmware SZL coverage is parked behind a
`[Fact(Skip = ...)]` until either S7netplus exposes a public `ReadSzlAsync`
or we ship a raw S7comm PDU helper. See
[`docs/v2/s7.md` "CPU diagnostics (SZL)"](../v2/s7.md#cpu-diagnostics-szl)
for the wire-status detail.
## When to trust the S7 tests, when to reach for a rig
| Question | Unit tests | Real PLC |
@@ -127,6 +141,15 @@ structs — not covered. UDT fan-out IS covered (PR-S7-D2 / #300) via the
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
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
(`S7SzlParser`) is unit-tested against golden bytes; flipping the wire
path on requires either an S7netplus PR or a raw-PDU helper. Once that's
in, [`S7_1500SzlTests.System_CpuType_against_live_S7_1500_returns_non_empty_string`](../../tests/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests/S7_1500/S7_1500SzlTests.cs)
should be flipped from `[Fact(Skip = ...)]` to env-var-gated against the
self-hosted runner with the lab rig.
Without any of these, S7 driver correctness against real hardware is trusted
from field deployments, not from the test suite.

View File

@@ -1068,6 +1068,94 @@ FB-instance DBs imported via PR-S7-D3 / [#301](https://github.com/dohertj2/lmxop
see [`docs/drivers/S7-TIA-Import.md` "Re-import on FB-interface edit"](../drivers/S7-TIA-Import.md#re-import-on-fb-interface-edit--caveat)
for the FB-instance-specific workflow.
## CPU diagnostics (SZL)
PR-S7-E1 / [#302](https://github.com/dohertj2/lmxopcua/issues/302) — every S7
CPU answers SZL (System Status List) queries with metadata about itself: CPU
type, firmware, order number, scan-cycle min/avg/max, and the diagnostic
buffer ring. The driver surfaces those through a virtual `@System.*` address
space dispatched against the SZL sub-protocol — no DB / merker tag declarations
required.
### Opt-in: `ExposeSystemTags`
Off by default. Set `ExposeSystemTags = true` in `S7DriverOptions` and
`DiscoverAsync` adds a `Diagnostics/` sub-folder under the driver root with
the variables listed below. Knobs:
| Option | Default | Notes |
| --- | --- | --- |
| `ExposeSystemTags` | `false` | Master switch. When `false` the SZL surface is invisible — no extra browse nodes, no extra wire traffic. |
| `DiagBufferDepth` | `10` | Number of diagnostic-buffer entries to discover under `DiagBuffer/Entry[N]`. Capped at 50. |
| `SzlCacheTtl` | `5 s` | TTL for the per-driver SZL cache. A burst of `@System.*` reads inside this window reuses one wire response per SZL ID. Set to `TimeSpan.Zero` to disable caching (every read hits the wire). |
### `@System.*` address table
| Address | OPC UA type | SZL ID | Index | What it is |
| --- | --- | --- | --- | --- |
| `@System.CpuType` | `String` | `0x0011` | `0x0000` | CPU friendly name (SZL index 0x0007) or MLFB fallback. |
| `@System.Firmware` | `String` | `0x0011` | `0x0000` | Firmware version, formatted `Vmaj.min.patch`. |
| `@System.OrderNo` | `String` | `0x0011` | `0x0000` | MLFB / order number, e.g. `6ES7 516-3AN01-0AB0`. |
| `@System.CycleMs.Min` | `Float64` | `0x0132` | `0x0005` | Shortest scan cycle observed since last reset, in milliseconds. |
| `@System.CycleMs.Max` | `Float64` | `0x0132` | `0x0005` | Longest scan cycle observed since last reset, in milliseconds. |
| `@System.CycleMs.Avg` | `Float64` | `0x0132` | `0x0005` | Rolling average scan-cycle time, in milliseconds. |
| `@System.DiagBuffer.Entry[N]` | `String` | `0x00A0` | `0x0000` | Diagnostic-buffer entry rendered as `<UTC ISO-8601> \| 0x<event id> \| prio=<N> \| <event text>`. `N` ranges from `0` (most recent) through `DiagBufferDepth-1`. |
The diagnostic-buffer entries surface as flat strings rather than a structured
DataType so dashboards / log scrapers can split / grep them without a custom
schema.
### What's wired today vs not-supported
S7netplus 0.20 builds SZL request packages internally
(`SzlReadRequestPackage` / `WriteSzlReadRequest`) but does **not** expose a
public `ReadSzlAsync` API. Until S7netplus catches up (or we ship a raw S7comm
PDU helper that side-steps the library), the production
[`S7NetSzlReader`](../../src/ZB.MOM.WW.OtOpcUa.Driver.S7/Szl/S7NetSzlReader.cs)
returns `null` on every call and every `@System.*` read surfaces as
`BadNotSupported`. The browse tree still lights up — operators can wire
clients against it — only the values come back not-supported.
The parser code (`S7SzlParser`) is fully tested against golden bytes
regardless. Flipping the wire path on is a one-method change in
`S7NetSzlReader` once the upstream surface is available; no parser / dispatch
/ cache changes needed.
snap7 (the simulator backing the integration profile) also doesn't implement
SZL — the integration test
[`S7_1500SzlTests`](../../tests/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests/S7_1500/S7_1500SzlTests.cs)
asserts the not-supported semantics against snap7 + parks the live-firmware
test behind `[Fact(Skip = ...)]` until the wire path lights up.
### Caching
Diagnostics shouldn't poll faster than `SzlCacheTtl` — a 100 ms HMI
subscription on every `@System.*` tag would otherwise hammer the comms
mailbox for data that doesn't change between scans. The per-driver
[`S7SzlCache`](../../src/ZB.MOM.WW.OtOpcUa.Driver.S7/Szl/S7SzlCache.cs)
de-dups concurrent reads by `(SzlId, SzlIndex)`; one SZL 0x0011 round-trip
backs `CpuType` + `Firmware` + `OrderNo` for the whole TTL window. Negative
results (SZL not supported) are cached just as aggressively — repeatedly
hammering a CPU that already said "not supported" wouldn't help.
`SzlCacheTtl = TimeSpan.Zero` disables caching entirely; useful for
diagnostics tests where you want every read to hit the wire.
### JSON config example
```json
{
"DriverConfig": {
"Host": "192.168.10.50",
"Port": 102,
"CpuType": "S71500",
"ExposeSystemTags": true,
"DiagBufferDepth": 20,
"SzlCacheTtl": "00:00:05"
}
}
```
## References
1. Siemens Industry Online Support, *Modbus/TCP Communication between SIMATIC S7-1500 / S7-1200 and Modbus/TCP Controllers with Instructions `MB_CLIENT` and `MB_SERVER`*, Entry ID 102020340, V6 (Feb 2021). https://cache.industry.siemens.com/dl/files/340/102020340/att_118119/v6/net_modbus_tcp_s7-1500_s7-1200_en.pdf