Modbus is one of the oldest and still most popular industrial communication protocols on the planet. Created by Modicon in 1979, it is today the lingua franca of sensors, drives, energy meters and PLCs in thousands of factories. Python Modbus is the fastest way to get that data onto your laptop — no vendor licenses, no gateway hardware, just a pip install and thirty lines of code.
This tutorial walks through the essentials: what Modbus is and why it matters, the difference between RTU and TCP, installing pymodbus, reading holding registers, writing setpoints, logging to CSV, and the serial-port variant for legacy RS-485 gear. Every snippet has been tested against real hardware.
What Modbus Is and Why You Should Know It
Modbus is a master-slave (client-server) protocol for exchanging data between industrial devices. Its biggest asset is simplicity: the spec defines only a handful of operations — read registers, write registers, read discrete inputs, read coils. There is no complex handshake, no certificates, no license fees.
In practice, Modbus is everywhere: temperature transmitters, VFDs, PLCs, energy meters, weather stations, inverters. If you work in automation, you will meet Modbus sooner rather than later. Python meets it through the pymodbus library and suddenly your laptop can talk to all of them.
Modbus RTU vs Modbus TCP
Modbus exists in two dominant variants that differ in the physical layer and framing.
Modbus RTU runs over RS-485 (or RS-232). Frames are binary, each containing a device address, function code, payload and CRC checksum. RTU is fast and deterministic but requires a physical serial cable and usually a USB-to-RS485 converter on the Python side.
Modbus TCP is the same protocol wrapped in TCP/IP. Instead of a serial cable you use standard Ethernet — RJ45 or Wi-Fi. Each device has an IP address and listens on port 502. Modbus TCP is easier to set up and is the natural choice when Python talks to a PLC over a LAN.
| Feature | Modbus RTU | Modbus TCP |
|---|---|---|
| Physical layer | RS-485 / RS-232 | Ethernet (TCP/IP) |
| Frame format | Binary + CRC | TCP/IP + MBAP header |
| Port / Medium | COM port (serial) | Port 502 (Ethernet) |
| Speed | 9600–115200 baud | 10/100 Mbps |
| Typical Python use | USB-to-RS485 converter | Direct via LAN |
pymodbus — Modbus in Python
pymodbus is the de-facto Python library for Modbus. It supports both Modbus TCP and RTU, ships a client and a simulator server, handles all standard function codes, and runs on Windows, Linux and macOS. Installation takes one line:
That’s it — no compiled extensions, no system dependencies. After installing you have access to the TCP client, the serial (RTU) client, data-conversion helpers and a minimal Modbus server for local testing.
Modbus Function Codes — What You Can Read
Modbus exposes four primary data areas, each with its own function code. Knowing which is which saves you hours of head-scratching.
| Data type | Function code | Access | pymodbus method |
|---|---|---|---|
| Coils (digital outputs) | 01 / 05 / 15 | Read / Write | read_coils() |
| Discrete Inputs | 02 | Read only | read_discrete_inputs() |
| Holding Registers | 03 / 06 / 16 | Read / Write | read_holding_registers() |
| Input Registers | 04 | Read only | read_input_registers() |
In practice you will spend 80% of your time on Holding Registers — this is where PLCs typically expose analog values: temperatures, pressures, speeds, counters, setpoints.
First Script — Reading Data from a PLC via Modbus TCP
Time for practice. The snippet below connects to a Modbus TCP device and reads ten holding registers starting at address 0. This is the foundation you will build everything on.
This script does exactly what an engineer would do manually in Modbus Poll — only automatically. Schedule it on a cron job, pipe the output to CSV, send an email on alarm, or feed a Grafana dashboard.
What Each Line Does
- Line 2: Import
ModbusTcpClientfrom pymodbus — this is our Modbus TCP client class. - Lines 5–6: PLC IP address and port. Port 502 is the Modbus TCP default.
- Line 9: Create the client object and open the TCP session.
- Lines 12–16: Read ten holding registers starting at register 0.
slave=1is the Modbus Unit ID. - Lines 19–23: Check for errors and print each register value. Always handle the error case — real networks drop packets.
Continuous Monitoring — Polling in a Loop
A single read is only the start. In production you poll continuously — read every second, react on thresholds, log the history. The snippet below extends the example with error handling and a clean shutdown.
The PLC often stores analog values as scaled integers (real value × 10 or × 100), which is why we divide. Ctrl+C cleanly exits the loop and closes the socket — your PLC will thank you for not leaving stale connections open.
Modbus RTU — Serial Port Variant
If your device uses RS-485, you need a USB-to-RS485 converter (30-80 EUR) and a small change in code. Replace ModbusTcpClient with ModbusSerialClient:
Only the connection setup changes. Baud rate, parity and stop bits must match the device datasheet exactly — 9600-N-8-1 is the most common default for RTU devices.
Writing to Modbus Registers
Modbus is bidirectional. You can write setpoints, change VFD speeds, toggle outputs. Use write_register() for a single word or write_registers() for multiple:
Warning: writing to PLC registers changes live machine parameters. Always test on a simulator first, and never enable writes to registers that influence safety logic — safety must live inside the PLC, not over a network from Python.
Logging Modbus Data to CSV
Reading without logging is half a job. The snippet below polls three registers every five seconds and appends them to a CSV with a timestamp. Open it later in Excel, pandas or any BI tool.
After a few hours of polling you have a time-series file ready for analysis, plotting or ML model training. Upgrade the CSV to InfluxDB or TimescaleDB when you outgrow the file.
Testing Without a Physical PLC — Simulators
No PLC on your desk? No problem. A Modbus simulator fakes a slave device on your computer so you can develop against it. The common choices:
- ModRSsim2 — free Windows simulator, great for beginners, supports TCP and RTU.
- pymodbus server — spin up a slave in Python itself, useful for unit tests.
- diagslave — lightweight command-line simulator for Linux and Windows.
Start with ModRSsim2: launch it, set register values by hand, point your Python client at 127.0.0.1:502 and iterate. You can develop the entire data pipeline without touching the production PLC — a habit worth forming early.
Troubleshooting the Common Failures
ConnectionException: Failed to connect — network path blocked. Verify the PLC is on the same subnet, ping it, check firewall rules, and confirm port 502 is actually open (some PLCs ship with Modbus TCP disabled by default).
All registers read as 0 or nonsense — wrong Unit ID (slave= argument). Many devices use Unit ID 1, but gateways and RTU-over-TCP bridges often use 247 or the device’s physical address.
Values look off by factor of 10 or 100 — the PLC is publishing scaled integers. Check the documentation or the PLC program; divide on the Python side accordingly.
Reads succeed but writes silently fail — the register is read-only (Input Register, function code 04) and you should be writing to a Holding Register instead, or the PLC has write protection enabled for this area.
FAQ
Which is faster, Modbus TCP or Modbus RTU?
Modbus TCP over 100 Mbps Ethernet is roughly 10-20× faster than Modbus RTU at 115200 baud. For new installations, TCP is almost always the better choice. RTU still wins on cable cost for long linear runs and in EMC-hostile environments where RS-485’s differential signaling is more robust than Ethernet.
Can I have multiple Python clients polling the same PLC?
Yes — Modbus TCP supports multiple parallel connections. Each client opens its own TCP session. Mind the PLC’s connection limit (typically 8-16 concurrent Modbus TCP sessions) and avoid hammering with sub-100 ms polls from ten clients at once.
Is pymodbus production-ready?
Yes. Version 3.x is stable, well-tested, and used in commercial integrations. For enterprise deployments, pair it with proper logging, reconnect-on-failure logic, and a circuit-breaker pattern so a down PLC does not crash your whole service.
Should I use async pymodbus?
If you poll dozens of devices simultaneously — yes, use AsyncModbusTcpClient. For a single PLC the sync API is simpler and plenty fast.
How do I read 32-bit floats from a Modbus device?
Modbus registers are 16-bit. Floats and 32-bit integers are split across two consecutive registers, with byte order (big-endian / little-endian / word-swapped) varying by vendor. Use pymodbus.payload.BinaryPayloadDecoder to reassemble them correctly — always test against a known value first because vendors disagree on register order.
Can Modbus do security / authentication?
Classic Modbus has none. There is Modbus TCP Secure (port 802) with TLS, but adoption is limited. In practice, isolate Modbus devices on a dedicated OT network behind a firewall and rely on network-layer security. Do not expose Modbus TCP on public networks.
What’s Next
You now have a working Modbus foundation in Python — TCP, RTU, read, write, log. This is enough to unlock most retrofit and data-acquisition projects across the industry.
To go deeper, our Python for Automation Engineers course walks through the full industrial Python stack: Modbus, OPC UA, Snap7, SQL, reporting, visualization, and a complete Factory Data Analyzer project built from scratch. For the communication layer specifically — Modbus, Profinet, OPC UA side by side — the Communication Protocols course covers what you need to integrate across vendor ecosystems.
Both courses are built around real industrial projects, not textbook exercises — same philosophy you see in this article.



