<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rebecca Anderson</title>
    <description>The latest articles on DEV Community by Rebecca Anderson (@rebecca_anderson_e63d00b1).</description>
    <link>https://dev.to/rebecca_anderson_e63d00b1</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3879522%2Ff85dbd75-4fbb-4927-aa81-d5143ee06921.webp</url>
      <title>DEV Community: Rebecca Anderson</title>
      <link>https://dev.to/rebecca_anderson_e63d00b1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rebecca_anderson_e63d00b1"/>
    <language>en</language>
    <item>
      <title>How to Handle Modbus Bus Collisions When Multiple Python Scripts Poll the Same RS485 Sensor</title>
      <dc:creator>Rebecca Anderson</dc:creator>
      <pubDate>Sat, 06 Jun 2026 14:02:32 +0000</pubDate>
      <link>https://dev.to/rebecca_anderson_e63d00b1/how-to-handle-modbus-bus-collisions-when-multiple-python-scripts-poll-the-same-rs485-sensor-4f6g</link>
      <guid>https://dev.to/rebecca_anderson_e63d00b1/how-to-handle-modbus-bus-collisions-when-multiple-python-scripts-poll-the-same-rs485-sensor-4f6g</guid>
      <description>&lt;p&gt;This is the normal IoT developer experience: You write a Python script using &lt;code&gt;pymodbus&lt;/code&gt;  to read data from an RS485 soil sensor or a solar inverter. It runs perfect on your test bench for hours,&lt;/p&gt;

&lt;p&gt;But then, the project scales up. You spin up a second Python script on the same machine to handle real-time MQTT cloud synchronization. Or perhaps the factory's main SCADA system suddenly needs to poll that exact same hardware sensor at the same time.&lt;/p&gt;

&lt;p&gt;Boom. Your terminal fills up with &lt;code&gt;ModbusIOException&lt;/code&gt;, timeout errors, or complete serial port lockups. &lt;/p&gt;

&lt;p&gt;If you are currently trying to fix this by writing complex software semaphores or cross-process locks in Python, stop. Let’s look at why this happens at the hardware level and how to solve it properly through architecture.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Root Cause: RS485 is Strictly Single-Master
&lt;/h3&gt;

&lt;p&gt;The fundamental limitation isn't your Python code; it's the physics of the RS485 bus. &lt;/p&gt;

&lt;p&gt;RS485 is a half-duplex serial interface. This means data can travel in both directions, but only &lt;strong&gt;one device can talk on the wire at any given microsecond&lt;/strong&gt;. The Modbus RTU protocol is strictly a Master-Slave architecture. The Master sends a query, the Slave responds. &lt;/p&gt;

&lt;p&gt;When you launch two independent Python scripts executing parallel polling loops, they have no awareness of each other's timing. Eventually, a race condition occurs: both scripts attempt to write to the serial port simultaneously. &lt;/p&gt;

&lt;p&gt;The electrical signals collide on the copper wire, resulting in garbage bytes, broken frames, and catastrophic CRC failures.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Script A] ---- Query (01 03 00 00 ...) ----&amp;gt;  ⚡ COLLISION ⚡
[Script B] ---- Query (01 03 00 0A ...) ----&amp;gt;  [ RS485 Bus Blown ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Why Software Locks SUCK Here
&lt;/h3&gt;

&lt;p&gt;Your first instinct might be to implement a file lock (&lt;code&gt;fcntl&lt;/code&gt;), a Redis-based distributed lock, or a Python &lt;code&gt;threading.Lock()&lt;/code&gt; to serialize the access to the serial port. &lt;/p&gt;

&lt;p&gt;While this prevents data collisions, it introduces massive software engineering overhead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Latency Spikes:&lt;/strong&gt; If Script A is stuck waiting for a slow sensor response, Script B is completely blocked, leading to data gaps in your cloud database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deadlocks:&lt;/strong&gt; If one script crashes while holding the serial lock, the entire data acquisition system freezes until a hard reboot.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  The Architectural Fix: Decoupling via Cache
&lt;/h3&gt;

&lt;p&gt;The elegant way to solve multi-host collision is to completely offload the serial polling task from your Python applications. Instead of letting your scripts talk directly to the raw serial line, you place a hardware protocol gateway in between that supports &lt;strong&gt;autonomous register caching&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this architecture, the gateway is the only Master on the RS485 physical bus. It constantly polls the hardware sensors at the physical layer, and caches the latest register data directly in its own high speed internal RAM cache.&lt;/p&gt;

&lt;p&gt;Your Python scripts can no longer connect to serial ports. Instead, they make normal, simultaneous Modbus TCP queries over Ethernet (Port 502).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Python Script A (TCP)] --\
                           +---&amp;gt; [ Caching Gateway ] ---&amp;gt; (Stable RS485 Polling) ---&amp;gt; [Sensors]
[Python Script B (TCP)] --/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  The Code: Clean Concurrent Modbus TCP Polling
&lt;/h3&gt;

&lt;p&gt;When you shift to a caching architecture, your Python code becomes remarkably clean. Because Modbus TCP inherently handles multiple network sockets, you can run as many parallel scripts, containers, or threads as you want without a single lock.&lt;/p&gt;

&lt;p&gt;Here is a clean implementation simulating two independent applications querying the network simultaneously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pymodbus.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ModbusTcpClient&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="c1"&gt;# Point this to your hardware caching gateway IP
&lt;/span&gt;&lt;span class="n"&gt;GATEWAY_IP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;192.168.1.200&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;GATEWAY_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;502&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;application_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;register_address&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Each independent script or thread opens its own clean TCP socket
&lt;/span&gt;    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ModbusTcpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GATEWAY_IP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GATEWAY_PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;App &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: Network unreachable.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;App &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: Connected to gateway port 502.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Query the cached hardware registers instantly (usually under 3ms)
&lt;/span&gt;            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_holding_registers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;register_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slave&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;App &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; Data Received: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;registers&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;App &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; Modbus Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;App &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; Network Exception: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Poll as fast as you want; no serial collisions possible
&lt;/span&gt;        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Simulating two completely separate applications running in parallel
&lt;/span&gt;    &lt;span class="n"&gt;app_cloud_sync&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;application_task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;app_local_scada&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;application_task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;app_cloud_sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;app_local_scada&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Hardware Layer Note
&lt;/h3&gt;

&lt;p&gt;To make this architecture work seamlessly in production environments, your hardware gateway must handle the scheduling and isolation. A passive, cheap serial-to-ethernet converter that just passes raw bytes will still cause collisions if two TCP packets arrive at the same time.&lt;/p&gt;

&lt;p&gt;In our commercial field deployments for remote solar farms, we completely shifted away from passive dongles to the industrial multi-channel storage gateways manufactured by &lt;a href="https://valtoris.com/product-center/serial-device-server/serial-to-ethernet/" rel="noopener noreferrer"&gt;Valtoris&lt;/a&gt;. Their hardware natively manages the asynchronous scheduling, features 3000V galvanic isolation to block ground loops on factory floors, and supports up to 32 concurrent Modbus TCP client connections.&lt;/p&gt;

&lt;p&gt;By moving the bus synchronization from unstable Python scripts down to dedicated hardware, your software can focus entirely on data processing and analytics, making your entire IoT stack infinitely more resilient.&lt;/p&gt;

</description>
      <category>python</category>
      <category>modbus</category>
      <category>iot</category>
      <category>backend</category>
    </item>
    <item>
      <title>Quick Python script to dump raw RS485 hex data</title>
      <dc:creator>Rebecca Anderson</dc:creator>
      <pubDate>Sun, 24 May 2026 08:56:01 +0000</pubDate>
      <link>https://dev.to/rebecca_anderson_e63d00b1/quick-python-script-to-dump-raw-rs485-hex-data-1p11</link>
      <guid>https://dev.to/rebecca_anderson_e63d00b1/quick-python-script-to-dump-raw-rs485-hex-data-1p11</guid>
      <description>&lt;p&gt;Before writing a full Modbus client, I usually just need to see if the bus is actually alive and outputting data.&lt;/p&gt;

&lt;p&gt;Here is a minimal hex dumper using pyserial. It doesn't parse anything; it just dumps raw bytes to the terminal so you can check for noise.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
python
import serial
import binascii

SERIAL_PORT = '/dev/ttyUSB0'
BAUD_RATE = 9600

def hex_dump():
    ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=0.5)
    print(f"Listening on {SERIAL_PORT}...")

    while True:
        if ser.in_waiting &amp;gt; 0:
            data = ser.read(ser.in_waiting)
            formatted_hex = ' '.join(binascii.hexlify(data).decode('utf-8').upper()[i:i+2] for i in range(0, len(data)*2, 2))
            print(f"RX: {formatted_hex}")

if __name__ == '__main__':
    hex_dump()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>python</category>
    </item>
    <item>
      <title>Reading Modbus RTU Data with Python</title>
      <dc:creator>Rebecca Anderson</dc:creator>
      <pubDate>Wed, 15 Apr 2026 02:12:40 +0000</pubDate>
      <link>https://dev.to/rebecca_anderson_e63d00b1/reading-modbus-rtu-data-with-python-4i37</link>
      <guid>https://dev.to/rebecca_anderson_e63d00b1/reading-modbus-rtu-data-with-python-4i37</guid>
      <description>&lt;p&gt;Most tutorials on reading Modbus RTU data with Python focus entirely on the software side. The standard advice is to run pip install pymodbus, plug a USB-to-RS485 adapter into your machine, and write a script using ModbusSerialClient.&lt;br&gt;
It usually works perfectly on your desk. But when you deploy it to a production environment, the connection randomly drops, you get timeout errors, or the data reads as garbage.&lt;/p&gt;

&lt;p&gt;Let's look at why this happens, and how to structure your Modbus integration so it actually stays up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Underlying Problem: Timing and Noise&lt;/strong&gt;&lt;br&gt;
Reading serial data directly via Python in a production setting introduces two major failure points:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;OS Timing Issues: Modbus RTU relies on strict timing gaps between data frames (typically 3.5 character times). Standard operating systems like Linux or Windows are not real-time OSs. If the CPU is busy and delays reading the serial buffer, the frame breaks, and your Python script throws a CRC error.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Electrical Isolation: The floors of factories are electrically loud. A normal USB-to-RS485 dongle does not provide galvanic isolation. The ground potential changes when a big machine nearby starts up. That spike goes all the way up the RS485 cable, crashes the USB driver on your server, and you have to unplug and plug it back in to fix it.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The Fix: Decoupling the Architecture&lt;/strong&gt;&lt;br&gt;
The most reliable way to read Modbus RTU in Python is to avoid dealing with the serial port entirely.&lt;/p&gt;

&lt;p&gt;Instead of letting your server handle the physical layer, decouple it using an industrial protocol gateway. You place a &lt;a href="https://valtoris.com/product-center/serial-device-server/serial-to-ethernet/" rel="noopener noreferrer"&gt;Modbus RTU-to-TCP converter&lt;/a&gt; right next to the physical sensors.&lt;/p&gt;

&lt;p&gt;The hardware gateway handles the strict RS485 timing, the electrical isolation, and the continuous polling. It then exposes that data over standard Ethernet as Modbus TCP.&lt;/p&gt;

&lt;p&gt;Your Python script no longer fights with ttyUSB0 or baud rates. It simply makes a standard TCP network request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Code: Switching to Modbus TCP&lt;/strong&gt;&lt;br&gt;
By shifting to TCP, the Python implementation becomes much cleaner and naturally handles network resilience. Here is the baseline structure using pymodbus:&lt;/p&gt;

&lt;p&gt;from pymodbus.client import ModbusTcpClient&lt;br&gt;
import time&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Point this to the IP of your hardware gateway
&lt;/span&gt;&lt;span class="n"&gt;GATEWAY_IP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;192.168.1.100&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;GATEWAY_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;502&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;poll_sensor&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Use TCP Client instead of Serial
&lt;/span&gt;    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ModbusTcpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GATEWAY_IP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GATEWAY_PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connection failed. Check network or gateway.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="c1"&gt;# Read 2 registers from Slave ID 1, starting at address 0
&lt;/span&gt;        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_holding_registers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slave&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Modbus Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;raw_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;registers&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Raw Registers: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;raw_data&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Basic parsing example
&lt;/span&gt;            &lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;10.0&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Temperature: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; °C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Exception caught: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;poll_sensor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;One Last Gotcha: Endianness&lt;/strong&gt;&lt;br&gt;
If your script connects but the data doesn't make sense (for example, your temperature says 18432 instead of 24.5), you haven't done anything wrong. There is a problem with the byte order.&lt;/p&gt;

&lt;p&gt;The Modbus protocol specification does not strictly define how 32-bit floats or long integers should be ordered across 16-bit registers. Different sensor manufacturers use different byte orders (Big-Endian vs. Little-Endian).&lt;/p&gt;

&lt;p&gt;If you come across this, don't try to figure out the bits by hand. To change the order of the bytes until the output matches what you expect, use the BinaryPayloadDecoder module that comes with pymodbus.&lt;/p&gt;

&lt;p&gt;from pymodbus.payload import BinaryPayloadDecoder&lt;br&gt;
from pymodbus.constants import Endian&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Example: Decoding a 32-bit float
&lt;/span&gt;&lt;span class="n"&gt;decoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BinaryPayloadDecoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromRegisters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;byteorder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Endian&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Big&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wordorder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Endian&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Little&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;real_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode_32bit_float&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>python</category>
      <category>modbus</category>
      <category>iot</category>
      <category>software</category>
    </item>
  </channel>
</rss>
