149 lines
7.0 KiB
Markdown
149 lines
7.0 KiB
Markdown
# Siemens S7 Driver
|
|
|
|
Getting-started guide for the Siemens S7 driver. This is the short path — for
|
|
the full per-field spec read [`docs/v2/driver-specs.md §5`](../v2/driver-specs.md),
|
|
for hands-on CLI testing read [Driver.S7.Cli.md](../Driver.S7.Cli.md), and for
|
|
the test-harness map read [S7-Test-Fixture.md](S7-Test-Fixture.md).
|
|
|
|
## What it talks to
|
|
|
|
Siemens S7 PLCs — S7-300, S7-400, S7-1200, S7-1500, plus S7-200 / S7-200 Smart
|
|
/ LOGO! 0BA8 — over the native **S7comm** protocol on **ISO-on-TCP, TCP port
|
|
102**. The wire is spoken by the pure-managed [S7netplus](https://github.com/S7NetPlus/s7netplus)
|
|
(`S7.Net`) library: no native DLL, no P/Invoke, no out-of-process isolation. The
|
|
driver runs in-process in the OtOpcUa server's .NET 10 AnyCPU host on every OS
|
|
the server runs on.
|
|
|
|
This is the **leanest** OtOpcUa driver — read/write/subscribe/discover plus a
|
|
connectivity probe, and nothing else. It implements no alarm source and no
|
|
per-call host resolver (a single S7 instance targets a single CPU).
|
|
|
|
## Project split
|
|
|
|
| Project | Target | Role |
|
|
|---------|--------|------|
|
|
| `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7/` | net10.0 | In-process driver — hosts the `S7.Net.Plc` connection and the address parser |
|
|
| `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.Contracts/` | net10.0 | Dependency-free config records + enums (`S7DriverOptions`, `S7CpuType`, `S7DataType`) bound from `DriverConfig` JSON |
|
|
|
|
## Minimum deployment
|
|
|
|
Register the driver instance in the central config DB (or `appsettings.json`).
|
|
No separate service, no DLL deployment:
|
|
|
|
```jsonc
|
|
"Drivers": {
|
|
"s7-line-1": {
|
|
"Type": "S7",
|
|
"Config": {
|
|
"Host": "10.20.30.40",
|
|
"CpuType": "S71500",
|
|
"Rack": 0,
|
|
"Slot": 0,
|
|
"Tags": [
|
|
{ "Name": "Running", "Address": "DB1.DBX0.0", "DataType": "Bool", "Writable": false },
|
|
{ "Name": "Speed", "Address": "DB1.DBD4", "DataType": "Float32", "Writable": true }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
S7 exposes a symbol table, but `S7.Net` does not surface it — so the driver
|
|
operates off a **static, per-site tag list**, not live symbol discovery.
|
|
|
|
### Rack / slot / CPU family
|
|
|
|
`CpuType` selects the ISO-TSAP slot byte used during the connection handshake;
|
|
pick the family that matches the PLC exactly. `Rack` is almost always `0`
|
|
(relevant only for distributed S7-400 racks). `Slot` conventions per family:
|
|
S7-300 = slot 2, S7-400 = slot 2 or 3, S7-1200 / S7-1500 = slot 0 (onboard PN).
|
|
A wrong slot causes a connection refusal during the handshake. See
|
|
`src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.Contracts/S7DriverOptions.cs` for the
|
|
per-field defaults.
|
|
|
|
## Address forms
|
|
|
|
Addresses use Siemens TIA-Portal / STEP 7 Classic syntax, parsed by
|
|
`src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7/S7AddressParser.cs`:
|
|
|
|
| Area | Example | Meaning |
|
|
|------|---------|---------|
|
|
| Data block | `DB1.DBX0.0` / `DB1.DBW0` / `DB1.DBD4` | DB number + size suffix `X`(bit) / `B`(byte) / `W`(word) / `D`(dword), optional `.bit` for `DBX` |
|
|
| Merker (M) | `MB0` / `MW0` / `MD4` / `M0.0` | Marker byte; size prefix `B`/`W`/`D`, or bare offset `.bit` for bit access |
|
|
| Input (I) | `IB0` / `IW0` / `I0.0` | Process-image input |
|
|
| Output (Q) | `QB0` / `QW0` / `Q0.0` | Process-image output |
|
|
|
|
Parsing is strict and runs once at `InitializeAsync` so a config typo fails fast
|
|
at load instead of surfacing as `BadInternalError` on every read. Bit offsets
|
|
must be 0-7, byte offsets non-negative, DB numbers >= 1.
|
|
|
|
> **Timer (`T{n}`) and Counter (`C{n}`)** addresses parse cleanly but the read
|
|
> path has no decode case for them yet — the driver rejects them at init with an
|
|
> explicit error rather than letting them surface a misleading type-mismatch.
|
|
|
|
## Data types
|
|
|
|
`S7DataType` declares the **semantic** type; `S7.Net` returns an unsigned boxed
|
|
value (bool / byte / ushort / uint) that the driver reinterprets without an
|
|
extra PLC round-trip. Wired through today: `Bool`, `Byte`, `Int16`, `UInt16`,
|
|
`Int32`, `UInt32`, `Float32`. `Int64`, `UInt64`, `Float64`, `String`, and
|
|
`DateTime` are declared in the enum but **rejected at init** — half-implemented
|
|
types must not create OPC UA nodes that then return `BadNotSupported` on every
|
|
access.
|
|
|
|
## Capability surface
|
|
|
|
`S7Driver : IDriver, ITagDiscovery, IReadable, IWritable, ISubscribable, IHostConnectivityProbe`
|
|
(`src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs`).
|
|
|
|
| Capability | Path | Notes |
|
|
|------------|------|-------|
|
|
| `IReadable` | `ReadAsync` → `S7.Net.Plc.ReadAsync` | One request/response per tag, serialized on a per-PLC semaphore |
|
|
| `IWritable` | `WriteAsync` → `S7.Net.Plc.WriteAsync` | Read-only tags (`Writable=false`) return `BadNotWritable` |
|
|
| `ITagDiscovery` | `DiscoverAsync` | Emits a flat `S7/` folder of the configured tags — no live browse |
|
|
| `ISubscribable` | per-tag poll loop with capped exponential backoff | S7 has no push model; floor is 100 ms (the CPU services the comms mailbox once per scan) |
|
|
| `IHostConnectivityProbe` | periodic `S7.Net.Plc.ReadStatusAsync` (CPU-status PDU) | `host:port` host key; `Running`/`Stopped` transitions raise `OnHostStatusChanged` |
|
|
|
|
### Single-connection policy
|
|
|
|
One `S7.Net.Plc` instance per PLC, serialized with a `SemaphoreSlim`.
|
|
Parallelising reads against a single CPU doesn't help — the CPU scans its
|
|
comms mailbox at most once per cycle and queues concurrent requests wire-side
|
|
anyway, while wasting the CPU's 8-64 connection-resource budget.
|
|
|
|
## PUT/GET communication
|
|
|
|
S7-1200 / S7-1500 ship with **PUT/GET access disabled** by default. A driver
|
|
pointed at a freshly-flashed CPU sees a hard access-denied fault. The driver
|
|
maps it specifically to `BadNotSupported`, flags the instance `Faulted` (a
|
|
configuration alert, not a transient fault), and does **not** blind-retry —
|
|
because the CPU will keep refusing. Fix: enable PUT/GET communication in TIA
|
|
Portal under *Protection & Security* for the CPU.
|
|
|
|
## Error mapping
|
|
|
|
| Condition | StatusCode | Health |
|
|
|-----------|------------|--------|
|
|
| Tag not in config | `BadNodeIdUnknown` | unchanged |
|
|
| Read-only tag written | `BadNotWritable` | unchanged |
|
|
| Unimplemented data type | `BadNotSupported` | unchanged |
|
|
| PUT/GET denied | `BadNotSupported` | `Faulted` (config alert) |
|
|
| CPU / hardware fault | `BadDeviceFailure` | `Degraded` |
|
|
| Socket / timeout | `BadCommunicationError` | `Degraded` |
|
|
|
|
## Testing
|
|
|
|
- **Unit tests** — `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.Tests/` cover the
|
|
address parser, the reinterpret/box conversions, and the driver lifecycle.
|
|
- **Integration fixture** — a Docker S7 simulator on the shared docker host; see
|
|
[S7-Test-Fixture.md](S7-Test-Fixture.md) for the coverage map and endpoint.
|
|
- **CLI** — [Driver.S7.Cli.md](../Driver.S7.Cli.md) documents the standalone
|
|
read/write/probe CLI for manual checks against a real or simulated CPU.
|
|
|
|
## Further reading
|
|
|
|
- [`docs/v2/driver-specs.md §5`](../v2/driver-specs.md) — full per-field spec,
|
|
DriverConfig JSON shape, and operational stability notes
|
|
- [Driver.S7.Cli.md](../Driver.S7.Cli.md) — standalone S7 driver CLI
|
|
- [S7-Test-Fixture.md](S7-Test-Fixture.md) — simulator + test-harness map
|