Files
lmxopcua/docs/drivers/S7.md
T
Joseph Doherty b7dfb5aff2 docs(s7): wide-type/Timer-Counter support — CLI help + driver-specs + S7 driver doc
Drop the "not yet implemented / BadNotSupported" stale note from all three
S7 CLI --type option descriptions (ReadCommand, WriteCommand, SubscribeCommand)
and replace with accurate help listing the full supported type set, byte-anchored
addressing for wide types, and Timer/Counter read-only status.

docs/v2/driver-specs.md §5: add Supported Data Types table, Byte-Anchored
Addressing table (DBB/MB/IB/QB + examples), Timer/Counter read section with
the Counter-BCD known-limitation, and Deferrals list.

docs/drivers/S7.md: expand Data types to a full table, add "Wide types &
Timer/Counter" section (byte-anchored addressing, Timer/Counter read-only,
Counter BCD known-limitation, deferrals), update Address forms table and
1-D array Deferrals note.
2026-06-17 06:22:36 -04:00

12 KiB
Raw Blame History

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 / DB1.DBB8 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
Timer T0 / T15 Timer area (read-only — see Wide types section)
Counter C0 / C10 Counter area (read-only — see Wide types section)

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.

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.

DataType S7 Type Width Read Write
Bool BOOL 1 bit yes yes
Byte BYTE 1 byte yes yes
Int16 INT 2 bytes yes yes
UInt16 WORD 2 bytes yes yes
Int32 DINT 4 bytes yes yes
UInt32 DWORD 4 bytes yes yes
Float32 REAL 4 bytes yes yes
Int64 LINT 8 bytes yes yes
UInt64 ULINT/LWORD 8 bytes yes yes
Float64 LREAL 8 bytes yes yes
String STRING StringLength + 2 bytes yes yes
DateTime DATE_AND_TIME 8 bytes yes yes
Timer (T{n}) TIME yes read-only
Counter (C{n}) COUNTER yes read-only

Wide types (Int64, UInt64, Float64, String, DateTime) and Timer/Counter require byte-anchored addressing — see the section below.

Wide types & Timer/Counter

Byte-anchored addressing for wide / structured types

Wide types (Int64, UInt64, Float64/LReal, String, DateTime) are byte-anchored: the address must use the B suffix pointing at the first byte of the value; the driver reads the correct number of contiguous bytes based on the DataType.

DataType Address form Bytes read Example
Int64 DB{n}.DBB{offset} / MB{offset} / IB{offset} / QB{offset} 8 DB1.DBB8 → LINT at bytes 815
UInt64 same 8 DB2.DBB16 → ULINT at bytes 1623
Float64 same 8 DB1.DBB8 → LREAL at bytes 815
DateTime same 8 DB3.DBB0 → DATE_AND_TIME at bytes 07
String same StringLength + 2 DB4.DBB0, StringLength=40 → 42 bytes

Using a W or D suffix with a wide DataType is a config error caught at InitializeAsync. For array tags (isArray: true), wide-type element arrays are deferred — see Deferrals below.

Timer read

Address: T{n} (e.g. T0, T15). DataType must be Float64. The OPC UA node is a scalar Float64 whose value is the timer's elapsed time in seconds (as a double). Timers are read-only this phase; a write attempt returns BadNotWritable.

Counter read

Address: C{n} (e.g. C0, C10). DataType must be Int32. The OPC UA node is a scalar Int32 whose value is the current count (raw word from the PLC). Counters are read-only this phase; a write attempt returns BadNotWritable.

Known limitation — Counter BCD encoding on S7-300/400: S7.Net's Counter.FromByteArray returns the raw big-endian word without BCD decode. On classic S7-300/400 the C-area word is BCD-encoded (0999), so the surfaced value can differ from the actual count on that hardware. S7-1200/1500 use IEC/DB counters (plain integers) where the raw word is correct. BCD reinterpretation for legacy C-area is a live-hardware-gated follow-up.

Deferrals (not yet implemented)

The following are explicitly deferred and will produce a config error or BadNotSupported if attempted:

  • Wide-type arrays — array tags (isArray: true) with element types Int64, UInt64, Float64, String, or DateTime.
  • S7WString (2-byte character strings; distinct from classic STRING).
  • DTL / DateTimeLong (12-byte Siemens date-and-time-long).
  • Timer / Counter writes — surfaced as BadNotWritable until write support lands.

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.

1-D array support

An S7 tag becomes a 1-D OPC UA array node when its TagConfig JSON carries "isArray": true and "arrayLength": N (N ≥ 1). The canonical rule: isArray: true + arrayLength >= 1 → array; isArray: false (any length) → scalar.

Read mechanism — the driver issues a single ReadBytesAsync call over the contiguous memory span starting at the declared address for N × (bytes per element) bytes, then loops over the response buffer decoding each element individually using the same reinterpret/box logic as scalar reads. This keeps wire round-trips at 1 per array tag regardless of N.

Supported element types — the narrow scalar types (Bool, Byte, Int16, UInt16, Int32, UInt32, Float32). Wide types (Int64, UInt64, Float64, String, DateTime) and Timer/Counter are deferred for array contexts. The ReadBytesAsync network half is a thin S7.Net call; the decode half is fully unit-tested.

Unit test coveragetests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.Tests/ covers the contiguous-block read and per-element decode loop. Integration fixture is down during normal dev (the S7 sim is on the shared Docker host but currently offline).

Live-verify — integration-fixture-gated (not Mac-verifiable without the S7 sim up).

Deferrals — array writes, multi-dimensional arrays, per-element historization, wide-type array elements (Int64, UInt64, Float64, String, DateTime), and Timer/Counter array elements.

See Uns.md §Array tags for the cross-driver coverage matrix and the UI authoring flow.

Further reading