Marcio Cunha

How to read Modbus RTU sensors with Python and PyModbus

Learn how to develop a robust Python script for serial communication with industrial devices via Modbus RTU, handling timeouts and exceptions.

Marcio Cunha•4 min
Also available in:PortuguêsEspañol
Summary
  • Industrial serial communication requires resilience against electrical noise and unexpected bus interruptions.
  • The PyModbus library simplifies sending binary frames and decoding sensor and PLC registers.
  • Handling timeouts and connection exceptions prevents monitoring software from freezing in factory floors.
  • Accurate sensor reading depends on proper memory address mapping and correct data type conversions.
  • Modern automation systems benefit from integrating robust local protocols for real-time telemetry.

Understanding the Modbus RTU protocol on the factory floor

The Modbus protocol is a widely used communication standard in industry since the 1970s for connecting electronic devices such as programmable logic controllers, frequency inverters, and power meters. In the RTU variant, which stands for Remote Terminal Unit, data is transmitted compactly in binary format via serial cables, usually using the RS-485 physical layer. In practice, this means conversations between the computer and sensors occur through bursts of electrical pulses traveling across twisted pair wires, requiring strict timing and synchronization rules to prevent reading errors.

When building data acquisition systems, the physical stability of the bus is just as important as the software. In harsh industrial environments, electric motors and switching power supplies generate intense electromagnetic interference that can corrupt data packets along the way. Therefore, choosing shielded cables and proper grounding forms an essential foundation even before writing the first line of Python code. For those looking to expand monitoring infrastructure, understanding how to use MQTT to integrate industrial sensors locally helps transform raw serial data into highly reliable distributed telemetry.

Preparing the Python environment and PyModbus library

To interact with the serial bus from a computer or a mini-computer like a Raspberry Pi, we need a library capable of translating high-level commands into Modbus requests understandable by sensors. The Python ecosystem offers PyModbus, a mature and feature-rich tool to act both as a client and a server. In practice, we install the library using the pip package manager directly in the operating system terminal, ensuring that all serial communication dependencies are ready for use.

Before proceeding with implementation, keep in mind that serial communication requires proper hardware access permissions and the correct USB-serial converter port, such as ttyUSB0 on Linux or COM3 on Windows. In scenarios where different devices share the same physical bus, connection organization and network topology prevent signal reflection and packet loss. If your project also involves reading complex analog quantities, it is worth checking the guide on how to read 4-20mA industrial sensors with ESP32 safely to understand how to handle raw electrical signals before digitizing them.

Writing the serial reading script with error handling

Developing a robust reading script means assuming that at any moment the sensor may stop responding, the cable may break, or the signal may suffer severe interference. Instead of letting the program crash with a fatal exception, we implement try-except blocks to isolate timeout failures, which occur when the device takes longer than expected to return the requested response. In practice, this ensures that the system retries in a future cycle without interrupting the main monitoring process execution.

The following code demonstrates how to structure a Modbus RTU connection using PyModbus, safely reading input registers from a typical industrial sensor:

from pymodbus.client import ModbusSerialClient as ModbusClient
import time

client = ModbusClient(
    method='rtu',
    port='/dev/ttyUSB0',
    baudrate=9600,
    stopbits=1,
    bytesize=8,
    parity='N',
    timeout=1
)

if client.connect():
    print("Serial connection established successfully.")
    try:
        # Read 2 registers starting from address 0
        result = client.read_input_registers(address=0, count=2, slave=1)
        if result.isError():
            print("Error returned by Modbus device.")
        else:
            values = result.registers
            print(f"Values read: {values}")
    except Exception as e:
        print(f"Communication failure or timeout: {e}")
    finally:
        client.close()
else:
    print("Unable to open serial port.")

This code block creates a serial client instance, configures fundamental transmission parameters such as baud rate and parity, and executes the reading with crash protection. Using the finally block ensures that the serial port is correctly released regardless of the success or failure of the previous operation, preventing hardware access conflicts by the operating system.

Interpreting registers and validating data

Reading raw sensor registers is only the first step of the engineering process. Most of the time, manufacturers store floating-point physical quantities or 32-bit integers split across two consecutive 16-bit registers, requiring bitwise operations and endianness conversions to extract the real temperature, pressure, or flow value. In practice, this means we must reorganize the byte order according to the equipment technical specification to avoid completely distorted readings.

In addition to mathematical decoding, range validation is essential to ensure that the collected data makes physical sense in the application context. If an industrial temperature sensor reports a value of five hundred degrees in a controlled environment, the software should signal an anomaly rather than recording the data blindly. This validation layer protects control panels and ensures the operational reliability of the entire automation system. To explore these architectural concepts further in production, see our related technical analysis on How to test DDR4 RAM memory with an oscilloscope on bench.

Final considerations on resilience in industrial automation

Building Modbus RTU sensor reading routines with Python requires meticulous attention to hardware details, protocols, and exception handling. When we anticipate network failures, electrical noise, and communication timeouts, we transform a fragile script into a highly reliable engineering tool for industrial and laboratory environments. Combining Python flexibility with PyModbus versatility opens doors for advanced telemetry integrations without relying exclusively on expensive or closed proprietary software.

Keeping code clean, documented, and prepared for failure scenarios ensures maintainability and project scalability over the years. By applying best practices in error handling and data validation, we create solid foundations for any modern real-time monitoring, supervision, and control system.