Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 32 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Python-snap7 is a pure Python S7 communication library for interfacing with Siem

## Key Architecture

### snap7/ — Legacy S7 protocol (S7-300/400, PUT/GET on S7-1200/1500)
- **snap7/client.py**: Main Client class for connecting to S7 PLCs
- **snap7/server.py**: Server implementation for PLC simulation
- **snap7/logo.py**: Logo PLC communication
Expand All @@ -19,6 +20,20 @@ Python-snap7 is a pure Python S7 communication library for interfacing with Siem
- **snap7/type.py**: Type definitions and enums (Area, Block, WordLen, etc.)
- **snap7/error.py**: Error handling and exceptions

### s7/ — Unified client with S7CommPlus + legacy fallback
- **s7/client.py**: Unified Client — tries S7CommPlus, falls back to snap7.Client
- **s7/async_client.py**: Unified AsyncClient — same pattern, async
- **s7/server.py**: Unified Server wrapping both legacy and S7CommPlus
- **s7/_protocol.py**: Protocol enum (AUTO/LEGACY/S7COMMPLUS)
- **s7/_s7commplus_client.py**: Pure S7CommPlus sync client (internal)
- **s7/_s7commplus_async_client.py**: Pure S7CommPlus async client (internal)
- **s7/_s7commplus_server.py**: S7CommPlus server emulator (internal)
- **s7/connection.py**: S7CommPlus low-level connection
- **s7/protocol.py**: S7CommPlus protocol constants/enums
- **s7/codec.py**: S7CommPlus encoding/decoding
- **s7/vlq.py**: Variable-Length Quantity encoding
- **s7/legitimation.py**: Authentication helpers

## Implementation Details

### Protocol Stack
Expand All @@ -41,24 +56,31 @@ The library implements the complete S7 protocol stack:
- Block operations (list, info, upload, download)
- Date/time operations

### Usage
### Usage (unified s7 package — recommended for S7-1200/1500)

```python
from s7 import Client

client = Client()
client.connect("192.168.1.10", 0, 1) # auto-detects S7CommPlus vs legacy
data = client.db_read(1, 0, 4)
client.disconnect()
```

### Usage (legacy snap7 package — S7-300/400)

```python
import snap7

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

# Read/write operations
data = client.db_read(1, 0, 4)
client.db_write(1, 0, bytearray([1, 2, 3, 4]))

# Memory area access
marker_data = client.mb_read(0, 4)
client.mb_write(0, 4, bytearray([1, 2, 3, 4]))

# Disconnect
client.disconnect()
```

Expand Down Expand Up @@ -98,15 +120,15 @@ pytest tests/test_client.py
### Code Quality
```bash
# Type checking
mypy snap7 tests example
mypy snap7 s7 tests example

# Linting and formatting check
ruff check snap7 tests example
ruff format --diff snap7 tests example
ruff check snap7 s7 tests example
ruff format --diff snap7 s7 tests example

# Auto-format code
ruff format snap7 tests example
ruff check --fix snap7 tests example
ruff format snap7 s7 tests example
ruff check --fix snap7 s7 tests example
```

### Development with tox
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ doc: .venv/bin/sphinx-build

.PHONY: check
check: .venv/bin/pytest
uv run ruff check snap7 tests example
uv run ruff format --diff snap7 tests example
uv run ruff check snap7 s7 tests example
uv run ruff format --diff snap7 s7 tests example

.PHONY: ruff
ruff: .venv/bin/tox
Expand Down
93 changes: 66 additions & 27 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,55 +13,94 @@
.. image:: https://readthedocs.org/projects/python-snap7/badge/
:target: https://python-snap7.readthedocs.io/en/latest/

About
=====

Python-snap7 is a pure Python S7 communication library for interfacing with Siemens S7 PLCs.
python-snap7
============

The name "python-snap7" is historical — the library originally started as a Python wrapper
around the `Snap7 <http://snap7.sourceforge.net/>`_ C library. As of version 3.0, the C
library is no longer used, but the name is kept for backwards compatibility.
Python-snap7 is a pure Python S7 communication library for interfacing with
Siemens S7 PLCs. It supports Python 3.10+ and runs on Windows, Linux, and macOS
without any native dependencies.

Python-snap7 is tested with Python 3.10+, on Windows, Linux and OS X.
The name "python-snap7" is historical — the library originally started as a
Python wrapper around the `Snap7 <http://snap7.sourceforge.net/>`_ C library.
As of version 3.0, the C library is no longer used, but the name is kept for
backwards compatibility.

The full documentation is available on `Read The Docs <https://python-snap7.readthedocs.io/en/latest/>`_.


Version 3.0 - Pure Python Rewrite
==================================
Installation
============

Install using pip::

Version 3.0 is a ground-up rewrite of python-snap7. The library no longer wraps the
C snap7 shared library — instead, the entire S7 protocol stack (TPKT, COTP, and S7)
is now implemented in pure Python. This is a **breaking change** from all previous
versions.
$ pip install python-snap7

No native libraries or platform-specific dependencies are required — python-snap7
is a pure Python package that works on all platforms.


Version 3.0 — Pure Python Rewrite
==================================

**Why this matters:**
Version 3.0 was a ground-up rewrite of python-snap7. The library no longer wraps
the C snap7 shared library — instead, the entire S7 protocol stack (TPKT, COTP,
and S7) is implemented in pure Python.

* **Portability**: No more platform-specific shared libraries (`.dll`, `.so`, `.dylib`).
python-snap7 now works on any platform that runs Python — including ARM, Alpine Linux,
and other environments where the C library was difficult or impossible to install.
* **Portability**: No more platform-specific shared libraries (``.dll``, ``.so``, ``.dylib``).
Works on any platform that runs Python — including ARM, Alpine Linux, and other
environments where the C library was difficult or impossible to install.
* **Easier installation**: Just ``pip install python-snap7``. No native dependencies,
no compiler toolchains, no manual library setup.
* **Easier to extend**: New features and protocol support can be added directly in Python.

**If you experience issues with 3.0:**

1. Please report them on the `issue tracker <https://github.com/gijzelaerr/python-snap7/issues>`_
with a clear description of the problem and the version you are using
(``python -c "import snap7; print(snap7.__version__)"``).
1. Please report them on the `issue tracker <https://github.com/gijzelaerr/python-snap7/issues>`_.
2. As a workaround, you can pin to the last pre-3.0 release::

$ pip install "python-snap7<3"

The latest stable pre-3.0 release is version 2.1.0. Documentation for pre-3.0
versions is available at `Read The Docs <https://python-snap7.readthedocs.io/en/v2/>`_.
Documentation for pre-3.0 versions is available at
`Read The Docs <https://python-snap7.readthedocs.io/en/v2/>`_.


Installation
============
Version 3.1 — S7CommPlus Protocol Support (unreleased)
=======================================================

Install using pip::
Version 3.1 adds support for the S7CommPlus protocol (up to V3), which is required
for communicating with newer Siemens S7-1200 and S7-1500 PLCs that have PUT/GET
disabled. This is fully backwards compatible with 3.0.

$ pip install python-snap7
The biggest change is the new ``s7`` module, which is now the recommended entry point
for connecting to any supported S7 PLC::

from s7 import Client

client = Client()
client.connect("192.168.1.10", 0, 1) # auto-detects S7CommPlus vs legacy S7
data = client.db_read(1, 0, 4)
client.disconnect()

The ``s7.Client`` automatically tries S7CommPlus first, and falls back to legacy S7
if the PLC does not support it. The existing ``snap7.Client`` continues to work
unchanged for legacy S7 connections.

**Help us test!** Version 3.1 needs more real-world testing before release. If you
have access to any of the following PLCs, we would greatly appreciate testing and
feedback:

* S7-1200 (any firmware version)
* S7-1500 (any firmware version)
* S7-1500 with TLS enabled
* S7-300
* S7-400
* S7-1200/1500 with PUT/GET disabled (S7CommPlus-only)
* LOGO! 0BA8 and newer

Please report your results — whether it works or not — on the
`issue tracker <https://github.com/gijzelaerr/python-snap7/issues>`_.

To install the development version::

No native libraries or platform-specific dependencies are required — python-snap7 is a pure Python package that works on all platforms.
$ pip install git+https://github.com/gijzelaerr/python-snap7.git@master
82 changes: 31 additions & 51 deletions doc/API/s7commplus.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,21 @@ S7CommPlus (S7-1200/1500)
releases. If you encounter problems, please `open an issue
<https://github.com/gijzelaerr/python-snap7/issues>`_.

The :mod:`snap7.s7commplus` package provides support for Siemens S7-1200 and
S7-1500 PLCs, which use the S7CommPlus protocol instead of the classic S7
protocol used by S7-300/400.

Both synchronous and asynchronous clients are available. When a PLC does not
support S7CommPlus data operations, the clients automatically fall back to the
legacy S7 protocol transparently.
The ``s7`` package provides a unified client for Siemens S7-1200 and S7-1500
PLCs. It automatically tries the S7CommPlus protocol first and falls back to
the legacy S7 protocol when needed.

Synchronous client
------------------

.. code-block:: python

from snap7.s7commplus.client import S7CommPlusClient
from s7 import Client

client = S7CommPlusClient()
client.connect("192.168.1.10")
client = Client()
client.connect("192.168.1.10", 0, 1)
data = client.db_read(1, 0, 4)
print(client.protocol) # Protocol.S7COMMPLUS or Protocol.LEGACY
client.disconnect()

Asynchronous client
Expand All @@ -33,11 +30,11 @@ Asynchronous client
.. code-block:: python

import asyncio
from snap7.s7commplus.async_client import S7CommPlusAsyncClient
from s7 import AsyncClient

async def main():
client = S7CommPlusAsyncClient()
await client.connect("192.168.1.10")
client = AsyncClient()
await client.connect("192.168.1.10", 0, 1)
data = await client.db_read(1, 0, 4)
await client.disconnect()

Expand All @@ -51,10 +48,10 @@ S7-1500 PLCs with firmware 2.x use S7CommPlus V2, which requires TLS. Pass

.. code-block:: python

from snap7.s7commplus.client import S7CommPlusClient
from s7 import Client

client = S7CommPlusClient()
client.connect("192.168.1.10", use_tls=True)
client = Client()
client.connect("192.168.1.10", 0, 1, use_tls=True)
data = client.db_read(1, 0, 4)
client.disconnect()

Expand All @@ -63,7 +60,7 @@ For PLCs with custom certificates, provide the certificate paths:
.. code-block:: python

client.connect(
"192.168.1.10",
"192.168.1.10", 0, 1,
use_tls=True,
tls_cert="/path/to/client.pem",
tls_key="/path/to/client.key",
Expand All @@ -73,56 +70,39 @@ For PLCs with custom certificates, provide the certificate paths:
Password authentication
-----------------------

Password-protected PLCs require authentication after connecting. Call
``authenticate()`` before performing data operations:
Password-protected PLCs require the ``password`` keyword argument:

.. code-block:: python

from snap7.s7commplus.client import S7CommPlusClient

client = S7CommPlusClient()
client.connect("192.168.1.10", use_tls=True)
client.authenticate(password="my_plc_password")
from s7 import Client

client = Client()
client.connect("192.168.1.10", 0, 1, use_tls=True, password="my_plc_password")
data = client.db_read(1, 0, 4)
client.disconnect()

The method auto-detects whether to use legacy (SHA-1 XOR) or new-style
(AES-256-CBC) authentication based on the PLC firmware version. For new-style
authentication, you can also provide a username:

.. code-block:: python

client.authenticate(password="my_password", username="admin")

.. note::

Authentication requires TLS to be active. Calling ``authenticate()``
without ``use_tls=True`` will raise :class:`~snap7.error.S7ConnectionError`.
Protocol selection
------------------

By default the client uses ``Protocol.AUTO`` which tries S7CommPlus first.
You can force a specific protocol:

Legacy fallback
---------------
.. code-block:: python

If the PLC returns an error for S7CommPlus data operations (common with some
firmware versions), the client automatically falls back to the classic S7
protocol. You can check whether fallback is active:
from s7 import Client, Protocol

.. code-block:: python
# Force legacy S7 only
client = Client()
client.connect("192.168.1.10", 0, 1, protocol=Protocol.LEGACY)

client.connect("192.168.1.10")
if client.using_legacy_fallback:
print("Using legacy S7 protocol")
# Force S7CommPlus (raises on failure)
client.connect("192.168.1.10", 0, 1, protocol=Protocol.S7COMMPLUS)

API reference
-------------

.. automodule:: snap7.s7commplus.client
:members:

.. automodule:: snap7.s7commplus.async_client
.. automodule:: s7.client
:members:

.. automodule:: snap7.s7commplus.connection
.. automodule:: s7.async_client
:members:
:exclude-members: S7CommPlusConnection
14 changes: 14 additions & 0 deletions doc/connecting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@ S7-1200 / S7-1500
client = snap7.Client()
client.connect("192.168.1.10", 0, 1)

.. tip::

For S7-1200/1500 PLCs you can also use the **experimental** ``s7`` package,
which automatically tries the newer S7CommPlus protocol and falls back to
legacy S7 when needed::

from s7 import Client

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

See :doc:`API/s7commplus` for full details.

S7-200 / Logo (TSAP Connection)
--------------------------------

Expand Down
Loading
Loading