Industrial IoT

Modbus RTU to MQTT Gateway with RS-485: Wiring, Settings and Code

Connect RS-485 Modbus RTU energy meters and sensors to a gateway that publishes their readings over MQTT. Covers wiring, termination, serial settings, register maps and a complete Python poller.

RS-485 bus of energy meters wired to a small Linux gateway that sends Modbus RTU to MQTT messages to the cloud
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

A Modbus RTU to MQTT gateway lets you take readings from RS-485 energy meters, flow meters, drives and sensors and publish them to a broker where dashboards and analytics can use them. This tutorial covers the physical layer that causes most problems (wiring, termination, serial settings), how to read a vendor register map, and a complete Python gateway that polls several devices on one bus and publishes their values over MQTT. A Raspberry Pi or any Linux box with a USB to RS-485 adapter is enough to follow along.

What Makes Modbus RTU Different from Modbus TCP

The Modbus data model is the same: coils, discrete inputs, holding registers and input registers. What changes is the transport. Modbus RTU runs over a serial line, almost always RS-485, as a multi-drop bus. One master polls up to 247 slave devices, each with a unique unit address. Frames are binary with a CRC, and frame boundaries are defined by silent gaps on the line.

That has practical consequences:

  • Only one master per bus. If a building management system or HMI already polls the meters, your gateway cannot simply join the same wires. You need a separate bus, a device with a second port, or a gateway that sits in front of both.
  • The bus is slow and shared. At 9600 baud, each request and response takes tens of milliseconds, and requests to different devices are serialised. Polling plans matter.
  • Electrical problems look like software problems. Missing termination or a swapped pair produces timeouts and CRC errors that no amount of code will fix.

If your devices are on Ethernet, use our Modbus TCP to MQTT tutorial instead.

Hardware You Need

  1. A gateway computer: a Raspberry Pi, industrial PC or any Linux machine.
  2. A USB to RS-485 adapter, ideally an isolated one with selectable termination and bias resistors. Isolation protects the gateway from ground potential differences between cabinets.
  3. Twisted-pair cable for the bus, preferably shielded, with a third conductor for the signal common.
  4. The Modbus manual for each device. You need its register map and its default serial settings.

RS-485 Wiring Done Right

RS-485 is robust when wired as intended and frustrating when not. Follow these rules:

  1. Daisy chain, not star. Run the bus from device to device in a line. Short stubs are tolerable; long branches cause reflections.
  2. Terminate both ends. Place a 120 ohm resistor across the A/B pair at the two physical ends of the bus, and nowhere else. Many adapters and meters have a termination switch or jumper.
  3. Bias once. Bias (fail-safe) resistors keep the line in a defined state when nobody is talking. Enable them in one place, usually the master adapter.
  4. Connect the common. Despite the name "two-wire", connect the signal ground (often labelled GND, COM or C) between devices so the receivers share a reference.
  5. Watch the A/B labelling. Vendors disagree on whether A is positive or negative, and some use D+ and D-. If nothing answers, swapping A and B at the adapter is a legitimate first test.
  6. Keep it away from power cables. Route the bus separately from motor and VFD cables, and ground the shield at one end.

Serial Settings: Baud, Parity and Stop Bits

Every device on a bus must use the same settings, and every device needs a unique address.

Setting Common values Notes
Baud rate 9600, 19200, 38400 9600 and 19200 are the most common defaults; longer cables favour lower rates
Data bits 8 Modbus RTU always uses 8 data bits
Parity Even, None, Odd The Modbus specification defaults to even parity, but many devices ship with none
Stop bits 1 or 2 With no parity, the specification calls for 2 stop bits, though many devices accept 1
Unit address 1 to 247 Set on the device front panel or by software; must be unique on the bus

Write the settings down as a short code such as 9600 8E1 or 19200 8N2, and set every device to match before you connect anything.

Reading a Register Map

A device manual lists registers in a table with address, description, data type, unit and sometimes a scale factor. The example below is a hypothetical three-phase energy meter used for this tutorial; real meters differ, so always take addresses from your own manual.

Description Function Address (0-based) Type Unit
Voltage L1-N 04 input 0 float32 V
Current L1 04 input 6 float32 A
Active power total 04 input 52 float32 W
Frequency 04 input 70 float32 Hz
Imported active energy 04 input 72 float32 kWh

Three things to check in every manual: whether addresses are zero-based or written as 30001 style (subtract the offset), whether 32-bit values use high-word-first or low-word-first order, and whether integer registers need a scale factor.

Prerequisites on the Gateway

sudo usermod -aG dialout $USER     # allow serial port access, then log out and back in
ls -l /dev/serial/by-id/           # stable device path for your adapter
python3 -m venv ~/rtu-gw && source ~/rtu-gw/bin/activate
pip install "pymodbus[serial]>=3.8,<3.9" "paho-mqtt>=2.0"

Use the /dev/serial/by-id/... path rather than /dev/ttyUSB0, because USB device numbers can change after a reboot.

The Modbus RTU to MQTT Gateway Code

The gateway polls each meter in turn, reads each register block in one request where possible, and publishes one JSON message per meter per cycle. Bundling a meter's values into one message keeps the message rate low, which is what you want on a slow bus and on a metered uplink.

# rtu_mqtt_gateway.py - poll Modbus RTU meters over RS-485, publish to MQTT
import json, logging, struct, time, uuid
import paho.mqtt.client as mqtt
from pymodbus.client import ModbusSerialClient

logging.basicConfig(level=logging.INFO)
log = logging.getLogger("rtu-gw")

# Replace port with your /dev/serial/by-id/... path in production
SERIAL = dict(port="/dev/ttyUSB0", baudrate=9600, parity="E", stopbits=1, bytesize=8, timeout=0.5)
POLL_SECONDS = 10
TOPIC_ROOT = "connectmq/demo/e5a1/plant1/energy"     # use your own random id
MQTT_HOST, MQTT_PORT = "broker.connectmq.com", 1883   # production: 8883 with TLS
MQTT_USER, MQTT_PASS = None, None                     # production: "YOUR_USERNAME", "YOUR_PASSWORD"
CLIENT_ID = f"rtu-gw-{uuid.uuid4().hex[:8]}"          # production: "YOUR_CLIENT_ID"

# Meters on the bus: Modbus unit address -> topic name
METERS = [
    {"unit": 1, "name": "mdb1_incomer"},
    {"unit": 2, "name": "mdb1_compressors"},
]


def f32(regs, offset, big_endian_words=True):
    words = regs[offset:offset + 2] if big_endian_words else regs[offset:offset + 2][::-1]
    return struct.unpack(">f", struct.pack(">HH", *words))[0]


def read_meter(client, unit):
    """Read the hypothetical meter's float32 input registers in two block requests."""
    block_a = client.read_input_registers(0, count=8, slave=unit)        # voltage, current
    block_b = client.read_input_registers(52, count=24, slave=unit)      # power .. energy
    for rr in (block_a, block_b):
        if rr.isError():
            raise IOError(str(rr))
    a, b = block_a.registers, block_b.registers
    return {
        "voltage_l1": round(f32(a, 0), 1),
        "current_l1": round(f32(a, 6), 2),
        "power_total": round(f32(b, 0), 0),
        "frequency": round(f32(b, 18), 2),
        "energy_import": round(f32(b, 20), 1),
    }


def main():
    mq = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID)
    if MQTT_USER:
        mq.username_pw_set(MQTT_USER, MQTT_PASS)
        mq.tls_set()
    mq.will_set(f"{TOPIC_ROOT}/gateway", "offline", qos=1, retain=True)
    mq.on_connect = lambda c, u, f, rc, p: c.publish(f"{TOPIC_ROOT}/gateway", "online", qos=1, retain=True)
    mq.connect(MQTT_HOST, MQTT_PORT, keepalive=60)
    mq.loop_start()

    bus = ModbusSerialClient(**SERIAL)
    errors = {m["unit"]: 0 for m in METERS}

    while True:
        cycle_start = time.monotonic()
        if not bus.connected and not bus.connect():
            log.error("cannot open serial port %s", SERIAL["port"])
            time.sleep(5)
            continue
        for meter in METERS:
            topic = f"{TOPIC_ROOT}/{meter['name']}"
            try:
                values = read_meter(bus, meter["unit"])
                errors[meter["unit"]] = 0
                payload = {**values, "quality": "good",
                           "ts": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())}
                mq.publish(topic, json.dumps(payload), qos=1, retain=True)
                log.info("%s %s", meter["name"], values)
            except Exception as exc:
                errors[meter["unit"]] += 1
                log.warning("unit %s: %s", meter["unit"], exc)
                if errors[meter["unit"]] == 3:   # publish bad quality once, after 3 misses
                    mq.publish(topic, json.dumps({"quality": "bad"}), qos=1, retain=True)
            time.sleep(0.05)                     # small gap between devices on the bus
        time.sleep(max(0, POLL_SECONDS - (time.monotonic() - cycle_start)))


if __name__ == "__main__":
    main()

Note the offsets in read_meter: block B starts at register 52, so frequency at register 70 is offset 18 and imported energy at 72 is offset 20. Getting these offsets wrong is the most common bug when you batch reads, so print raw registers once while commissioning.

Run and Verify

Run the gateway, then watch the output from any machine:

python rtu_mqtt_gateway.py
mosquitto_sub -h broker.connectmq.com -p 1883 -i rtu-viewer-e5a1 -t "connectmq/demo/e5a1/#" -v
connectmq/demo/e5a1/plant1/energy/mdb1_incomer {"voltage_l1": 231.4, "current_l1": 18.72, "power_total": 12890.0, "frequency": 50.01, "energy_import": 48213.6, "quality": "good", "ts": "2026-10-16T10:05:00Z"}

The public MQTT test broker is readable by anyone, so use it only for test data. For a real site, create a private broker and switch to port 8883 with your credentials.

Troubleshooting Modbus RTU

Symptom Likely cause What to try
No response from any device A/B swapped, wrong baud or parity, wrong port Swap A/B, confirm 8E1 vs 8N1, check /dev/serial/by-id
Some devices answer, others time out Duplicate or wrong unit address, wiring break Disconnect all but one device and test each
Intermittent CRC errors Missing termination, star topology, noise from drives Terminate both ends, re-route cable, use shielded twisted pair
Works at 9600, fails at higher rates Long cable or poor wiring Stay at the lower rate or fix the cabling
Exception code 2 (illegal data address) Address beyond the device's map or block spans a gap Split the block into smaller reads
Random timeouts while another system is running Two masters on the same bus Give the gateway its own bus or port

Scaling Up

A single RS-485 port is fine for a handful of meters. When you have dozens, consider one adapter per panel or Modbus RTU to TCP converters that let one gateway reach several buses over Ethernet, then reuse the TCP bridge. Publishing on change rather than every cycle, as described in our report by exception guide, also keeps the uplink quiet. For remote sites on mobile data, see MQTT over cellular.

A hosted MQTT broker such as ConnectMQ removes the need to run a server for these gateways. The free Serverless plan supports up to 25 connections, enough for a pilot with several gateways, and the paid Serverless plan scales to 1,000. The portal's threshold alert rules can watch values such as power_total and log an alert when a limit is crossed. For the architecture behind it all, read the industrial IoT MQTT guide.

Get Started with ConnectMQ

Get your RS-485 meters online today. Sign up at app.connectmq.com, choose the free Serverless plan (no credit card), create a broker and copy the generated username, password and client ID into the gateway script. Your meter readings will show up in the ConnectMQ live topic explorer as soon as the first poll completes.

Frequently Asked Questions

How many Modbus RTU devices can one RS-485 bus support?

Modbus addresses allow up to 247 devices, but the electrical limit depends on transceiver load; standard RS-485 supports 32 unit loads, and many modern transceivers present fractional loads. In practice, polling time usually becomes the limit first.

Can I read Modbus RTU devices that a BMS already polls?

Not on the same bus, because Modbus RTU allows only one master. Use a second port on the device, a separate meter output, or ask the BMS integrator to expose the data another way.

Which baud rate should I use for Modbus RTU?

Use what every device on the bus supports, typically 9600 or 19200. Lower rates tolerate longer cables and noisier environments better.

Should a Modbus RTU gateway publish every poll?

Not necessarily. Bundling a device's values into one message per cycle, or publishing only on significant change, keeps message volume low without losing information.

Related articles

IIoT Edge Gateway: What It Does and How to Choose One

An IIoT edge gateway connects machines that speak Modbus, OPC UA or proprietary protocols to MQTT and the cloud. Learn its core roles, common architectures and how to choose the right one.

7 min read

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.