Skip to content

RTU Framer not Handling Maximum Charachter Interval #1278

Closed
@erezblm

Description

@erezblm

Versions

  • Python: 3.10.6
  • OS: Ubuntu 20.04.1
  • Pymodbus: 3.1.0
  • Modbus Hardware (if used): USB to RS485 Adapter

Pymodbus Specific

  • Server: rtu

Description

I'm using the Modbus server for testing our client implementation.
As part of our client code, it tests the server for a few baud rates. As a consequence of this, some bad messages are sent with a specified interval between them.
After some debugging, I figured out that the RTU Framer is storing the last received frame indefinitely. As a consequence the next frame which was good was added to the last bad frame and then throws altogether because it cannot parse.
See the framer code below.

According to Modbus serial implementation guide (section 2.5.1.1 MODBUS Message RTU Framing):
"If a silent interval of more than 1.5 character times occurs between two characters, the message frame is declared incomplete and
should be discarded by the receiver."

Character time is based on the baud rate, and it seems that the RTU Framer doesn't handle silence between charchters at all.

Code and Logs

Some logs, the second message is good and appended to the previous:

Handling data: 0x0 0x20 0x40
Frame - [b'\x00 @'] not ready
Handling data: 0x1 0x3 0x40 0x0 0x0 0xb 0x11 0xcd
Frame check failed, ignoring!!
Resetting frame - Current Frame in buffer - 0x20 0x0 0x20 0x40 0x1 0x3 0x40 0x0 0x0 0xb 0x11 0xcd

It's all here basically, 'addToFrame' just append the buffer and 'isFrameReady' returns false if the buffer is smaller than the expected function code, and in this case just log 'not ready' and returns.

    def processIncomingPacket(
        self, data, callback, unit, **kwargs
    ):  # pylint: disable=arguments-differ
        """Process new packet pattern.

        This takes in a new request packet, adds it to the current
        packet stream, and performs framing on it. That is, checks
        for complete messages, and once found, will process all that
        exist.  This handles the case when we read N + 1 or 1 // N
        messages at a time instead of 1.

        The processed and decoded messages are pushed to the callback
        function to process and send.

        :param data: The new packet data
        :param callback: The function to send results to
        :param unit: Process if unit id matches, ignore otherwise (could be a
               list of unit ids (server) or single unit id(client/server)
        :param kwargs:
        """
        if not isinstance(unit, (list, tuple)):
            unit = [unit]
        self.addToFrame(data)
        single = kwargs.get("single", False)
        while True:
            if self.isFrameReady():
                if self.checkFrame():
                    if self._validate_unit_id(unit, single):
                        self._process(callback)
                    else:
                        header_txt = self._header["uid"]
                        txt = f"Not a valid unit id - {header_txt}, ignoring!!"
                        _logger.debug(txt)
                        self.resetFrame()
                        break
                else:
                    _logger.debug("Frame check failed, ignoring!!")
                    self.resetFrame()
                    break
            else:
                txt = f"Frame - [{data}] not ready"
                _logger.debug(txt)
                break

Suggestion

Add logic to 'addToFrame' checking the last time a buffer was added (stored as a member variable), and if the interval is bigger than expected (according to the baud rate), reset previous buffer and set the buffer to the received one.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions