Files
lmxopcua/docs/Driver.S7.Cli.md
2026-04-26 10:51:07 -04:00

9.7 KiB

otopcua-s7-cli — Siemens S7 test client

Ad-hoc probe / read / write / subscribe tool for Siemens S7-300 / S7-400 / S7-1200 / S7-1500 (and compatible soft-PLCs) over S7comm / ISO-on-TCP port 102. Uses the same S7Driver the OtOpcUa server does (S7.Net under the hood).

Fourth of four driver test-client CLIs.

Build + run

dotnet run --project src/ZB.MOM.WW.OtOpcUa.Driver.S7.Cli -- --help

Common flags

Flag Default Purpose
-h / --host required PLC IP or hostname
-p / --port 102 ISO-on-TCP port (rarely changes)
-c / --cpu S71500 S7200 / S7200Smart / S7300 / S7400 / S71200 / S71500
--rack 0 Hardware rack (S7-400 distributed setups only)
--slot 0 CPU slot (S7-300 = 2, S7-400 = 2 or 3, S7-1200/1500 = 0)
--timeout-ms 5000 Per-operation timeout
--tsap-mode Auto ISO-on-TCP connection class: Auto / Pg / Op / S7Basic / Other. Hardened S7-1500 / ET 200SP CPUs may require Op or S7Basic. See s7.md TSAP / Connection Type.
--local-tsap (unset) Optional 16-bit local TSAP override (e.g. 0x0200). Required when --tsap-mode Other; wins over class default under Pg/Op/S7Basic.
--remote-tsap (unset) Optional 16-bit remote TSAP override. Required when --tsap-mode Other; wins over class default under Pg/Op/S7Basic.
--password (unset) Connection-level password sent right after OpenAsync. Used by hardened S7-300/400 (protection levels 1-3) and S7-1200/1500 (TIA Portal Connection Mechanism gate). Never logged. NB: S7netplus 0.20 doesn't expose SendPassword; the CLI prints a one-line warning and continues. See s7.md "PLC password / protection levels".
--protection-level Auto Declarative hint: Auto / None / Level1 / Level2 / Level3 (S7-300/400) / ConnectionMechanism (S7-1200/1500). Diagnostic only — the wire-side unlock is driven by --password.
--verbose off Serilog debug output

PUT/GET must be enabled

S7-1200 / S7-1500 ship with PUT/GET communication disabled by default. Enable it in TIA Portal: Device config → Protection & Security → Connection mechanisms → "Permit access with PUT/GET communication from remote partner". Without it the CLI's first read will surface BadNotSupported.

Pre-flight PUT/GET enablement (PR-S7-C5)

The driver issues a tiny 2-byte read against Probe.ProbeAddress (default MW0) immediately after OpenAsync and fails InitializeAsync with a typed S7PutGetDisabledException when the PLC rejects the read with the wire-level "function not allowed" response. The exception message names the exact TIA Portal toggle to flip — operators see the configuration fix at init time, not after the first per-tag read produces BadDeviceFailure.

Two opt-out knobs on the JSON Probe block:

  • ProbeAddress — set to "" (empty string) to skip the pre-flight read entirely. Useful when no fingerprint address has been wired.
  • SkipPreflight — set to true to defer the check to runtime while keeping the background liveness loop. Per-tag reads still surface BadDeviceFailure until PUT/GET is enabled, but Init succeeds and the driver becomes visible in the Admin UI.

See s7.md "Pre-flight PUT/GET enablement" for the full rationale, classifier behaviour, and the wire-level ErrorCode matching.

S7 address grammar cheat sheet

Form Meaning
DB1.DBW0 DB number 1, word offset 0
DB1.DBD4 DB number 1, dword offset 4
DB1.DBX2.3 DB number 1, byte 2, bit 3
DB10.STRING[0] DB 10 string starting at offset 0
M0.0 Merker bit 0.0
MW0 / MD4 Merker word / dword
IW4 Input word 4
QD8 Output dword 8

Commands

probe

# S7-1500 — default probe MW0
otopcua-s7-cli probe -h 192.168.1.30

# S7-300 (slot 2)
otopcua-s7-cli probe -h 192.168.1.31 -c S7300 --slot 2 -a DB1.DBW0

read

# DB word
otopcua-s7-cli read -h 192.168.1.30 -a DB1.DBW0 -t Int16

# Float32 from DB dword
otopcua-s7-cli read -h 192.168.1.30 -a DB1.DBD4 -t Float32

# Merker bit
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

otopcua-s7-cli write -h 192.168.1.30 -a DB1.DBW0 -t Int16 -v 42
otopcua-s7-cli write -h 192.168.1.30 -a DB1.DBD4 -t Float32 -v 3.14
otopcua-s7-cli write -h 192.168.1.30 -a M0.0 -t Bool -v true

Writes to M / Q are real — they drive the PLC program. Be careful what you flip on a running machine.

Hardened CPU — forcing OP-class TSAP

# Probe a hardened S7-1500 that rejects PG class but accepts OP.
otopcua-s7-cli probe -h 10.50.12.30 --tsap-mode Op

# Read against the same CPU.
otopcua-s7-cli read -h 10.50.12.30 --tsap-mode Op -a DB1.DBW0 -t Int16

# Manual TSAP override (e.g. site with a fixed proprietary TSAP gateway).
otopcua-s7-cli probe -h 10.50.12.30 --tsap-mode Other --local-tsap 0x4D57 --remote-tsap 0x4D58

Without --tsap-mode, the CLI uses S7netplus's CpuType-derived default (PG class for almost everything). The same connection-refused failure shape that a wrong --slot produces also shows up when the CPU rejects PG class — try --tsap-mode Op first when the handshake is failing on otherwise-correct endpoint config. See s7.md TSAP / Connection Type for the byte table and motivation.

Hardened CPU — supplying a connection-level password

# S7-300 protection-level 2 — read+write protected without unlock.
otopcua-s7-cli read -h 192.168.1.31 -c S7300 --slot 2 `
    --password "tia-portal-set-password" `
    --protection-level Level2 `
    -a DB1.DBW0 -t Int16

# S7-1500 ConnectionMechanism — TIA Portal Protection & Security pane gate.
otopcua-s7-cli probe -h 10.50.12.30 `
    --tsap-mode Op `
    --password "tia-portal-set-password" `
    --protection-level ConnectionMechanism

The password is emitted to the PLC immediately after OpenAsync succeeds and before the pre-flight PUT/GET probe runs (the same probe that would otherwise be the first operation a hardened CPU refuses). Never logged in any form; identifier-only success line is S7 password sent for {Host}.

S7netplus 0.20 does not yet expose a public SendPassword — the driver discovers the method reflectively, so a future minor release will be picked up automatically. Until then, configuring --password on a hardened CPU emits this warning at Init:

[Warning] S7 password is set on driver '<id>' against host '<host>', but
the linked S7netplus library does not expose SendPassword; password is
being ignored at the wire.

Init still completes (the COTP handshake itself doesn't require the password) but the first read against a hardened CPU will surface BadDeviceFailure. See s7.md "PLC password / protection levels" for the full motivation, the no-log invariant, and the workaround matrix.

subscribe

otopcua-s7-cli subscribe -h 192.168.1.30 -a DB1.DBW0 -t Int16 -i 500

S7comm has no native push — the CLI polls through PollGroupEngine just like Modbus / AB.

import-symbols

PR-S7-D1 / #299 — read a TIA Portal CSV ("Show all tags" export) or STEP 7 Classic .AWL file and emit a JSON tag fragment for appsettings.json, or a one-line summary. Mirrors the AB Legacy import-rslogix CLI in shape.

# TIA Portal CSV — emit JSON fragment to stdout
otopcua-s7-cli import-symbols --file plc-export.csv --format tia

# STEP 7 Classic AWL — emit summary line
otopcua-s7-cli import-symbols --file classic.awl --format awl --emit summary

# DE-locale CSV — auto-detected; output to file
otopcua-s7-cli import-symbols `
    --file plc-de.csv `
    --format tia `
    --emit appsettings-fragment `
    --output tags.json

# Strict mode — fail-fast on the first malformed row (CI lint)
otopcua-s7-cli import-symbols --file plc.csv --format tia --strict
Flag Default Purpose
-f / --file required Path to the TIA CSV or .AWL file
--format tia tia (CSV) or awl (STEP 7 Classic)
-d / --device none Optional documentation tag (held for symmetry with import-rslogix)
--emit appsettings-fragment appsettings-fragment (JSON) or summary (one-line counter)
-o / --output stdout Optional path; when set the JSON fragment is written there + summary line goes to stdout
--max-rows unlimited Defensive cap on rows imported
--strict off Fail-fast on the first malformed row (default permissive: skip + log)

UDT-typed rows import as placeholder tags (data type forced to Byte); see S7-TIA-Import.md for the full format reference, locale auto-detection, and AWL position-based addressing rules.