AdvancedClimateSystems / uModbus

Python implementation of the Modbus protocol.
Mozilla Public License 2.0
211 stars 82 forks source link

Modbus RTU over TCP #51

Closed ccatterina closed 6 years ago

ccatterina commented 6 years ago

Hi, Is there a way to send RTU messages through TCP? I need this functionality because we usually convert all the serial devices with rs485/ethernet converter.

OrangeTux commented 6 years ago

From what I understand you have a Modbus RTU Master that needs to send requests to Modbus TCP slave. Is that correct?

ccatterina commented 6 years ago

No, i have a Modbus TCP master (my raspberrypi) that needs to send requests to a Modbus RTU slave, in the middle there is a rs485/ethernet converter that receives the requests from the raspberry and sends them to the slave through the serial line.

ccatterina commented 6 years ago

In short, i need to send a Modbus RTU packet wrapped in a TCP/IP packet.

OrangeTux commented 6 years ago

This is no problem. I've build one myself using the building blocks that uModbus provides.

As you may know Modbus RTU and Modbus TCP have a few bytes in common, the PDU. As shown in this image.

To convert a Modbus TCP request into a Modbus RTU request you need to strip the MBAP header and add a CRC. After you receive the RTU response you need remove the CRC and prepend the message with the MBAP header and return that.

Here some code I wrote a while ago that did the job.

class ModbusRTUProxyHandler(RequestHandler):
    def rewrite_request_adu(self, tcp_request_adu):
        """ Rewrite a TCP request ADU to a RTU request ADU.  """
        slave_id = struct.unpack('>B', tcp_request_adu[6:7])[0]
        return rtu._create_request_adu(slave_id, tcp_request_adu[7:])

    def rewrite_response_adu(self, tcp_request_adu, rtu_response_adu):
        """ Rewrite a RTU repsonse ADU to a TCP response ADU. """
        response_pdu = rtu_response_adu[1:-2]

        # Byte 6 of the MBAP is the length field. This number depends on the
        # length of the adu and must be calculated. All other bytes of the
        # request MBAP can be copied to the response MBAP.
        mbap = tcp_request_adu[:4] +\
            struct.pack('>H', len(response_pdu) + 1) + tcp_request_adu[6:7]

        return mbap + response_pdu

    def call_slave(self, rtu_request_adu):
        self.server.rtu_lock.acquire()
        self.server.serial_port.write(rtu_request_adu)
        try:
            rtu_response_adu = self.server.serial_port.read(256)
        except SerialTimeoutException:
            pass
        finally:
            self.server.rtu_lock.release()

        return rtu_response_adu

    def process(self, tcp_request_adu):
        slave_id = struct.unpack('>B', tcp_request_adu[6:7])[0]

        log.info('<-- {0} - {1}.'.format(self.client_address[0],
                 hexlify(tcp_request_adu)))
        rtu_request_adu = self.rewrite_request_adu(tcp_request_adu)
        rtu_response_adu = self.call_slave(rtu_request_adu)

        return self.rewrite_response_adu(tcp_request_adu, rtu_response_adu)
ccatterina commented 6 years ago

Thank you very much. That's all I needed.

I close the issue.

rahimdzb commented 3 years ago

Does

D'après ce que j'ai compris, vous avez un maître Modbus RTU qui doit envoyer des requêtes à l'esclave Modbus TCP. Est-ce exacte ? In this case what we do exactly ! And does the code work?