IoT

Modbus to MQTT: Retrofitting Legacy Industrial Machinery for Cloud Telemetry

N
Niyaz
Jul 5, 2026
6 min read
Modbus to MQTT Conversion

A staggering percentage of active machinery in manufacturing plants runs on Modbus, a protocol designed in 1979. While Modbus is incredibly reliable for local, closed-loop PLC networks, it is completely unsuited for cloud ingestion. It is unencrypted, uses a rigid polling mechanism, lacks metadata, and operates on bare register addresses. To feed industrial data into modern Analytics or Machine Learning models, you must retrofit legacy systems. This is the classic "Brownfield" problem of IIoT.

Key Takeaways

  • Strategic insights to accelerate enterprise workflows and scaling.
  • Technical considerations for resilient architectural deployments.
  • Cost-benefit analysis of modern technological frameworks.

The standard solution is using an Edge Gateway to act as a protocol translator—reading Modbus registers at a deterministic frequency, parsing the raw integers into structured JSON, and publishing the data via MQTT (Message Queuing Telemetry Transport). This guide walks you through this exact implementation using Python.

Understanding the Modbus Register Map

Unlike MQTT, which uses descriptive topics (e.g., factory/pump/vibration), Modbus relies on numeric address lookup tables. You must know the register map of the target machine. Let's assume we are monitoring an industrial chiller with the following registers:

  • Register 30001 (Input Register): Temperature (°C), scaled by 10 (e.g., a value of 234 represents 23.4°C).
  • Register 30002 (Input Register): Pressure (PSI), scaled by 100.
  • Register 40001 (Holding Register): Compressor State (0 = Off, 1 = Idle, 2 = Running).

The Implementation Strategy

Our edge gateway script will do the following:

  1. Establish a connection to the Modbus TCP server (often a PLC or remote I/O module).
  2. Establish a secure connection to our central MQTT broker.
  3. Periodically poll the registers.
  4. Scale the raw values, attach timestamps and metadata, and encode the data into a JSON string.
  5. Publish the JSON message to an structured MQTT topic.

Python Implementation: The Protocol Translator

Below is a production-grade Python script using pymodbus (for Modbus communication) and paho-mqtt (for MQTT publishing).


import time
import json
import logging
from pymodbus.client import ModbusTcpClient
import paho.mqtt.client as mqtt

# Configure Logger
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("ModbusToMQTT")

# Configurations
MODBUS_IP = "192.168.1.150"
MODBUS_PORT = 502
MQTT_BROKER = "mqtt.enterprise.io"
MQTT_PORT = 8883  # MQTTS (TLS)
MQTT_TOPIC = "factory/site_alpha/chiller_01/telemetry"

# Initialize Clients
mb_client = ModbusTcpClient(MODBUS_IP, port=MODBUS_PORT)
mqtt_client = mqtt.Client(client_id="gateway_chiller_01", callback_api_version=mqtt.CallbackAPIVersion.VERSION2)

def connect_services():
    try:
        # Connect Modbus
        if not mb_client.connected:
            mb_client.connect()
            logger.info("Connected to Modbus TCP server")
        
        # Connect MQTT (Production systems must load SSL/TLS certs here)
        # mqtt_client.tls_set(ca_certs="ca.crt", certfile="client.crt", keyfile="client.key")
        mqtt_client.connect(MQTT_BROKER, MQTT_PORT, keepalive=60)
        logger.info("Connected to MQTT Broker")
    except Exception as e:
        logger.error(f"Connection failed: {e}")

def read_telemetry():
    # Read 2 input registers starting at address 0 (30001)
    input_reg = mb_client.read_input_registers(address=0, count=2)
    # Read 1 holding register starting at address 0 (40001)
    holding_reg = mb_client.read_holding_registers(address=0, count=1)
    
    if input_reg.isError() or holding_reg.isError():
        logger.warning("Error reading Modbus registers")
        return None
        
    # Parse and Scale Values
    raw_temp = input_reg.registers[0]
    raw_pressure = input_reg.registers[1]
    raw_state = holding_reg.registers[0]
    
    temperature = raw_temp / 10.0
    pressure = raw_pressure / 100.0
    
    state_map = {0: "Off", 1: "Idle", 2: "Running"}
    status = state_map.get(raw_state, "Unknown")
    
    payload = {
        "timestamp": int(time.time()),
        "metrics": {
            "temperature_c": temperature,
            "pressure_psi": pressure,
            "status": status
        },
        "metadata": {
            "device_id": "chiller_01",
            "location": "site_alpha_zone_3"
        }
    }
    return payload

def main():
    connect_services()
    while True:
        try:
            # Reconnect if connections dropped
            if not mb_client.connected:
                mb_client.connect()
            
            payload = read_telemetry()
            if payload:
                payload_str = json.dumps(payload)
                mqtt_client.publish(MQTT_TOPIC, payload_str, qos=1)
                logger.info(f"Published payload: {payload_str}")
                
        except Exception as e:
            logger.error(f"Error during cycle: {e}")
            
        time.sleep(5)  # Poll frequency (5 seconds)

if __name__ == "__main__":
    main()

Critical Design Considerations for Production

While the script above is a strong foundation, deploying this in a harsh industrial plant requires addressing two key issues:

  • Keep-Alives and Reconnection Logic: Modbus connections can drop due to electrical noise or network resets. Your script must detect connection loss and execute retries gracefully.
  • Buffer During Network Outages: If your connection to the cloud MQTT broker drops, you will lose data. Implement a local database buffer on the gateway to temporarily cache the readings until connection is restored. Read our guide on Resilient Store-and-Forward Buffering to learn how.

Summary Comparison

Feature / Aspect Traditional Approach Modern Architecture
Scalability Vertical scaling, higher downtime Horizontal scaling, seamless
Integration Time Weeks to months Days to weeks
Maintenance Cost High overhead Optimized OpEx

Conclusion

Retrofitting legacy machinery with a Python-based protocol translator is a cost-effective and powerful way to transform legacy hardware into cloud-ready nodes. By parsing register maps on the edge and communicating via structured, lightweight MQTT JSON, manufacturers gain instant visibility without replacing multi-million dollar capital assets.

Once your Modbus machines are streaming data to the cloud, the natural next step is calculating OEE (Overall Equipment Effectiveness) automatically — tracking Availability, Performance, and Quality per machine in real time. Our Industrial OEE & Machine Monitoring System is purpose-built for exactly this: it sits on top of the Modbus-to-MQTT bridge, turns raw telemetry into OEE scores, and alerts your team the moment a machine begins deviating from standard performance.

AdaptNXT specializes in brownfield integrations, protocol translation, and custom edge gateway software development. Contact our IoT engineers to start connecting your factory today.

Ready to implement these solutions? contact our team today to get started.

`n

Frequently Asked Questions (FAQ)

How does this technology improve ROI?

By automating repetitive tasks and ensuring high availability, operational costs are minimized while output increases.

Is it suitable for legacy systems?

Yes, modular architectures allow incremental upgrades without replacing the entire legacy infrastructure.

What are the main security considerations?

End-to-end encryption, strict access controls, and regular compliance audits are essential to mitigate risks.

`n
N

Niyaz

Niyaz is a Software Engineer at AdaptNXT, specializing in secure enterprise cloud AI integrations across AWS Bedrock, Azure OpenAI, and Google Vertex AI.

Category IoT
Share this article
Link copied to clipboard!

Want to Discuss Your Next Project?

Let's explore how our expertise can drive your business forward.

Get In Touch
Call
WhatsApp
Email