Files

7.0 KiB

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, for hands-on CLI testing read Driver.S7.Cli.md, and for the test-harness map read 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 (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:

"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 ReadAsyncS7.Net.Plc.ReadAsync One request/response per tag, serialized on a per-PLC semaphore
IWritable WriteAsyncS7.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 teststests/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 for the coverage map and endpoint.
  • CLIDriver.S7.Cli.md documents the standalone read/write/probe CLI for manual checks against a real or simulated CPU.

Further reading