[Python] Unable to read the full response coming over rs-485 from a Modbus device

Discussão em 'Python' iniciado por Stack, Fevereiro 25, 2025.

  1. Stack

    Stack Membro Participativo

    I have been trying to communicate with a I-7000 IO module device that supports Modbus. In general I am able to successfully send commands such as writing to a coil or reading a coil but I face one issue - I am unable to read the response coming from the IO module device, at least not every time. I am using pyserial and I expect 6 bytes returned with a specific order but I fail to read the message correctly. Each message has a device id, function code, coil address, value and CRC message at the end. Most of the time I am able to only read the CRC message at the end and sometimes I am able to read some of the other bytes before that. Rarely I am able to read the whole message.

    Here I did setup this simple code to debug my problem, it is not my main code but the issue I face is the same.

    import serial
    import time
    import crcmod.predefined

    # Function to calculate Modbus RTU CRC
    def calculate_rs_485_crc(message):
    crc16 = crcmod.predefined.mkCrcFun("modbus")
    return crc16(message)

    port = "/dev/ttyS0"
    baudrate = 115200
    timeout = 1

    ser = serial.Serial(
    port,
    baudrate,
    timeout=timeout,
    parity=serial.PARITY_NONE,
    )

    # Construct Modbus RTU command
    message = "020100060001"
    command = bytes.fromhex(message)
    crc = calculate_rs_485_crc(command)
    command_with_crc = command + crc.to_bytes(2, "little")

    print(f"Sent: {command_with_crc.hex()}")
    ser.write(command_with_crc)

    byteData = ser.read_until(b"\xcc")

    print(f"byteData: {byteData}")

    ser.close()


    I have checked baudrate and the port, they are okay. Whatever timeout I place, the result is the same. I tried putting a time.sleep(0.5) between the write and read commands but this makes it actually worse because I am unable to receive any data then. Having no sleep time at least lets me receive some parts of the message.

    This is my terminal where I see what is printed. Sometimes receive the end of the message (CRC bytes):
    Sent: 0201000600011df8
    byteData: b'Q\xcc'

    And sometimes I can see the actually expected message. Or sometimes also something in between.
    Sent: 0201000600011df8
    byteData: b'\x02\x01\x01\x00Q\xcc'

    Also, currently you can see me using read_until in the code but I tried using readline and just read by specifying the number of bytes I expect to read but none of it helped. Same result. I am not sure why this inconsistency happens. Most of my reading online shows timing issues or hardware issues but it is definitely not hardware issues because there is some other tools I used, provided by the manufacturer to talk to this device and it worked fine. If it is a timing issue, I don't understand how it is such because if I put some time to sleep in order to make sure every piece of the message has arrived, then i get nothing.

    I hope you can help me out.

    EDIT: Added mbpoll result:

    user@localhost:~$ sudo mbpoll -m rtu -b 115200 -d 8 -s 1 -p none /dev/ttyS0 -a 2 -r 1 -1 -t 0
    mbpoll 1.0-0 - FieldTalk(tm) Modbus(R) Master Simulator
    Copyright © 2015-2019 Pascal JEAN, https://github.com/epsilonrt/mbpoll
    This program comes with ABSOLUTELY NO WARRANTY.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type 'mbpoll -w' for details.

    Protocol configuration: Modbus RTU
    Slave configuration...: address = [2]
    start reference = 1, count = 1
    Communication.........: /dev/ttyS0, 115200-8E1
    t/o 1.00 s, poll rate 1000 ms
    Data type.............: discrete output (coil)

    -- Polling slave 2...
    Read discrete output (coil) failed: Connection timed out


    EDIT #2: Imported a function

    I have this other code which pretty much does the same thing, only it is in a function. I called this function in my code you can see above and then it works all fine. Not really sure why. But that is still not a good solution because I create two serial port instances and both write and read:

    import serial
    import time
    import logging
    import os
    import crcmod.predefined

    logger = logging.getLogger(__name__)

    def send_over_rs485(message: str, max_attempts=3):
    attempts = 0
    while attempts < max_attempts:
    try:
    ser = serial.Serial("/dev/ttyS0", 115200, timeout=0.3)

    command = bytes.fromhex(message)
    crc = calculate_rs_485_crc(command)
    command_with_crc = command + crc.to_bytes(2, "little")

    ser.write(command_with_crc)

    time.sleep(0.2)

    if ser.in_waiting:
    response = ser.readline()
    return response.hex()
    else:
    # print("No response available in rs-485 serial buffer.")
    logging.debug("No response available rs-485 in serial buffer.")

    except serial.SerialException as e:
    logging.error(f"Serial communication error: {e}")
    except OSError as e:
    logging.error(f"OS-level communication error: {e}")
    except Exception as e:
    logging.error(f"Unexpected error: {e}")
    finally:
    if "ser" in locals() and ser.is_open:
    ser.close()
    attempts += 1

    logging.warning(f"Reached maximum attempts without success: {attempts}")
    return None

    # Function to calculate Modbus RTU CRC
    def calculate_rs_485_crc(message):
    crc16 = crcmod.predefined.mkCrcFun("modbus")
    return crc16(message)


    So this funciton I import and call in my code above like this:

    import serial
    import time
    import crcmod.predefined
    # NOTE: IMPORTED FUNCTION HERE
    from serial_protocol import send_over_rs485 # NOTE: it worked only because of this


    # Function to calculate Modbus RTU CRC
    def calculate_rs_485_crc(message):
    crc16 = crcmod.predefined.mkCrcFun("modbus")
    return crc16(message)

    port = "/dev/ttyS0"
    baudrate = 115200
    timeout = 1

    ser = serial.Serial(
    port,
    baudrate,
    timeout=timeout,
    parity=serial.PARITY_NONE,
    )

    # Construct Modbus RTU command
    message = "020100060001"
    # NOTE: CALLED FUNCTION HERE
    # NOTE: This somehow makes it work:
    send_over_rs485(message)

    command = bytes.fromhex(message)
    crc = calculate_rs_485_crc(command)
    command_with_crc = command + crc.to_bytes(2, "little")

    print(f"Sent: {command_with_crc.hex()}")
    ser.write(command_with_crc)

    byteData = ser.read_until(b"\xcc")

    print(f"byteData: {byteData}")

    ser.close()


    With this change which I do not understand I get the full response. Hopefully you can help me out understand why.

    Response:

    Sent: 0201000600011df8
    in waiting false 0
    byteData: b'\x02\x01\x01\x00Q\xcc'

    Continue reading...

Compartilhe esta Página