Modbus TCP and RTU with Raspberry Pi PLCs: Python and Node-RED examples

A practical guide to implementing Modbus RTU and TCP on Raspberry Pi PLCs using Python (pymodbus 3.x) and Node-RED — with working examples for both single and dual-device setups.
June 1, 2026 by
Modbus TCP and RTU with Raspberry Pi PLCs: Python and Node-RED examples
Joan F. Aubets - Industrial Shields

Modbus is a master-slave communication protocol used worldwide in industrial automation. It runs over two transports: serial lines (Modbus RTU, via RS-485) and Ethernet (Modbus TCP). Both are supported natively on Industrial Shields Raspberry Pi PLCs — RTU through the RS-485 port, TCP through the Ethernet interface.

In this guide you will find working Python and Node-RED examples for both protocols, tested on Raspberry Pi PLC hardware with pymodbus 3.x.

Note on pymodbus versions: pymodbus 3.x introduced breaking API changes compared to 2.x. All examples in this guide use the current 3.x API. If you are using an older installation, run pip install --upgrade pymodbus before proceeding.

Prerequisites

Before running these examples, make sure you have the following in place:

Install pymodbus if not already present:

pip install pymodbus

Modbus RTU with Python

Slave (server)

Run this code on the Raspberry Pi PLC that will act as the slave. It starts a Modbus RTU server on the RS-485 port.

Modbus RTU with Python

from pymodbus.server import StartSerialServer
from pymodbus.framer import FramerType
from pymodbus.datastore import (
    ModbusServerContext,
    ModbusSequentialDataBlock,
    ModbusSlaveContext
)

store = ModbusSlaveContext(
    di=ModbusSequentialDataBlock(0, [0] * 10000),
    co=ModbusSequentialDataBlock(0, [0] * 10000),
    hr=ModbusSequentialDataBlock(0, [0] * 10000),
    ir=ModbusSequentialDataBlock(0, [0] * 10000)
)
context = ModbusServerContext(slaves=store, single=True)

StartSerialServer(
    context=context,
    port='/dev/ttySC0',       # RS-485 port on Raspberry PLC 21. Use /dev/ttySC2 for other models.
    framer=FramerType.RTU,
    baudrate=9600
)

Master (client)

Run this code on the second Raspberry Pi PLC. It connects to the slave and reads and writes holding registers.

from pymodbus.client import ModbusSerialClient
from pymodbus.framer import FramerType

client = ModbusSerialClient(
    port='/dev/ttySC0',
    framer=FramerType.RTU,
    baudrate=9600,
    timeout=3
)

if not client.connect():
    print("Connection failed. Check RS-485 wiring and port.")
    exit()

SLAVE_ID = 1
REGISTER_ADDRESS = 0
data_to_write = [10, 20, 30, 40, 50]

# Write registers
response = client.write_registers(REGISTER_ADDRESS, data_to_write, slave=SLAVE_ID)
if not response.isError():
    print("Write successful")
else:
    print("Write error:", response)

# Read registers
response = client.read_holding_registers(REGISTER_ADDRESS, count=5, slave=SLAVE_ID)
if not response.isError():
    print("Read successful:", response.registers)
else:
    print("Read error:", response)

client.close()

Wiring: Connect RS-485 A+ to A+ and B- to B- between the two units. Both PLCs use /dev/ttySC0 in this example.

Expected output when the communication is working:

Write successful
Read successful: [10, 20, 30, 40, 50]

Modbus RTU with Node-RED

This example uses Node-RED as the master and the Python slave above as the server.

  1. Install the node-red-contrib-modbus package from the Node-RED palette manager.
  2. Build the following flow: Inject → Function → Modbus Write and Modbus Read → Debug
  3. Configure the Function node to increment a counter on each trigger:
if (context.hasOwnProperty('num')) {
    context.num = context.num + 1;
} else {
    context.num = 0;
}
msg.payload = context.num;
return msg;
  1. Configure the Modbus Write node:
    • Server type: Serial
    • Serial port: /dev/ttySC0
    • Serial type: RTU
    • Baud rate: 9600
    • Function code: FC 6: Preset Single Register
    • Register address: 0
  2. Configure the Modbus Read node with the same server settings:
    • Function code: FC 3: Read Holding Registers
    • Address: 0, Quantity: 1
    • Poll rate: 5000 ms

The flow will write and read register 0 every 5 seconds. Use the Python slave from the previous section as the server.

Modbus TCP with Python

Testing with a single device

You do not need two Raspberry Pi PLCs to test Modbus TCP. You can run both the slave and the master on the same unit using localhost as the address.

Open two terminal sessions on the same PLC. Run the slave in the first and the master in the second.

Slave (server)

from pymodbus.server import StartTcpServer
from pymodbus.datastore import (
    ModbusServerContext,
    ModbusSequentialDataBlock,
    ModbusSlaveContext
)

store = ModbusSlaveContext(
    di=ModbusSequentialDataBlock(0, [0] * 10000),
    co=ModbusSequentialDataBlock(0, [0] * 10000),
    hr=ModbusSequentialDataBlock(0, [0] * 10000),
    ir=ModbusSequentialDataBlock(0, [0] * 10000)
)
context = ModbusServerContext(slaves=store, single=True)

# Listens on all interfaces, port 502
# For single-device testing, use address=("localhost", 502)
StartTcpServer(context=context, address=("0.0.0.0", 502))

Master (client)

from pymodbus.client import ModbusTcpClient

# Replace with the slave IP, or use "localhost" for single-device testing
client = ModbusTcpClient(host="192.168.1.100", port=502, timeout=3)

if not client.connect():
    print("Connection failed. Check IP address and network configuration.")
    exit()

SLAVE_ID = 1
REGISTER_ADDRESS = 0
data_to_write = [10, 20, 30, 40, 50]

# Write registers
response = client.write_registers(REGISTER_ADDRESS, data_to_write, slave=SLAVE_ID)
if not response.isError():
    print("Write successful")
else:
    print("Write error:", response)

# Read registers
response = client.read_holding_registers(REGISTER_ADDRESS, count=5, slave=SLAVE_ID)
if not response.isError():
    print("Read successful:", response.registers)
else:
    print("Read error:", response)

client.close()

Note on Ethernet ports: Raspberry Pi PLCs have two Ethernet interfaces (eth0 and eth1). For device-to-device connections, use the top Ethernet connector on both units, or adjust the IP configuration to match your wiring.

Modbus TCP with Python

Modbus TCP with Node-RED

This flow uses Node-RED as the TCP master. You can use the Python slave above as the server.

  1. Install node-red-contrib-modbus if not already installed.
  2. Build the same flow as the RTU example: Inject → Function → Modbus Write and Modbus Read → Debug
  3. Configure the Modbus Write node:
    • Server type: TCP
    • Host: IP address of the Modbus TCP slave (or localhost for single-device testing)
    • Port: 502
    • Function code: FC 6: Preset Single Register
    • Register address: 0
  4. Configure the Modbus Read node with the same TCP server settings:
    • Function code: FC 3: Read Holding Registers
    • Address: 0, Quantity: 1
    • Poll rate: 5000 ms

The flow reads and writes register 0 every 5 seconds from the TCP slave.

Error handling and connection robustness

In production environments, connections drop and devices restart. The examples above include a basic timeout parameter and an explicit connection check. For critical applications, add a retry loop:

import time
from pymodbus.client import ModbusTcpClient

MAX_RETRIES = 5
RETRY_DELAY = 2  # seconds

client = ModbusTcpClient(host="192.168.1.100", port=502, timeout=3)

for attempt in range(MAX_RETRIES):
    if client.connect():
        print("Connected")
        break
    print(f"Connection attempt {attempt + 1} failed. Retrying in {RETRY_DELAY}s...")
    time.sleep(RETRY_DELAY)
else:
    print("Could not connect after maximum retries.")
    exit()

# ... your read/write logic ...

client.close()

Related posts

Explore the full Raspberry Pi PLC range at industrialshields.com

​Search in our Blog

Modbus TCP and RTU with Raspberry Pi PLCs: Python and Node-RED examples
Joan F. Aubets - Industrial Shields June 1, 2026
Share this post
Tags

Looking for your ideal Programmable Logic Controller?

Take a look at this product comparison with other industrial controllers Arduino-based. 

We are comparing inputs, outputs, communications and other features with the ones of the relevant brands.

PLC Comparison