<?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>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>
