Modbus RTU and TCP for Retrofit Industrial IoT: Bridging Legacy PLCs to Azure
Start Blog Modbus RTU and TCP for Retrofit Industrial IoT: Bridging Legacy PLCs to Azure
Cloud Architecture Best Practices Industrial IoT Modbus

Modbus RTU and TCP for Retrofit Industrial IoT: Bridging Legacy PLCs to Azure

📅 April 2026 ⏳ 12 min read FSS Engineering Team

Walk into any factory built before 2010 and you will meet the same ghost: a control cabinet full of PLCs that still run the line perfectly, speak Modbus, and were never designed to touch the public internet. Replacing them is rarely an option. The capex is enormous, the downtime is unacceptable, and frankly, those controllers have outlasted three generations of IT strategy. The pragmatic path forward is the retrofit: leave the PLCs alone, drop in an edge gateway, and stream the data they have been quietly producing for thirty years into Azure IoT Hub where it can finally do something useful.

This post is the field guide we wish we had on our first brownfield project. We will cover Modbus RTU and TCP at the bit level, the RS-485 wiring mistakes that eat your weekends, the function codes you will actually use, addressing pitfalls that have ruined more integrators than any other single thing, and a working ESP32 gateway pattern that bridges Modbus to MQTT to Azure. If you are evaluating industrial IoT retrofit options, start here.

Why Modbus Is Still Everywhere

Modbus was published by Modicon in 1979. It is a request/response protocol, master/slave (the modern terminology is client/server), and it carries no security, no authentication, and almost no metadata. That should be a problem. In practice it is the reason Modbus won. The spec fits on a few dozen pages, every PLC vendor implements it, and the wire format is so simple you can decode it with an oscilloscope and a paper notepad.

You will encounter Modbus in two main flavors. Modbus RTU runs over RS-485 (occasionally RS-232) at 9600 to 115200 baud, with a compact binary frame and a CRC-16. Modbus TCP wraps the same protocol data unit in a TCP packet on port 502, drops the CRC (TCP handles integrity), and adds a 7-byte MBAP header. There is also Modbus ASCII, which you will hopefully never meet.

The Function Codes You Actually Need

Modbus defines around twenty function codes, but four of them cover roughly 95 percent of real integrations:

You will also occasionally see 0x01 Read Coils and 0x05 Write Single Coil for boolean I/O, and 0x02 Read Discrete Inputs for read-only digital inputs. Avoid the diagnostic function codes (0x08, 0x11) unless you have a specific reason; vendor support is uneven and they will not earn their keep.

RS-485 Wiring: Where Projects Die

If your Modbus RTU bus is unreliable, the cause is almost never the protocol. It is the physical layer. RS-485 is a differential pair (A and B, sometimes labeled D+ and D-), optionally with a shared ground reference, capable of running 1200 metres at 9600 baud if you respect three rules.

1. Topology must be a daisy chain

No stars. No spurs longer than a few centimetres. Cable enters a device on one terminal pair and leaves on the next. Every star topology in production is a future failure waiting for a humid morning.

2. Termination at both ends

120-ohm resistors across A and B at the two physical ends of the bus. Not in the middle. Not at every device. Some transceivers and PLCs have a DIP switch or jumper to enable internal termination — verify, do not assume. Without termination you get reflections that look like random CRC errors.

3. Bias the idle line

When no transceiver is driving the bus, A and B float. Noise gets interpreted as start bits. Pull A up to 5 V via roughly 680 ohms and B down to ground via the same value, applied once on the bus (typically at the master). Modern transceivers like the MAX13487 do failsafe biasing internally, but most legacy installs do not.

Use shielded twisted pair, ground the shield at one end only, and keep the cable away from VFD outputs and contactors. We have seen a 200-metre run go from 80 percent CRC errors to zero by moving it 30 cm away from a motor cable.

Addressing: The Bane of Modbus Integrators

This is where every integrator gets bitten at least once. The Modbus specification uses zero-based addressing on the wire. Many PLC vendors document their register maps using one-based numbering, sometimes with a leading prefix that indicates the register type.

If your reads return nonsense or off-by-one values, your first hypothesis should be one-based versus zero-based. Your second hypothesis should be byte order. Modbus transmits each 16-bit register big-endian, but 32-bit values (floats, longs) span two registers and vendors disagree on word order. You may see ABCD, CDAB, BADC, or DCBA. Schneider tends to use one order, Siemens another, and the cheap Chinese inverter you bought from AliExpress will use whatever the engineer felt like that morning. Confirm with a known value — have the PLC display 1.0 as a float and read both word orders to see which produces the IEEE 754 bit pattern 0x3F800000.

Signed versus unsigned is the third common trap. A temperature reading of -5 looks like 65531 if you decode the register as unsigned. Always check the vendor doc for the data type per register and configure your gateway map accordingly.

Gateway Hardware: ESP32 vs Commercial

For low-channel-count, single-cabinet retrofits, an ESP32 with an isolated RS-485 transceiver is hard to beat on cost and flexibility. A typical bill of materials looks like:

Total parts cost lands around 25 to 40 EUR, the firmware is fully under your control, and you can deploy OTA updates via Azure IoT Hub device twins. We routinely build this kind of connected device as the foundation for larger fleets.

Commercial gateways from Moxa, Advantech, or HMS Networks make sense when you need certifications (UL, ATEX), redundant power, hardened temperature ranges, or when the customer’s procurement team will simply not approve a custom board. They cost 400 to 1500 EUR per node and the firmware is a black box. The trade is operational comfort for vendor lock-in.

The Edge Gateway Pattern: Modbus to MQTT to Azure

The architecture we deploy looks like this:

  1. Edge gateway polls Modbus devices on a schedule (1 to 60 seconds depending on the signal).
  2. Decoded values are normalized to engineering units and tagged with a stable identifier.
  3. Gateway publishes JSON over MQTT to Azure IoT Hub using the device SDK.
  4. IoT Hub routes messages to Event Hubs, then to Stream Analytics or directly into a time-series store.
  5. An analytics dashboard visualizes trends and triggers alerts.

This pattern is the backbone of nearly every Azure IoT data pipeline we build. Read more on Hub provisioning in our piece on Azure IoT Hub for device manufacturers.

Working ESP32 Modbus Client Code

Below is a stripped-down but realistic Arduino-framework sketch using the eModbus library. It polls four holding registers from a slave at address 1, decodes them, and publishes JSON over MQTT.

#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <ModbusClientRTU.h>

#define RS485_RX  16
#define RS485_TX  17
#define RS485_DE  4

HardwareSerial RS485(2);
ModbusClientRTU MB(RS485_DE);
WiFiClient net;
PubSubClient mqtt(net);

uint32_t lastPoll = 0;
const uint8_t SLAVE_ID = 1;
const uint16_t START_REG = 0;   // 40001 in vendor docs
const uint16_t COUNT = 4;

void onData(ModbusMessage response, uint32_t token) {
  uint16_t regs[4];
  for (uint8_t i = 0; i < COUNT; i++) {
    response.get(3 + i * 2, regs[i]);
  }
  // Word swap for ABCD float (vendor specific)
  union { uint32_t u; float f; } conv;
  conv.u = ((uint32_t)regs[0] << 16) | regs[1];
  float temperature = conv.f;
  int16_t pressure = (int16_t)regs[2];   // signed
  uint16_t status   = regs[3];

  char payload[160];
  snprintf(payload, sizeof(payload),
    "{"t":%.2f,"p":%d,"s":%u,"ts":%lu}",
    temperature, pressure, status, millis());
  mqtt.publish("devices/gw01/telemetry", payload);
}

void onError(Error err, uint32_t token) {
  Serial.printf("Modbus error 0x%02X token %un", (int)err, token);
}

void setup() {
  Serial.begin(115200);
  RS485.begin(19200, SERIAL_8E1, RS485_RX, RS485_TX);
  MB.onDataHandler(&onData);
  MB.onErrorHandler(&onError);
  MB.setTimeout(1000);
  MB.begin(RS485);

  WiFi.begin("plant-iot", "secret");
  while (WiFi.status() != WL_CONNECTED) delay(200);
  mqtt.setServer("hub.azure-devices.net", 8883);
  mqtt.connect("gw01", "hub.azure-devices.net/gw01/?api-version=2021-04-12", "<SAS>");
}

void loop() {
  mqtt.loop();
  if (millis() - lastPoll > 5000) {
    lastPoll = millis();
    Error e = MB.addRequest(0x1234, SLAVE_ID, READ_HOLD_REGISTER, START_REG, COUNT);
    if (e != SUCCESS) Serial.printf("Queue err 0x%02Xn", (int)e);
  }
}

This is intentionally minimal. Production firmware adds TLS via Azure root CA, SAS token rotation, device twin handling, exponential backoff on Wi-Fi loss, and a watchdog. But the polling loop and decode logic stay essentially this shape.

Polling Strategy and Rate Limits

Modbus RTU is half-duplex. Only one device transmits at a time. At 19200 baud, a typical read of 10 registers plus turnaround takes around 30 ms. With 20 slaves on a bus, polling every signal every second is realistic; polling every 100 ms is not. Group registers into contiguous blocks per slave so one request returns many values. Stagger slaves so you do not finish one polling cycle and immediately start the next — leave breathing room for retries.

For high-channel-count systems, deploy multiple RS-485 segments behind one gateway with a separate UART per segment, or use a Modbus TCP gateway that lets you parallelize across slaves over a switched network.

Error Handling and Resilience

Treat every Modbus read as a thing that will fail. Build your gateway around three rules:

  1. Cache last-known-good values with a timestamp. When a poll fails, publish the cached value with a quality flag (good, stale, bad) rather than dropping the message.
  2. Buffer telemetry locally when the cloud connection drops. A 4 MB partition on the ESP32 holds hours of compressed history. Replay on reconnect.
  3. Alert on protocol-level failures, not just on missing values. A slave that returns exception code 0x02 (illegal data address) means your map is wrong, not that the sensor is dead.

Security: Modbus Has None

Every Modbus master can write to every register on every slave on the bus. There is no authentication, no encryption, no concept of a privileged operation. Modbus TCP exposes the same situation across the entire IP network. The only safe assumption is that the Modbus segment is hostile-by-default and must be isolated.

For deeper context on protocol security across the stack, see our overview of industrial protocols and where they fit.

Real-World Example: 30-Year-Old PLCs in a Brownfield Plant

Consider a typical retrofit we have done several times: a metals processing line built in the early 1990s. Six Allen-Bradley SLC 500 PLCs control the line, each with a Modbus RTU adapter card added in a previous upgrade. The customer wants OEE visibility, downtime root-cause analysis, and a maintenance dashboard. Replacing the PLCs would cost north of 400,000 EUR and require six weeks of downtime. Neither is acceptable.

The retrofit looked like this:

  1. Two ESP32-S3 gateways in DIN-rail enclosures, one per electrical room, each handling three PLCs on a daisy-chained RS-485 bus.
  2. Vendor register maps reverse-engineered against the existing HMI; 87 signals tagged across the line (motor currents, hydraulic pressures, cycle counts, fault bits).
  3. Polling at 2 s for high-priority signals, 30 s for slow-moving ones.
  4. MQTT to Azure IoT Hub, routed to Time Series Insights and Power BI.
  5. Custom React dashboard for the maintenance team, integrated with the existing CMMS via webhook.

Total project: nine weeks, hardware cost under 5,000 EUR, zero PLC firmware changes, zero production downtime during commissioning. Within three months the dashboard had paid for itself by attributing 14 hours of unexplained downtime per month to a single intermittent hydraulic valve that had been blamed on three different things over the previous two years.

When to Move Beyond Modbus

Modbus is a fine transport for periodic numeric data. It struggles with event-driven signals, large payloads, and any kind of structured metadata. If your retrofit needs include vibration spectra, vision data, or anything sub-second, plan for OPC UA or a parallel sensor network bridged at the gateway. We often run Modbus in parallel with custom IoT sensor nodes on the same edge gateway, with the gateway harmonizing both into a single cloud-side schema.

Bring Your Brownfield Plant Online

A Modbus retrofit is the lowest-risk, fastest-payback path from a legacy plant floor to a modern data platform. Done well, it pays for itself inside a year and unlocks a decade of analytics on equipment you already own. Done badly, it produces a wall of CRC errors and a dashboard nobody trusts. The difference is the depth of the engineering — in the wiring, the register map, the polling discipline, and the cloud-side schema.

FSS designs and deploys industrial IoT retrofits end to end, from RS-485 cable selection to Azure dashboards to third-party system integrations. If you have a cabinet full of PLCs and a backlog of unanswered questions about what they are doing, talk to our connected devices team and let us scope a pilot for one line.

Building something connected?

FSS Technology designs and builds IoT products from silicon to cloud — embedded firmware, custom hardware, and Azure backends.

Talk to our team →