Skip to content

Commit

Permalink
Allow socket frames to be split in multiple packets (#1923)
Browse files Browse the repository at this point in the history
  • Loading branch information
janiversen authored Jan 4, 2024
1 parent 81df5b1 commit 6913e9c
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 34 deletions.
55 changes: 24 additions & 31 deletions pymodbus/framer/socket_framer.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,21 @@ def checkFrame(self):
Return true if we were successful.
"""
if self.isFrameReady():
(
self._header["tid"],
self._header["pid"],
self._header["len"],
self._header["uid"],
) = struct.unpack(">HHHB", self._buffer[0 : self._hsize])

# someone sent us an error? ignore it
if self._header["len"] < 2:
self.advanceFrame()
# we have at least a complete message, continue
elif len(self._buffer) - self._hsize + 1 >= self._header["len"]:
return True
if not self.isFrameReady():
return False
(
self._header["tid"],
self._header["pid"],
self._header["len"],
self._header["uid"],
) = struct.unpack(">HHHB", self._buffer[0 : self._hsize])

# someone sent us an error? ignore it
if self._header["len"] < 2:
self.advanceFrame()
# we have at least a complete message, continue
elif len(self._buffer) - self._hsize + 1 >= self._header["len"]:
return True
# we don't have enough of a message yet, wait
return False

Expand Down Expand Up @@ -128,23 +129,15 @@ def frameProcessIncomingPacket(self, single, callback, slave, tid=None, **kwargs
The processed and decoded messages are pushed to the callback
function to process and send.
"""
while True:
if not self.isFrameReady():
if len(self._buffer):
# Possible error ???
if self._header["len"] < 2:
self._process(callback, tid, error=True)
break
if not self.checkFrame():
Log.debug("Frame check failed, ignoring!!")
self.resetFrame()
continue
if not self._validate_slave_id(slave, single):
header_txt = self._header["uid"]
Log.debug("Not a valid slave id - {}, ignoring!!", header_txt)
self.resetFrame()
continue
self._process(callback, tid)
if not self.checkFrame():
Log.debug("Frame check failed, ignoring!!")
return
if not self._validate_slave_id(slave, single):
header_txt = self._header["uid"]
Log.debug("Not a valid slave id - {}, ignoring!!", header_txt)
self.resetFrame()
return
self._process(callback, tid)

def _process(self, callback, tid, error=False):
"""Process incoming packets irrespective error condition."""
Expand Down
34 changes: 31 additions & 3 deletions test/test_framers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
from pymodbus.client.base import ModbusBaseClient
from pymodbus.exceptions import ModbusIOException
from pymodbus.factory import ClientDecoder
from pymodbus.framer.ascii_framer import ModbusAsciiFramer
from pymodbus.framer.binary_framer import ModbusBinaryFramer
from pymodbus.framer.rtu_framer import ModbusRtuFramer
from pymodbus.framer import (
ModbusAsciiFramer,
ModbusBinaryFramer,
ModbusRtuFramer,
ModbusSocketFramer,
)
from pymodbus.transport import CommType
from pymodbus.utilities import ModbusTransactionState

Expand All @@ -26,6 +29,10 @@ def fixture_rtu_framer():
"""RTU framer."""
return ModbusRtuFramer(ClientDecoder())

@pytest.fixture(name="socket_framer")
def fixture_socket_framer():
"""Socket framer."""
return ModbusSocketFramer(ClientDecoder())

@pytest.fixture(name="ascii_framer")
def fixture_ascii_framer():
Expand Down Expand Up @@ -359,3 +366,24 @@ def test_decode_ascii_data(ascii_framer, data):
assert data.get("fcode") == 1
else:
assert not data

def test_recv_split_packet():
"""Test receive packet."""
response_ok = False

def _handle_response(_reply):
"""Handle response."""
nonlocal response_ok
response_ok = True

message = bytearray(b"\x00\x01\x00\x00\x00\x0b\x01\x03\x08\x00\xb5\x12\x2f\x37\x21\x00\x03")
for i in range(0, len(message)):
part1 = message[:i]
part2 = message[i:]
response_ok = False
framer = ModbusSocketFramer(ClientDecoder())
if i:
framer.processIncomingPacket(part1, _handle_response, slave=0)
assert not response_ok, "Response should not be accepted"
framer.processIncomingPacket(part2, _handle_response, slave=0)
assert response_ok, "Response is valid, but not accepted"

0 comments on commit 6913e9c

Please sign in to comment.