From 15e56432d36cded79b869593de54a8b729d243fe Mon Sep 17 00:00:00 2001 From: Gijs Molenaar Date: Wed, 19 Aug 2026 08:49:10 +0200 Subject: [PATCH] docs: correct S7CommPlus support guidance --- doc/API/client.rst | 11 ++++++++++- doc/API/tags.rst | 29 +++++++++++++++-------------- doc/connecting.rst | 32 ++++++++++++++++++++------------ doc/limitations.rst | 15 +++++++++------ 4 files changed, 54 insertions(+), 33 deletions(-) diff --git a/doc/API/client.rst b/doc/API/client.rst index 15b6a811..110a553a 100644 --- a/doc/API/client.rst +++ b/doc/API/client.rst @@ -21,6 +21,10 @@ s7commplus.Client s7commplus.AsyncClient ---------------------- +The asynchronous client currently supports the TLS connection and +legitimation path. Legacy V1 SessionKey authentication is available only on +the synchronous client. + .. code-block:: python import asyncio @@ -64,7 +68,8 @@ For PLCs with custom certificates, provide the certificate paths: Password authentication ----------------------- -Password-protected PLCs require the ``password`` keyword argument: +The synchronous client accepts the ``password`` keyword for both TLS +legitimation and the post-SessionKey exchange used by older V1 PLCs: .. code-block:: python @@ -75,6 +80,10 @@ Password-protected PLCs require the ``password`` keyword argument: data = client.db_read(1, 0, 4) client.disconnect() +For a V1 PLC, omit ``use_tls=True``. The asynchronous client has no +``password`` argument on ``connect``; on a TLS connection, authenticate +explicitly with ``await client.authenticate(password)``. + Concurrent async reads ---------------------- diff --git a/doc/API/tags.rst b/doc/API/tags.rst index 630adbb6..cddce191 100644 --- a/doc/API/tags.rst +++ b/doc/API/tags.rst @@ -104,28 +104,29 @@ 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 :meth:`~snap7.tags.Tag.from_access_string` -with LIDs discovered via :meth:`~s7commplus.client.S7CommPlusClient.browse`: +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 s7.tags import Tag client = Client() client.connect("192.168.1.10") - # Create a symbolic tag (LIDs come from browse) - tag = Tag.from_access_string( - "8A0E0001.A", # DB1, LID 0xA - datatype="REAL", - name="Motor.Speed", - symbol_crc=0x12345678, # optional layout version check - ) - - # Read/write via S7CommPlus symbolic access - speed = client.read_tag(tag) - client.write_tag(tag, 1500.0) + 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)) API reference ------------- diff --git a/doc/connecting.rst b/doc/connecting.rst index 96b428ff..96455b87 100644 --- a/doc/connecting.rst +++ b/doc/connecting.rst @@ -141,11 +141,13 @@ with the ``s7commplus`` extra: .. 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. + protocol, which predates TLS and uses a SessionKey handshake instead. + The synchronous ``Client`` performs this handshake when ``use_tls`` is + false; it does not fall back to the classic PUT/GET protocol. V1 support + is experimental and the asynchronous client currently supports only the + TLS path. See `issue #710 + `_ for the original + V1 hardware report. TLS handshake rejected by the PLC (connection reset) ------------------------------------------------------ @@ -207,7 +209,9 @@ PLC Password Authentication ---------------------------- If the PLC has a password configured (``Full access (no protection)`` -disabled in TIA Portal), call ``authenticate`` after ``connect``: +disabled in TIA Portal), pass it to the synchronous client's ``connect`` +method. The client applies it to either the legacy SessionKey flow or the +TLS legitimation flow, as appropriate for the PLC: .. code-block:: python @@ -217,15 +221,19 @@ disabled in TIA Portal), call ``authenticate`` after ``connect``: client.connect( "192.168.1.10", use_tls=True, + password="hunter2", ) - 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: +For an older V1 PLC, omit ``use_tls=True``; the password is then used by the +post-SessionKey legitimation exchange. The asynchronous client currently +supports password authentication only on TLS connections, using +``await client.authenticate(...)`` after connecting. + +The explicit synchronous ``authenticate`` method is also TLS-only. It +auto-detects whether the TLS PLC 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: .. code-block:: python diff --git a/doc/limitations.rst b/doc/limitations.rst index c10b75fa..19f4544c 100644 --- a/doc/limitations.rst +++ b/doc/limitations.rst @@ -1,8 +1,9 @@ Protocol Limitations and FAQ ============================ -python-snap7 implements the S7 protocol over TCP/IP. The following operations -are **not possible** with this protocol: +python-snap7 implements both the classic S7 protocol and S7CommPlus over +TCP/IP. The following limitations apply to the classic protocol exposed by +``s7.Client``; the native S7CommPlus client has different capabilities: .. list-table:: :header-rows: 1 @@ -11,11 +12,13 @@ are **not possible** with this protocol: * - Limitation - Explanation * - Read tag/symbol names from PLC - - Symbol names exist only in the TIA Portal project file, not in the PLC. - The S7 protocol only addresses data by area, DB number, and byte offset. + - The classic S7 protocol only addresses data by area, DB number, and byte + offset. The experimental ``s7commplus.Client.browse()`` API can read the + symbol tree from supported S7-1200/1500 PLCs. * - Get DB structure or layout from PLC - - The PLC stores only raw bytes. The structure definition lives in the TIA - Portal project. You must define your data layout in your Python code. + - Classic S7 reads DBs as raw bytes, so callers must supply the layout. + S7CommPlus browsing can reconstruct type information and optimized + symbolic access paths on supported S7-1200/1500 PLCs. * - Discover PLCs on the network - The classic S7 protocol has no broadcast discovery mechanism. However, python-snap7 provides PROFINET DCP discovery via the ``s7 discover``