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
2 changes: 0 additions & 2 deletions .gitattributes

This file was deleted.

1 change: 0 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ repos:
- id: check-symlinks
- id: check-toml
- id: check-xml
exclude: ^s7commplus/zlib_dicts/
- id: check-yaml
- id: check-illegal-windows-names
- id: check-merge-conflict
Expand Down
92 changes: 42 additions & 50 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,55 +1,48 @@
CHANGES
========

4.0.0 (unreleased)
-------------------

Major release: new `s7commplus` package with S7CommPlus protocol support.

* Return S7 item errors when pure-Python server reads target unregistered areas
or ranges outside registered memory instead of fabricating successful data.
* Echo calling and called TSAP parameters in pure-Python server connection
confirmations, and restart the receive deadline after each TPKT header.
* Correlate S7CommPlus responses by opcode, function, and sequence; discard
bounded stale replies from earlier requests, preserve interleaved
notifications, and serialize synchronous wire requests.
* Decode corroborating CPU execution attributes so S7CommPlus `get_cpu_state()`
distinguishes RUN from STOP on S7-1500 and returns UNKNOWN for absent or
inconsistent state attributes, including S7-1200 responses that omit them.
* Support LOGO reconnection/heartbeat options and preserve explicit TSAPs.
* Correct legacy GetVarSubStreamed qualifiers and reject unusable authentication challenges.
* Validate batched symbolic read item coverage and preserve explicit PLC errors.
* Fix classic S7 TPKT bounds and COTP Class 0 header validation in sync/async clients.
* Reject incomplete reads and mismatched or missing read/write acknowledgements.
* Size read/write chunks in whole elements and preserve ctypes write datatypes;
correct BIT lengths and timer/counter index addressing.

* New `s7commplus` package for S7CommPlus protocol (S7-1200/1500)
* S7CommPlus V1, V2 (TLS), and V3 support for S7-1200/1500
* S7CommPlus area read/write (M, I, Q, counters, timers)
* Require an explicit target datatype in S7CommPlus multi-write tuples; add
`datatype=` to sync/async symbolic and area writes for scalar PLC targets.
* S7CommPlus PLC start/stop via INVOKE
* S7CommPlus object browsing via EXPLORE
* S7CommPlus live symbol browsing (`client.browse()`) and datablock listing (experimental)
* Fix V1 SessionKey challenge requests being rejected by S7-1200 FW 4.2 PLCs,
consume non-fatal SystemEvents while waiting for the matching response, and
strip per-fragment V3 HMACs from browse responses (#710)
* S7CommPlus active-alarm browsing and alarm subscriptions (experimental)
* S7CommPlus symbolic data subscriptions and notification decoding (experimental)
* TIA Portal XML import for SymbolTable (`SymbolTable.from_tia_xml()`) (experimental)
* S7CommPlus CPU state reading and block transfer (upload/download)
* Correct the SessionKey emulator fingerprint encoding and reject missing or
malformed authentication structures during integration tests.
* Fix the legacy SecurityKey descriptor to identify the newly generated
session key instead of an all-zero placeholder.
* Keep SessionKey activation and public connection state pending until the PLC
accepts session setup, with complete cleanup on rejection or transport error.
* **Symbolic (LID-based) access for optimized DBs** (experimental):
`Tag.from_access_string("8A0E0001.A", "REAL")` creates a symbolic Tag;
`client.read_tag(tag)` routes to S7CommPlus LID-based access via the
PLC's symbol tree. Required for S7-1200/1500 DBs with
"Optimized block access" enabled (the TIA Portal V13+ default).
3.2.0
-----

Feature and robustness release for the classic S7 protocol implementation.

### Packaging

* Move S7CommPlus support to the standalone
[`s7commplus`](https://github.com/gijzelaerr/s7commplus) package. The `s7`
import remains available as an alias for the classic `snap7` package.

### New features

* Add configurable request rate limiting to synchronous and asynchronous
clients, including fixed-interval and token-bucket modes (#823).
* Add synchronous and asynchronous APIs for forcing I/O bits, cancelling
forces, and reading the PLC force table (#796, #800).
* Add synchronous and asynchronous session password set/clear APIs for
password-protected PLCs (#792, #799).
* Support LOGO reconnection, heartbeat, rate-limiting, and connection callback
options while preserving explicit TSAPs (#875).
* Add an experimental serial PPI client for S7-200 PLCs, including V-memory,
system-memory, I/O, counter, and timer access (#824).

### Bug fixes

* Parse structured and flat SZL 0x0011 records correctly when reading order
codes and firmware versions from S7-1500 and classic PLCs (#783, #789).
* Dispatch server callbacks and queue protocol events for client requests
instead of reporting only the server start event (#853, #856).
* Validate classic S7 TPKT bounds and COTP Class 0 headers in synchronous and
asynchronous clients (#874).
* Reject incomplete reads and mismatched or missing read/write
acknowledgements (#874).
* Size read/write chunks in whole elements and preserve ctypes write
datatypes; correct BIT lengths and timer/counter index addressing (#874).
* Validate `write_multi_vars()` inputs consistently and preserve each item's
declared datatype (#855, #874).
* Echo the calling and called TSAP values in server connection confirmations,
and restart the receive deadline after each complete TPKT header (#893).
* Return S7 item errors for reads from unregistered server areas or addresses
outside registered memory instead of returning fabricated data (#896).

3.1.2
-----
Expand Down Expand Up @@ -166,7 +159,6 @@ Feature and bug fix release for the pure Python S7 communication library.

### Thanks

* [@bonk-dev](https://github.com/bonk-dev) — [HarpoS7](https://github.com/bonk-dev/HarpoS7): the session authentication implementation in `s7commplus/session_auth/` is a Python port of HarpoS7 (MIT license, see `s7commplus/session_auth/LICENSE-HarpoS7`)
* [@hs2bws-hash](https://github.com/hs2bws-hash) — extensive real PLC testing of Partner BSend/BRecv (#668)
* [@QuakeString](https://github.com/QuakeString) — read optimizer inspiration via python-snap7-optimized fork

Expand Down
3 changes: 0 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,3 @@ Documentation

Read the full documentation at
`python-snap7.readthedocs.io <https://python-snap7.readthedocs.io/en/latest/>`_.

For native communication with S7-1200 and S7-1500 controllers without PUT/GET,
see the standalone `s7commplus <https://github.com/gijzelaerr/s7commplus>`_ package.
9 changes: 9 additions & 0 deletions doc/API/client-base.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Shared Client Internals
=======================

Pure-computation and compatibility methods shared by ``Client`` and
``AsyncClient``. Applications normally access these methods through one of the
concrete clients.

.. automodule:: snap7.client_base
:members:
27 changes: 27 additions & 0 deletions doc/API/client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,42 @@ Client
The ``s7`` package implements the classic S7 protocol for S7-300/400 PLCs and
PUT/GET access on S7-1200/1500.

Use ``Client`` for synchronous applications::

from s7 import Client

with Client() as client:
client.connect("192.168.1.10", 0, 1)
data = client.db_read(1, 0, 4)

Use ``AsyncClient`` when the surrounding application uses ``asyncio``::

import asyncio
from s7 import AsyncClient

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

asyncio.run(main())

Both clients expose the same main PLC operations, including area and DB
access, block transfer, CPU information and control, clock access, force
operations, and session passwords. Methods that perform I/O are coroutines on
``AsyncClient``. See :doc:`../advanced` for the less common operations.

s7.Client
---------

.. automodule:: snap7.client
:members:
:inherited-members:

s7.AsyncClient
--------------

.. automodule:: snap7.async_client
:members:
:inherited-members:
:exclude-members: AsyncISOTCPConnection
9 changes: 9 additions & 0 deletions doc/API/demo.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Demo Server
===========

Implementation of the optional live host-metrics demo used by ``s7 demo``.
Install ``python-snap7[demo]`` before running it. This module is intended for
examples and integration testing, not production monitoring.

.. automodule:: snap7.demo
:members:
10 changes: 10 additions & 0 deletions doc/API/ppi.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
PPI
===

Serial PPI transport and client support for S7-200 PLCs. Install the optional
dependency with ``pip install "python-snap7[ppi]"`` and see :doc:`../ppi` for
setup, area semantics, and current limitations.

.. automodule:: snap7.ppi
:members:
:show-inheritance:
10 changes: 10 additions & 0 deletions doc/API/rate-limiter.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Rate Limiter
============

The synchronous and asynchronous clients create a limiter from their
``max_requests_per_second`` and ``rate_limit_*`` constructor arguments. Most
applications should configure the client instead of constructing this class
directly.

.. automodule:: snap7.rate_limiter
:members:
8 changes: 8 additions & 0 deletions doc/API/szl.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
SZL Parsers
===========

Helpers for decoding System Status List records returned by
``Client.read_szl()`` and ``AsyncClient.read_szl()``.

.. automodule:: snap7.szl
:members:
59 changes: 0 additions & 59 deletions doc/API/tags.rst
Original file line number Diff line number Diff line change
Expand Up @@ -90,65 +90,6 @@ Supported types

Arrays are supported for any fixed-size type via ``[count]`` suffix.

Optimized block access (S7CommPlus)
------------------------------------

.. warning::

Symbolic (LID-based) access is **experimental** and requires real PLC
testing. The wire-level implementation follows the S7CommPlusDriver
reference but has not yet been validated against hardware.

S7-1200/1500 DBs with "Optimized block access" enabled (the default in
TIA Portal V13+) do not use fixed byte offsets. The PLC internally
relocates variables between downloads, so addresses like ``DB1.DBX0.0``
are unreliable.

For optimized blocks, use the low-level ``read_symbolic`` and
``write_symbolic`` methods with access paths discovered via
:meth:`~s7commplus.client.S7CommPlusClient.browse`. A typed, name-based
S7CommPlus tag API is not implemented yet; :class:`~snap7.tags.Tag` and the
``read_tag`` methods above belong to the classic ``s7.Client`` API.

.. code-block:: python

import struct

from s7commplus import Client
from s7commplus.protocol import DataType

client = Client()
client.connect("192.168.1.10", use_tls=True)

variables = client.browse()
speed_info = next(item for item in variables if item["name"] == "Motor.Speed")
path = [int(part, 16) for part in speed_info["access_sequence"].split(".")]

# Symbolic values are currently exposed as raw wire bytes.
raw = client.read_symbolic(path[0], path[1:])
speed = struct.unpack(">f", raw)[0]
client.write_symbolic(path[0], path[1:], struct.pack(">f", 1500.0), datatype=DataType.REAL)

The write datatype must match the PLC variable's ``data_type`` from browsing.
For example, an INT requires ``struct.pack(">h", value)`` and
``datatype=DataType.INT``. BLOB is not a generic scalar datatype. The
``datatype=`` keyword is supported by both sync and async symbolic and area
writes; omitting it preserves the legacy BLOB encoding.

Multi-write requires four-element tuples with an explicit datatype. The
three-element form from development versions is rejected before sending:

.. code-block:: python

client.db_write_multi([
(7, 0, struct.pack(">f", 2.0), DataType.REAL),
(7, 4, struct.pack(">H", 512), DataType.WORD),
])

These DB/offset addresses must be validated for the target PLC; they are not
interchangeable with browse-derived symbolic paths. Explicit BLOB remains
available for targets that accept it, including the raw-byte emulator.

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

Expand Down
9 changes: 9 additions & 0 deletions doc/API/util.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,12 @@ REAL, STRING, etc.):

.. automodule:: snap7.util
:members:

DB layout helpers
-----------------

``DB`` and ``Row`` map a textual DB layout specification onto a byte buffer.
They are also exported directly from ``s7`` and ``snap7``.

.. automodule:: snap7.util.db
:members:
90 changes: 90 additions & 0 deletions doc/advanced.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
Advanced PLC Operations
=======================

The client API includes PLC management operations in addition to memory reads
and writes. Availability depends on the PLC family, firmware, protection
level, and current CPU state. A PLC can reject a valid request that it does not
implement.

The examples below use :class:`~snap7.client.Client`. The corresponding
:class:`~snap7.async_client.AsyncClient` methods have the same purpose and must
be awaited.

Identification and diagnostics
------------------------------

Read common identification and status records after connecting::

order_code = client.get_order_code()
cpu_info = client.get_cpu_info()
cp_info = client.get_cp_info()
protection = client.get_protection()
state = client.get_cpu_state()
diagnostics = client.read_diagnostic_buffer()

For records without a convenience method, use ``read_szl(szl_id, index)`` or
``read_szl_list()``. SZL layouts vary between PLC generations; the typed
helpers in :mod:`snap7.szl` parse the layouts used by the convenience methods.

CPU state and clock
-------------------

``plc_stop()``, ``plc_hot_start()``, and ``plc_cold_start()`` change CPU state.
Clock operations are provided by ``get_plc_datetime()``,
``set_plc_datetime()``, and ``set_plc_system_datetime()``.

.. warning::

CPU control and clock changes affect a real controller. Confirm that the
operation is safe for the machine and process before issuing it.

Block operations
----------------

Use ``list_blocks()`` and ``list_blocks_of_type()`` to enumerate blocks,
``get_block_info()`` to inspect one, ``upload()`` or ``full_upload()`` to read
one, and ``download()`` or ``delete()`` to modify the PLC. ``compress()`` and
``copy_ram_to_rom()`` expose the corresponding PLC maintenance operations.

These methods transfer individual PLC blocks; they do not create a complete
TIA Portal project or full PLC backup.

Session passwords
-----------------

For classic S7 CPUs configured with a session password, authenticate after
connecting and clear the password before disconnecting::

client.connect("192.168.1.10", 0, 2)
client.set_session_password("secret")
try:
data = client.db_read(1, 0, 4)
finally:
client.clear_session_password()
client.disconnect()

Forcing inputs and outputs
--------------------------

``force_bit()`` and ``cancel_force()`` support the ``Area.PE`` input and
``Area.PA`` output areas. ``read_force_table()`` returns active force entries
when the PLC supports SZL 0x0025::

from s7.type import Area

client.force_bit(Area.PA, byte_offset=0, bit=1, value=True)
active_forces = client.read_force_table()
client.cancel_force(Area.PA, byte_offset=0, bit=1)

.. warning::

A force overrides normal process behavior and can remain active after the
application exits. Track every applied force and cancel it deliberately.

Raw protocol and compatibility operations
-----------------------------------------

``iso_exchange_buffer()`` sends a caller-provided S7 PDU. The ``as_*`` methods
and completion callbacks preserve compatibility with the historical Snap7 API;
new asynchronous applications should normally use ``AsyncClient`` instead.
See :doc:`API/client` for the complete method reference.
Loading
Loading