Skip to content

Commit 22a5c08

Browse files
gijzelaerrclaude
andcommitted
fix: add SequenceNumber to Get/SetMultiVariables payloads (#738)
S7-1200 FW V4.5 requires a VLQ-encoded SequenceNumber field after the ObjectQualifier in all Get/SetMultiVariables requests. Without it the PLC rejects with error 11862009. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8c22db4 commit 22a5c08

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

s7/_s7commplus_client.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,7 @@ def _build_read_payload(items: list[tuple[int, int, int]]) -> bytes:
484484
for addr in addresses:
485485
payload += addr
486486
payload += encode_object_qualifier()
487+
payload += encode_uint32_vlq(1)
487488
payload += struct.pack(">I", 0)
488489

489490
return bytes(payload)
@@ -570,6 +571,7 @@ def _build_write_payload(items: list[tuple[int, int, bytes]]) -> bytes:
570571
payload += encode_pvalue_blob(data)
571572
payload += bytes([0x00])
572573
payload += encode_object_qualifier()
574+
payload += encode_uint32_vlq(1)
573575
payload += struct.pack(">I", 0)
574576

575577
return bytes(payload)
@@ -622,6 +624,7 @@ def _build_area_read_payload(area_rid: int, start: int, size: int) -> bytes:
622624
payload += encode_uint32_vlq(field_count)
623625
payload += addr_bytes
624626
payload += encode_object_qualifier()
627+
payload += encode_uint32_vlq(1)
625628
payload += struct.pack(">I", 0)
626629
return bytes(payload)
627630

@@ -643,6 +646,7 @@ def _build_area_write_payload(area_rid: int, start: int, data: bytes) -> bytes:
643646
payload += encode_pvalue_blob(data)
644647
payload += bytes([0x00])
645648
payload += encode_object_qualifier()
649+
payload += encode_uint32_vlq(1)
646650
payload += struct.pack(">I", 0)
647651
return bytes(payload)
648652

@@ -675,6 +679,7 @@ def _build_symbolic_read_payload(access_area: int, lids: list[int], symbol_crc:
675679
payload += encode_uint32_vlq(field_count)
676680
payload += addr_bytes
677681
payload += encode_object_qualifier()
682+
payload += encode_uint32_vlq(1)
678683
payload += struct.pack(">I", 0)
679684
return bytes(payload)
680685

@@ -702,6 +707,7 @@ def _build_symbolic_write_payload(access_area: int, lids: list[int], data: bytes
702707
payload += encode_pvalue_blob(data)
703708
payload += bytes([0x00])
704709
payload += encode_object_qualifier()
710+
payload += encode_uint32_vlq(1)
705711
payload += struct.pack(">I", 0)
706712
return bytes(payload)
707713

tests/test_s7_unit.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@
99
_parse_read_response,
1010
_build_write_payload,
1111
_parse_write_response,
12+
_build_area_read_payload,
13+
_build_area_write_payload,
14+
_build_symbolic_read_payload,
15+
_build_symbolic_write_payload,
1216
)
13-
from s7.codec import encode_pvalue_blob
17+
from s7.codec import encode_object_qualifier, encode_pvalue_blob
1418
from s7.connection import S7CommPlusConnection, _element_size
1519
from s7.protocol import DataType, ElementID, ObjectId
1620
from s7.vlq import (
@@ -175,6 +179,42 @@ def test_write_read_consistency(self) -> None:
175179
assert isinstance(write_payload, bytes)
176180

177181

182+
class TestSequenceNumber:
183+
"""Verify all payload builders include a SequenceNumber after ObjectQualifier."""
184+
185+
@staticmethod
186+
def _has_sequence_number(payload: bytes) -> bool:
187+
oq = encode_object_qualifier()
188+
idx = bytes(payload).find(oq)
189+
assert idx >= 0, "ObjectQualifier not found in payload"
190+
seq_offset = idx + len(oq)
191+
return payload[seq_offset : seq_offset + 1] == encode_uint32_vlq(1)
192+
193+
def test_read_payload_has_sequence_number(self) -> None:
194+
payload = _build_read_payload([(1, 0, 4)])
195+
assert self._has_sequence_number(payload)
196+
197+
def test_write_payload_has_sequence_number(self) -> None:
198+
payload = _build_write_payload([(1, 0, bytes([1, 2, 3, 4]))])
199+
assert self._has_sequence_number(payload)
200+
201+
def test_area_read_payload_has_sequence_number(self) -> None:
202+
payload = _build_area_read_payload(82, 0, 4)
203+
assert self._has_sequence_number(payload)
204+
205+
def test_area_write_payload_has_sequence_number(self) -> None:
206+
payload = _build_area_write_payload(82, 0, b"\x00\x00\x00\x00")
207+
assert self._has_sequence_number(payload)
208+
209+
def test_symbolic_read_payload_has_sequence_number(self) -> None:
210+
payload = _build_symbolic_read_payload(0x8A0E0001, [1, 4])
211+
assert self._has_sequence_number(payload)
212+
213+
def test_symbolic_write_payload_has_sequence_number(self) -> None:
214+
payload = _build_symbolic_write_payload(0x8A0E0001, [1, 4], b"\x01")
215+
assert self._has_sequence_number(payload)
216+
217+
178218
# -- Connection unit tests --
179219

180220

0 commit comments

Comments
 (0)