|
2026 Industrial IoT Practice: ESP32 Modbus RTU to MQTT Protocol Bridge

2026 Industrial IoT Practice: ESP32 Modbus RTU to MQTT Protocol Bridge

Industrial sites have massive amounts of legacy equipment still using Modbus RTU protocol communicating over RS485 buses. These devices have no networking capability themselves. But what if you want to push their data to the cloud for remote monitoring? The most practical solution is to build a protocol bridge gateway - read Modbus RTU data and convert it to MQTT to send to the cloud platform.

Today we’ll use an ESP32 plus an RS485 module to build an industrial data collector from scratch. No PLC needed, no expensive industrial gateway required, cost under 100 yuan.

Why Modbus RTU to MQTT is Needed

Modbus RTU is one of the most fundamental communication protocols in industry, supported by大量 sensors, instruments, and variable frequency drives. But it has a fatal flaw: it only runs on RS485 buses, limited transmission distance, and can’t directly access the internet.

MQTT is the opposite - lightweight, TCP/IP-based, naturally suited for the cloud. Bridging the two is like giving traditional industrial equipment “wings”.

This solution is particularly suitable for:

  • Factory workshop temperature/pressure/flow data remote collection

  • Building automation system (BAS) data to cloud

  • Agricultural greenhouse environmental sensor networking

  • Legacy equipment retrofit, no need to replace entire system

Hardware List

ComponentModelQuantityReference Price
Main boardESP32-WROOM-32 development board1¥25
RS485 moduleMAX485 TTL to RS4851¥5
Debug Modbus deviceTemperature/humidity transmitter RS485 output1¥35
Connection cablesJumper wires1 set¥3
Power supply5V/1A adapter1¥8
TotalAbout ¥76

If you already have field equipment supporting Modbus RTU, you can skip the temperature/humidity transmitter.

Wiring Instructions

Wiring from ESP32 to MAX485 module is straightforward:

MAX485 PinESP32 PinDescription
DIGPIO17 (TX)Data input
ROGPIO16 (RX)Data output
DE + REGPIO4Transmit/receive control
VCC5VPower
GNDGNDGround

RS485 bus wiring:

  • A+ connects to all devices’ A terminal (positive)

  • B- connects to all devices’ B terminal (negative)

  • Both ends of bus need 120Ω termination resistors (required when cable length exceeds 100 meters)

⚠️ Notes:

  • All devices must share common ground, otherwise communication is unstable

  • RS485 is differential signal, A/B cannot be reversed, reversal will cause complete data read failure

  • DE and RE shorted together then connected to same GPIO, high level for transmit, low level for receive

Software Implementation

We use Arduino IDE + two key libraries:

  • ModbusMaster — Modbus RTU master protocol stack

  • PubSubClient — MQTT client

Install libraries first, search and install in Arduino IDE.

Complete Code

#include <ModbusMaster.h>
#include <WiFi.h>
#include <PubSubClient.h>

// WiFi configuration
const char* ssid = "YourWiFi";
const char* password = "YourPassword";

// MQTT configuration
const char* mqtt_server = "broker.emqx.io";
const int mqtt_port = 1883;
const char* mqtt_client_id = "modbus-gateway-001";
const char* mqtt_topic = "factory/sensor/data";

// RS485 pins
#define RX_PIN    16
#define TX_PIN    17
#define DE_RE_PIN 4

ModbusMaster node;
WiFiClient espClient;
PubSubClient mqtt(espClient);

// Control RS485 transmit/receive
void preTransmission() {
    digitalWrite(DE_RE_PIN, HIGH);
}

void postTransmission() {
    digitalWrite(DE_RE_PIN, LOW);
}

void setup() {
    Serial.begin(115200);

    // Initialize RS485 control pin
    pinMode(DE_RE_PIN, OUTPUT);
    digitalWrite(DE_RE_PIN, LOW);

    // Initialize Modbus master
    node.begin(1, Serial2);  // Slave address 1
    node.preTransmission(preTransmission);
    node.postTransmission(postTransmission);

    // Configure Serial2 RX/TX
    Serial2.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN);

    // Connect WiFi
    WiFi.begin(ssid, password);
    Serial.print("Connecting WiFi");
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("\nWiFi connected: " + WiFi.localIP().toString());

    // Connect MQTT
    mqtt.setServer(mqtt_server, mqtt_port);
    mqtt_connect();
}

void mqtt_connect() {
    while (!mqtt.connected()) {
        if (mqtt.connect(mqtt_client_id)) {
            Serial.println("MQTT connected");
        } else {
            Serial.print("MQTT connect failed: ");
            Serial.println(mqtt.state());
            delay(3000);
        }
    }
}

void loop() {
    if (!mqtt.connected()) {
        mqtt_connect();
    }
    mqtt.loop();

    // Read Modbus registers (holding registers starting from 0x0000, read 2 registers)
    uint8_t result = node.readHoldingRegisters(0x0000, 2);

    if (result == node.ku8MBSuccess) {
        // Assume register 0 = temperature(×10), register 1 = humidity(×10)
        float temperature = node.getResponseBuffer(0x00) / 10.0;
        float humidity = node.getResponseBuffer(0x01) / 10.0;

        // Build JSON message
        String payload = "{";
        payload += "\"temperature\":" + String(temperature, 1) + ",";
        payload += "\"humidity\":" + String(humidity, 1) + ",";
        payload += "\"timestamp\":" + String(millis());
        payload += "}";

        // Publish to MQTT
        if (mqtt.publish(mqtt_topic, payload.c_str())) {
            Serial.println("Published: " + payload);
        }
    } else {
        Serial.print("Modbus error: ");
        Serial.println(result, HEX);
    }

    delay(5000);  // Collect once every 5 seconds
}

Code Key Points

  1. RS485 transmit/receive control via DE_RE_PIN, pull high before transmit, pull low after transmit, ModbusMaster library callbacks handle automatically

  2. Read holding registers using readHoldingRegisters() method, pass starting address and register count

  3. Convert register raw values to actual physical quantities (like divide by 10 to get temperature), then package as JSON and publish to MQTT

MQTT Message Format

Collector publishes a JSON message to factory/sensor/data every 5 seconds:

{
  "temperature": 23.5,
  "humidity": 65.2,
  "timestamp": 123456789
}

In the cloud you can use Node-RED, InfluxDB + Grafana, or even directly subscribe to this topic with Home Assistant for data visualization and alerts.

Common Problem Troubleshooting

Problem 1: Modbus Read Returns Error Code

This is the most common pitfall. Error code reference table:

Error CodeMeaningTroubleshooting Direction
0xE2Invalid responseCheck baud rate, slave address are correct
0xE4TimeoutCheck if A/B wires reversed, termination resistors in place
0xE6Data checksum failedCheck serial parameters (8N1), cable quality

Troubleshooting steps:

  1. Verify slave device manual, confirm baud rate (commonly 9600), data bits (8), stop bits (1), parity (none) settings are correct

  2. Check if slave address matches parameter in code node.begin(), default is 1

  3. Use multimeter to measure RS485 bus A-B voltage, should fluctuate between 0.2V~5V during normal communication, and confirm termination resistors (120Ω) are installed

Problem 2: MQTT Connection Repeatedly Disconnects

  • Confirm ESP32 WiFi signal strength is sufficient (above -70dBm)

  • Check if MQTT broker has anonymous connection enabled (broker.emqx.io supports anonymous, but enterprise environments may need username/password)

  • If data volume is large, reduce mqtt.setBufferSize() value or increase mqtt.setKeepAlive() time

Problem 3: Data Jumps Severely

  • When RS485 bus is too long, signal reflection causes data anomalies, check if termination resistors (120Ω) are correctly installed at both bus ends

  • Confirm all devices’ common ground connection is reliable

  • Software level can add simple filtering: read 3 times consecutively and take median, or use moving average

Problem 4: ESP32 Frequently Restarts

May be watchdog triggered. Common causes:

  • loop() has long blocking operations (like WiFi connection timeout too long)

  • Stack overflow, try adding ESP.getFreeHeap() monitoring in setup()

  • Recommend adding retry count limit on Modbus read failure (like max 3 retries)

Advanced Extensions

After completing basic collection, you can continue to extend:

  • Multi-slave polling: Modify node.begin() slave address, use array to poll multiple devices

  • Offline caching: Add SD card module, cache data locally when network disconnects

  • OTA upgrades: Enable ESP32 OTA function, remote firmware updates

  • TLS encryption: Enable TLS for MQTT connection, ensure data transmission security

Summary

Using ESP32 + RS485 module to build Modbus RTU to MQTT gateway, low cost, fast deployment, simple maintenance. For small to medium-scale industrial IoT retrofit projects, this is much more cost-effective than buying commercial industrial gateways. Core idea is: use ModbusMaster to read register data, use PubSubClient to send MQTT, the protocol conversion logic in between is just a few lines of code.

Hope this blog post is helpful to you!