Modbus to MQTT Bridge Testing — How to Validate Before Deployment

Deploying a Modbus-MQTT bridge to production without testing it against a simulator first is how engineers end up at 2 AM debugging wrong register mappings on a factory floor. This guide shows you how to build a complete test environment using a Modbus slave simulator and a local MQTT broker, validate every register mapping, and confirm data type handling before your bridge touches real hardware.

The Modbus-MQTT Bridge Architecture

A Modbus-MQTT bridge sits between a Modbus network and an MQTT broker:

Modbus Device (PLC/sensor)  →  Bridge  →  MQTT Broker  →  Application
[Slave: registers/coils]        [Polls + converts]    [Publishes topics]    [Node-RED, cloud, SCADA]

The bridge polls the Modbus slave at a configured interval, reads specified registers, converts the raw values into JSON or CSV payloads, and publishes them to MQTT topics. On the reverse path, some bridges also subscribe to MQTT topics and write values back to Modbus holding registers (write-back mode).

Testing the bridge before connecting real hardware validates three things:

  • Connectivity: The bridge can connect to the Modbus device and the MQTT broker
  • Mapping: The correct registers map to the correct MQTT topics
  • Data types: Raw register values are converted correctly (32-bit floats, scaling factors, byte order)

Setting Up the Test Environment

You need three components running simultaneously:

ComponentRoleRecommended Tool
Modbus Slave SimulatorReplaces real Modbus hardwareModbusSimulator
MQTT BrokerReceives messages from the bridgeEclipse Mosquitto (local) or HiveMQ Cloud
MQTT Client (subscriber)Inspects what the bridge publishesmosquitto_sub, MQTT Explorer, or Node-RED debug node

All three can run on the same development machine. The bridge reads from the simulator (localhost:502 for TCP) and publishes to the local broker (localhost:1883).

Configuring the Modbus Slave Simulator

The simulator acts as the Modbus device your bridge will poll. Key configuration decisions:

Mode: Match your production device. If your real PLC uses Modbus TCP on port 502, configure the simulator for TCP. If it's RTU over RS485, configure for RTU (using a virtual COM port pair on Windows, or socat on Linux).

Unit ID: Set the same Unit ID as your real device. If your real device uses ID 1, configure the simulator with ID 1.

Register values: Pre-populate holding registers with known test values that you can verify end-to-end. Avoid zeros and round numbers — they're too easy to mistake for default/uninitialised values. Use distinctive values:

# Good test values for holding registers:
HR 0 = 1234   # Should appear as 1234 in MQTT payload
HR 1 = 9999   # Or as 9.999 if scale factor /1000 is applied
HR 2+3 = combined to float 23.75   # 32-bit IEEE 754: 0x41BE0000

# For coils:
Coil 0 = ON (1)
Coil 1 = OFF (0)
Coil 2 = ON (1)

ModbusSimulator lets you set these values through its GUI and watch which registers the bridge is polling in real time — confirming the bridge is actually connecting and reading.

Running a Local MQTT Broker

Install Mosquitto for a simple local test broker:

# Ubuntu/Debian
sudo apt-get install mosquitto mosquitto-clients

# Windows: download installer from mosquitto.org
# Or use Docker:
docker run -d -p 1883:1883 eclipse-mosquitto

Start subscribing to all topics before starting the bridge so you capture every message from the beginning:

# Subscribe to everything (wildcard)
mosquitto_sub -h localhost -p 1883 -t '#' -v

# Or subscribe to a specific device prefix
mosquitto_sub -h localhost -p 1883 -t 'modbus/#' -v

The -v flag prints topic names alongside message payloads, which is essential for verifying topic mappings.

Configuring the Bridge

Bridge configuration varies by software, but the core parameters are always the same:

# Example: Node-RED Modbus-MQTT bridge configuration
# (pseudocode — actual config depends on the bridge software)

modbus:
  type: TCP
  host: 127.0.0.1   # Simulator running on localhost
  port: 502
  unit_id: 1
  timeout: 2000     # ms

polling:
  - name: "temperature"
    function: FC03   # Read Holding Registers
    address: 0       # Register 40001 in most tools
    count: 1
    interval: 1000   # ms
    mqtt_topic: "plant/zone1/temperature"
    scale: 0.1       # Raw value 235 = 23.5 degrees
    
  - name: "motor_running"
    function: FC01   # Read Coils
    address: 0
    count: 1
    interval: 500
    mqtt_topic: "plant/zone1/motor/status"

mqtt:
  host: 127.0.0.1
  port: 1883
  client_id: "modbus-bridge-test"
  qos: 1

Set polling intervals short (500ms) during testing so you see values update quickly. You can increase intervals before production deployment.

Validating Register Mappings

With the simulator running, broker listening, and bridge started, verify each register mapping systematically:

  1. Confirm bridge connects to simulator: Check bridge logs for "Connected to Modbus device" and check ModbusSimulator's connection status pane for an active connection
  2. Confirm MQTT messages appear: Within one polling interval, messages should appear in your mosquitto_sub output
  3. Verify register values match: For each configured register, compare the value set in the simulator against what appears in the MQTT payload
  4. Test value changes propagate: Change a holding register value in the simulator and confirm the MQTT message updates within one polling interval
# Example: change HR 0 from 1234 to 5678 in simulator
# Expected MQTT output within ~1 second:
# plant/zone1/temperature {"value": 567.8}  (if scale factor 0.1 applied)

# If you see the old value persisting: check polling interval and bridge logs
# If you see wrong value: check scale factor and register address mapping

Testing Data Type Conversion

Data type errors are the most common source of bridge deployment failures. Test each type you use:

32-bit unsigned integers (UINT32)

# Set HR 10 = 0x0001 (high word) and HR 11 = 0x86A0 (low word)
# Combined: 0x000186A0 = 100,000 decimal
# Expected MQTT payload: {"value": 100000}
# Wrong byte order would give: {"value": 25165824} or similar garbage

32-bit IEEE 754 floats

# IEEE 754 representation of 23.75:
# Hex: 0x41BE0000
# High word (HR 20): 0x41BE = 16830
# Low word (HR 21): 0x0000 = 0
#
# Set HR 20 = 16830, HR 21 = 0 in simulator
# Expected: {"temperature": 23.75}
# If you get a very large or negative number: byte order is wrong
# Try swapping to CDAB (byte-swap) or BADC (word-swap) register order

Coil to boolean conversion

# Set Coil 0 = ON in simulator
# Expected: {"motor_running": true} or {"motor_running": 1} depending on bridge config
# If you get {"motor_running": false} when coil is ON: coil addressing is off by 1
# Try coil address 0 vs coil address 1 (some bridges use 1-based addressing)

Scaling factors

# Device returns raw count: HR 5 = 2750 (represents 27.50 degrees Celsius)
# Bridge scale factor: 0.01
# Expected MQTT: {"temperature": 27.5}
# Verify: set simulator HR 5 = 10000, expect MQTT value = 100.0

Edge Cases to Test Before Production

Beyond normal operation, test these failure modes before deploying:

Modbus connection loss: Stop the simulator while the bridge is running. The bridge should log an error and attempt reconnection. Restart the simulator and verify the bridge automatically reconnects and resumes publishing.

MQTT broker disconnect: Stop the local broker. The bridge should queue unsent messages (if configured for QoS 1+) and reconnect when the broker comes back. Verify no data loss during the disconnect window.

Modbus exception responses: Configure the bridge to read a register address that doesn't exist in the simulator (e.g., address 9999 when the simulator only has 100 registers). The bridge should receive exception code 0x02 (Illegal Data Address) and handle it gracefully — log the error, skip publishing, and continue polling other registers. It should NOT crash or stop all polling.

All-zeros register read: Set all simulator registers to 0. Verify the bridge publishes the zero values and doesn't filter them out as "empty" data. Some poorly-written bridges skip zero values, which causes missing data in the MQTT stream.

Max register read: Configure the bridge to read 125 holding registers in one FC03 request (the Modbus spec maximum). Verify the bridge handles the multi-register response correctly and maps each register to the correct MQTT topic.

Notes on Common Bridge Software

Node-RED (node-red-contrib-modbus + MQTT out node): The Modbus Flex Getter node supports both TCP and RTU. Configure "FC" as 3 (read holding registers), "start" as the 0-based register address. MQTT out node publishes to the configured topic. Test with ModbusSimulator on localhost before connecting to real hardware.

Ewon Flexy / Kepware: Industrial gateways with Modbus drivers built in. Configure a Modbus TCP channel pointing to the simulator's IP and port. Tag addresses in Kepware use 1-based addressing (40001 = holding register 0). Verify the Kepware tag browser shows correct live values from the simulator before adding the MQTT plugin.

mqtt2modbus / modbus2mqtt (open source): Python-based bridge scripts. Configuration via YAML files. Test with a local Mosquitto broker first. The simulator lets you reproduce specific register states reproducibly for unit testing the bridge configuration file.

Azure IoT Edge Modbus module / AWS IoT Greengrass Modbus adapter: Cloud-hosted gateways that poll Modbus and forward to the cloud. Use ModbusSimulator during the development/staging phase so you don't accumulate cloud costs while testing gateway configuration. Switch to real hardware only after the configuration is validated.

Download ModbusSimulator to Test Your Bridge →

Frequently Asked Questions

What is a Modbus to MQTT bridge?

A Modbus-to-MQTT bridge is a gateway (software or hardware) that polls registers from a Modbus slave device and publishes their values as MQTT messages to a broker. Used in IIoT deployments to connect legacy Modbus equipment (PLCs, meters, drives) to modern cloud platforms, Node-RED dashboards, or SCADA systems that communicate via MQTT.

How do I test a Modbus MQTT bridge without real hardware?

Run ModbusSimulator as the Modbus source and a local Mosquitto broker as the MQTT destination. Configure the bridge to connect to the simulator's IP:502 (or COM port for RTU). Subscribe to MQTT topics with mosquitto_sub. Set known values in the simulator and verify they appear correctly in the MQTT output. This validates the entire pipeline without physical hardware.

What MQTT topic format is standard for Modbus data?

There's no single mandatory standard. Common conventions: device/unit_id/register_type/address (e.g., device/1/hr/0), or domain-specific hierarchies like plant/zone/device/measurement. Sparkplug B (an IIoT standard) uses structured JSON payloads with spBv1.0/group/NDATA/device. Define your topic hierarchy before configuring the bridge to avoid rebuilding topic structures later.

How do I handle 32-bit float Modbus registers in MQTT payloads?

32-bit floats use two consecutive 16-bit holding registers. The bridge must combine both registers respecting byte order (ABCD big-endian or CDAB little-endian or BADC/DCBA variants). Verify by setting a known float value in the simulator (e.g., 23.75 = 0x41BE0000) and confirming the MQTT payload value matches. Wrong byte order produces garbage values.

What causes data loss in a Modbus MQTT bridge?

Common causes: Modbus polling timeout too short (missed reads), MQTT broker disconnects without reconnect/queue logic, QoS 0 messages lost in transit on cellular/WAN connections, bridge buffer overflow when broker is unreachable, and Modbus exception responses being silently discarded. Use QoS 1 for critical data and test broker disconnect/reconnect behavior explicitly before production.

Can I test write-back (MQTT to Modbus) with a simulator?

Yes. Some bridges support write-back: subscribing to an MQTT topic and writing the received value to a Modbus holding register. Publish a test value to the write-back topic with mosquitto_pub and verify ModbusSimulator shows the register updating. This validates both directions of the bridge pipeline without touching real hardware.

Test Your Modbus-MQTT Bridge Without Real Hardware

ModbusSimulator acts as a fully configurable Modbus TCP or RTU slave. Set exact register values, test data type handling, and validate your bridge configuration before connecting to production equipment. Free trial available.

Download Free Trial → Learn More