From 03f7d0b529234279b524449a53c7245e40160fd0 Mon Sep 17 00:00:00 2001 From: Gijs Molenaar Date: Tue, 18 Aug 2026 14:51:21 +0200 Subject: [PATCH] feat(s7commplus): add multi-write APIs --- s7commplus/async_client.py | 10 +++++++++- s7commplus/client.py | 14 +++++++++++++- tests/test_s7_server.py | 33 +++++++++++++++++++++++++++++++++ tests/test_s7_unit.py | 10 ++++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) diff --git a/s7commplus/async_client.py b/s7commplus/async_client.py index f632365b..06f8e7c6 100644 --- a/s7commplus/async_client.py +++ b/s7commplus/async_client.py @@ -451,10 +451,18 @@ async def db_read(self, db_number: int, start: int, size: int) -> bytes: async def db_write(self, db_number: int, start: int, data: bytes) -> None: """Write raw bytes to a data block.""" - payload = _build_write_payload([(db_number, start, data)]) + await self.db_write_multi([(db_number, start, data)]) + + async def db_write_multi(self, items: list[tuple[int, int, bytes]]) -> None: + """Write multiple data block regions in a single request.""" + payload = _build_write_payload(items) response = await self._send_request(FunctionCode.SET_MULTI_VARIABLES, payload) _parse_write_response(response) + async def write_multi(self, items: list[tuple[int, int, bytes]]) -> None: + """Alias for :meth:`db_write_multi`.""" + await self.db_write_multi(items) + async def db_read_multi(self, items: list[tuple[int, int, int]]) -> list[bytes]: """Read multiple data block regions in a single request.""" payload = _build_read_payload(items) diff --git a/s7commplus/client.py b/s7commplus/client.py index 7bef905a..2836b382 100644 --- a/s7commplus/client.py +++ b/s7commplus/client.py @@ -137,13 +137,25 @@ def db_write(self, db_number: int, start: int, data: bytes) -> None: start: Start byte offset data: Bytes to write """ + self.db_write_multi([(db_number, start, data)]) + + def db_write_multi(self, items: list[tuple[int, int, bytes]]) -> None: + """Write multiple data block regions in a single request. + + Args: + items: List of ``(db_number, start_offset, data)`` tuples. + """ if self._connection is None: raise RuntimeError("Not connected") - payload = _build_write_payload([(db_number, start, data)]) + payload = _build_write_payload(items) response = self._connection.send_request(FunctionCode.SET_MULTI_VARIABLES, payload) _parse_write_response(response) + def write_multi(self, items: list[tuple[int, int, bytes]]) -> None: + """Alias for :meth:`db_write_multi`.""" + self.db_write_multi(items) + def db_read_multi(self, items: list[tuple[int, int, int]]) -> list[bytes]: """Read multiple data block regions in a single request. diff --git a/tests/test_s7_server.py b/tests/test_s7_server.py index 1ba92bee..3073954f 100644 --- a/tests/test_s7_server.py +++ b/tests/test_s7_server.py @@ -207,6 +207,24 @@ def test_multi_read(self, server: S7CommPlusServer) -> None: finally: client.disconnect() + def test_multi_write(self, server: S7CommPlusServer) -> None: + client = S7CommPlusClient() + client.connect("127.0.0.1", port=TEST_PORT) + try: + client.db_write_multi( + [ + (1, 0, b"first"), + (1, 10, b"second"), + (2, 20, b"third"), + ] + ) + + assert client.db_read(1, 0, 5) == b"first" + assert client.db_read(1, 10, 6) == b"second" + assert client.db_read(2, 20, 5) == b"third" + finally: + client.disconnect() + def test_explore(self, server: S7CommPlusServer) -> None: client = S7CommPlusClient() client.connect("127.0.0.1", port=TEST_PORT) @@ -304,6 +322,21 @@ async def test_multi_read(self, server: S7CommPlusServer) -> None: temp = struct.unpack(">f", results[0])[0] assert abs(temp - 23.5) < 0.1 # May be modified by earlier test + async def test_multi_write(self, server: S7CommPlusServer) -> None: + async with S7CommPlusAsyncClient() as client: + await client.connect("127.0.0.1", port=TEST_PORT) + await client.write_multi( + [ + (1, 0, b"alpha"), + (1, 10, b"beta"), + (2, 20, b"gamma"), + ] + ) + + assert await client.db_read(1, 0, 5) == b"alpha" + assert await client.db_read(1, 10, 4) == b"beta" + assert await client.db_read(2, 20, 5) == b"gamma" + async def test_explore(self, server: S7CommPlusServer) -> None: async with S7CommPlusAsyncClient() as client: await client.connect("127.0.0.1", port=TEST_PORT) diff --git a/tests/test_s7_unit.py b/tests/test_s7_unit.py index 1c03ffc8..57c03862 100644 --- a/tests/test_s7_unit.py +++ b/tests/test_s7_unit.py @@ -501,6 +501,16 @@ def test_db_read_multi_not_connected(self) -> None: with pytest.raises(RuntimeError, match="Not connected"): client.db_read_multi([(1, 0, 4)]) + def test_db_write_multi_not_connected(self) -> None: + client = S7CommPlusClient() + with pytest.raises(RuntimeError, match="Not connected"): + client.db_write_multi([(1, 0, b"data")]) + + def test_write_multi_not_connected(self) -> None: + client = S7CommPlusClient() + with pytest.raises(RuntimeError, match="Not connected"): + client.write_multi([(1, 0, b"data")]) + def test_explore_not_connected(self) -> None: client = S7CommPlusClient() with pytest.raises(RuntimeError, match="Not connected"):