Connecting to PLCs

This page shows how to connect to different Siemens PLC models using python-snap7.

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 (s7 package)

Warning

S7-1200 and S7-1500 PLCs ship with PUT/GET communication disabled by default. Use s7commplus.Client to communicate via S7CommPlus, which does not require PUT/GET to be enabled. If you need to use s7.Client (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 (S7CommPlus)

from s7commplus import Client

client = Client()
client.connect("192.168.1.10")

S7-1200 / S7-1500 (Legacy PUT/GET)

If PUT/GET access is enabled in TIA Portal, you can also use the legacy protocol:

from s7 import Client

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

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 s7commplus import Client

client = Client()
client.connect(
    "192.168.1.10",
    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",
    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 rejected by the PLC (connection reset)

S7 PLCs have a minimal TLS stack that rejects ClientHello messages containing features it does not recognise. Two common causes:

  • Post-quantum key share (OpenSSL ≥ 3.5) — the default ClientHello advertises the X25519MLKEM768 hybrid group whose ~1.2 KB key share the PLC drops.

  • Modern signature algorithms — OpenSSL advertises Ed25519, Ed448, and RSA-PSS variants in the signature_algorithms extension. Instead of ignoring unknown algorithms (as TLS 1.2 requires), the PLC treats them as a fatal error and sends a TCP RST.

CPython’s ssl module exposes no API for either the supported_groups or signature_algorithms lists. The fix is to restrict both through OpenSSL’s own configuration via the OPENSSL_CONF environment variable.

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

    # Restrict TLS parameters for S7 PLC compatibility.
    openssl_conf = openssl_init
    
    [openssl_init]
    ssl_conf = ssl_configuration
    
    [ssl_configuration]
    system_default = system_default_sect
    
    [system_default_sect]
    # Classic ECDHE curves only — no post-quantum groups.
    Groups = x25519:secp256r1:secp384r1
    # Classic signature algorithms only — no Ed25519, Ed448, or RSA-PSS.
    SignatureAlgorithms = RSA+SHA256:RSA+SHA384:RSA+SHA512:ECDSA+SHA256:ECDSA+SHA384
    
  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 both settings applied, the ClientHello contains only algorithms that S7 PLCs understand and the handshake completes normally.

PLC Password Authentication

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

from s7commplus import Client

client = Client()
client.connect(
    "192.168.1.10",
    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 TSAP addressing:

from s7 import Client

client = 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:

from s7 import Client

client = 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 — snap7 is an alias for s7:

import snap7

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