Connecting to PLCs

This page shows how to connect to different Siemens PLC models using python-snap7. All examples use the recommended s7 package, which works with every supported PLC model.

Rack/Slot Reference

PLC Model

Rack

Slot

Notes

S7-300

0

2

S7-400

0

3

May vary with multi-rack configurations

S7-1200

0

1

PUT/GET access must be enabled in TIA Portal (or use S7CommPlus)

S7-1500

0

1

PUT/GET access must be enabled in TIA Portal (or use S7CommPlus)

S7-200 / Logo

Use set_connection_params with TSAP addressing (legacy snap7 package)

Warning

S7-1200 and S7-1500 PLCs ship with PUT/GET communication disabled by default. When using s7.Client, the library automatically tries the S7CommPlus protocol first, which does not require PUT/GET to be enabled. If you need to use the legacy protocol, enable PUT/GET in TIA Portal under the CPU properties. See TIA Portal Configuration for step-by-step instructions.

S7-300

from s7 import Client

client = Client()
client.connect("192.168.1.10", 0, 2)

S7-400

from s7 import Client

client = Client()
client.connect("192.168.1.10", 0, 3)

S7-1200 / S7-1500

from s7 import Client

client = Client()
client.connect("192.168.1.10", 0, 1)
print(client.protocol)  # Protocol.S7COMMPLUS or Protocol.LEGACY

The client automatically tries S7CommPlus first and falls back to legacy S7 when needed. You can also force a specific protocol:

from s7 import Client, Protocol

client = Client()
# Force legacy S7 only (requires PUT/GET enabled)
client.connect("192.168.1.10", 0, 1, protocol=Protocol.LEGACY)

# Force S7CommPlus (raises on failure)
client.connect("192.168.1.10", 0, 1, protocol=Protocol.S7COMMPLUS)

See Client for details on TLS and password authentication.

S7CommPlus over TLS (V2/V3, TIA Portal V17+)

S7-1500 firmware ≥ V2.9 and S7-1200 firmware ≥ V4.5 negotiate S7CommPlus V2 or V3, which transports the protocol inside a TLS 1.3 session. Pass use_tls=True to connect to activate it:

from s7 import Client, Protocol

client = Client()
client.connect(
    "192.168.1.10", rack=0, slot=1,
    protocol=Protocol.S7COMMPLUS,
    use_tls=True,
)
data = client.db_read(1, 0, 4)
client.disconnect()

The client wraps the ISO-on-TCP socket with TLS 1.3 between the InitSSL exchange and the CreateObject request. By default the PLC’s certificate is not verified — fine for development, not fine in production. To verify the PLC against a CA bundle, pass tls_ca:

client.connect(
    "192.168.1.10", rack=0, slot=1,
    protocol=Protocol.S7COMMPLUS,
    use_tls=True,
    tls_ca="/path/to/plc-ca.pem",
)

If the PLC requires mutual TLS (client-side certificate), supply tls_cert and tls_key as well.

The cryptography package is required for TLS support. Install with the s7commplus extra:

pip install 'python-snap7[s7commplus]'

Note

Older S7-1200 firmware (FW < 4.5) negotiates V1 of the S7CommPlus protocol, which predates TLS and uses a different proprietary handshake. Client(...) falls back transparently to legacy PUT/GET on those PLCs (db_read / db_write work); browse() and other CommPlus-only operations are not yet supported on those firmwares — see issue #710.

TLS handshake fails on OpenSSL 3.5+ (post-quantum key share)

On systems with OpenSSL ≥ 3.5 (e.g. Debian 13 and other recent distributions) the TLS 1.3 ClientHello advertises a post-quantum hybrid key-exchange group, X25519MLKEM768, by default. Its key share is roughly 1.2 KB, and the S7-1500’s TLS stack rejects the oversized ClientHello — it drops the connection mid-handshake, so connect(use_tls=True) fails with a connection reset by peer.

The PLC mandates TLS 1.3 (it refuses TLS 1.2 outright), and CPython’s ssl module exposes no API to restrict the TLS 1.3 supported_groups list. The fix is to restrict the offered groups through OpenSSL’s own configuration — via the OPENSSL_CONF environment variable — to the classic ECDHE curves that every S7-1200/1500 supports.

  1. Create an OpenSSL configuration file, e.g. s7-openssl.cnf:

    # Restrict the TLS key-exchange groups to classic ECDHE curves so the
    # ClientHello stays small enough for the S7-1500 to accept.
    openssl_conf = openssl_init
    
    [openssl_init]
    ssl_conf = ssl_configuration
    
    [ssl_configuration]
    system_default = system_default_sect
    
    [system_default_sect]
    Groups = x25519:secp256r1:secp384r1
    
  2. Point OPENSSL_CONF at it before the Python process starts. OpenSSL reads this configuration once, when it initialises, so setting the variable from inside Python (e.g. via os.environ) after ssl is imported is too late — it must be set in the environment:

    # for a single run
    OPENSSL_CONF=/path/to/s7-openssl.cnf python your_script.py
    
    # or for the whole shell session
    export OPENSSL_CONF=/path/to/s7-openssl.cnf
    

With the groups restricted to classic curves, the TLS 1.3 handshake no longer offers X25519MLKEM768 and the S7-1500 completes it normally.

Note

This only affects OpenSSL ≥ 3.5. On older OpenSSL releases the default key-exchange groups are already classic ECDHE curves, so no extra configuration is needed.

PLC Password Authentication

If the PLC has a password configured (Full access (no protection) disabled in TIA Portal), call authenticate after connect:

from s7 import Client, Protocol

client = Client()
client.connect(
    "192.168.1.10", rack=0, slot=1,
    protocol=Protocol.S7COMMPLUS,
    use_tls=True,
)
client.authenticate(password="hunter2")
data = client.db_read(1, 0, 4)

Authentication requires TLS to be active (use_tls=True). The client auto-detects whether the PLC firmware uses the legacy SHA-1 challenge or the newer AES-256-CBC challenge. For accounts with a username (TIA Portal V17+ user-based access control), pass it explicitly:

client.authenticate(password="hunter2", username="operator")

S7-200 / Logo (TSAP Connection)

S7-200 and Logo PLCs require TSAP addressing via the legacy snap7 package:

import snap7

client = snap7.Client()
client.set_connection_params("192.168.1.10", 0x1000, 0x2000)
client.connect("192.168.1.10", 0, 0)

Using a Non-Standard Port

from s7 import Client

client = Client()
client.connect("192.168.1.10", 0, 1, tcp_port=1102)

Routing (Multi-Subnet Access)

Warning

Routing support is experimental and may change in future versions.

When the target PLC sits on a different subnet behind a gateway PLC, use connect_routed to let the gateway forward the connection:

import snap7

client = snap7.Client()
client.connect_routed(
    host="192.168.1.1",       # gateway PLC address
    router_rack=0,            # gateway rack
    router_slot=2,            # gateway slot
    subnet=0x0001,            # target subnet ID
    dest_rack=0,              # target PLC rack
    dest_slot=3,              # target PLC slot
)
data = client.db_read(1, 0, 4)
client.disconnect()

Legacy snap7 Package

If you have existing code using snap7.Client, it continues to work unchanged:

import snap7

client = snap7.Client()
client.connect("192.168.1.10", 0, 1)