snap7-client
Async Rust client for Siemens S7 PLCs over ISO-on-TCP. Pure Rust — no FFI, no native C dependency.
Part of the rs-snap7 workspace.
Features
| Capability | Status |
|---|---|
| S7Comm (S7-300/400) — read/write DB, multi-area, blocks, SZL | ✅ |
| S7CommPlus (S7-1200/1500 integrity mode) | ✅ |
Connection pool (S7Pool) with RAII checkout |
✅ |
| Multi-read / multi-write with automatic PDU batching | ✅ |
| Typed tag addressing — DB, Merker, Timer, Counter | ✅ |
| Tag read/write with type decoding/encoding | ✅ |
Area absolute addressing — read_area / write_area for any area |
✅ |
| Block operations — list, numbers, info, upload, download, delete, fill | ✅ |
| PLC control — stop, hot-start, cold-start, status (SZL 0x0424) | ✅ |
| PLC information — order code, CPU info, CP info, module list | ✅ |
| Session password — set, clear, read protection level | ✅ |
| SZL queries — system status list, clock, protection | ✅ |
| Copy RAM → ROM, compress memory | ✅ |
TLS transport (S7CommPlus encrypted via tokio-rustls) |
✅ |
| UDP transport | ✅ |
Sync (blocking) API — via sync feature |
✅ |
| Pure Rust, zero native dependencies | ✅ |
Add to your project
[]
= "0.1"
= { = "1", = ["rt-multi-thread", "macros"] }
Quick start
Single connection
use ;
use TcpTransport;
use SocketAddr;
async
Multi-read (single PDU round-trip)
use MultiReadItem;
let items = vec!;
let results = client.read_multi_vars.await?;
// results[0] and results[1] — automatically batched across PDUs when needed
Connection pool
use ;
use SocketAddr;
let pool = new;
let guard = pool.acquire.await?;
guard.client.db_read.await?;
// connection returned to pool on drop
Typed tag parsing
use parse_tag;
// DB tags — comma or dot separator
let tag = parse_tag?; // REAL at byte offset 4
let tag = parse_tag?; // dot separator, same result
let tag = parse_tag?; // bit 0 of byte 332
// Merker (M) tags — single-part, no separator
let tag = parse_tag?; // byte at offset 10
let tag = parse_tag?; // word at offset 20
let tag = parse_tag?; // dword at offset 4
let tag = parse_tag?; // bit: byte 10, bit 3
let tag = parse_tag?; // bit: byte 5, bit 7 (MX prefix)
// Timer and Counter — element-index addressing
let tag = parse_tag?; // Timer 5
let tag = parse_tag?; // Counter 3
Read/write any area
use ;
// Merker byte read
let data = client.read_area.await?;
// Timer read (element-index addressing, 2 bytes per element)
let data = client.read_area.await?;
// Counter read
let data = client.read_area.await?;
// Write Merker word
client.write_area.await?;
PLC status
// Uses SZL 0x0424 — works on S7-300/400/1200/1500
let status = client.get_plc_status.await?;
// PlcStatus::Run | PlcStatus::Stop | PlcStatus::Unknown
TLS (S7CommPlus encrypted)
use tls_connect;
let stream = tls_connect.await?;
let client = from_transport.await?;
Sync (blocking) API
Enable the sync feature and use snap7_client::client_sync::S7ClientSync.
= { = "0.1", = ["sync"] }
Tag address syntax
DB tags use a comma or dot separator:
DB<n>,<type><byte-offset>
DB<n>.<type><byte-offset> # dot separator, same result
DB<n>,<byte-offset>.<bit> # bit access
Merker, Timer, Counter tags are single-part:
M<byte>.<bit> MX<byte>.<bit> MB<byte> MW<byte> MD<byte>
T<n> C<n>
| Area | Type | Width | Example |
|---|---|---|---|
| DB | REAL |
4 B | DB1,REAL0 |
| DB | DINT |
4 B | DB1,DINT4 |
| DB | DWORD |
4 B | DB1,DWORD4 |
| DB | INT |
2 B | DB1,INT8 |
| DB | WORD |
2 B | DB1,WORD8 |
| DB | BYTE |
1 B | DB1,BYTE10 |
| DB | bit | 1 bit | DB1,332.0 |
| Merker | bit | 1 bit | M10.3 / MX10.3 |
| Merker | byte | 1 B | MB10 |
| Merker | word | 2 B | MW20 |
| Merker | dword | 4 B | MD4 |
| Timer | S5Time | 2 B | T5 |
| Counter | BCD | 2 B | C3 |
License
MIT — see LICENSE.