Legacy shop-floor devices — PLCs, drives, power meters — speak Modbus TCP or RTU. They predate cloud connectivity and have no native MQTT support. An edge gateway running Mosquitto reads these devices over their existing protocol and republishes to any MQTT subscriber, including cloud IoT platforms. The devices see a normal Modbus poll. The cloud sees a structured message stream.
Hardware
A Raspberry Pi 4 (4GB) handles up to ~10,000 messages/second at this workload.
For industrial deployment:
- Enclosure: DIN rail mount (Multicomp Pro, Waveshare, or equivalent). Control cabinets don't accommodate desktop hardware.
- Power: 24VDC-to-5V DIN rail PSU. USB power adapters are not rated for continuous industrial duty.
- Storage: Industrial-grade microSD (Transcend or SanDisk Industrial) or USB SSD. Consumer microSD fails under continuous write workloads within months.
- Network: Assign a static IP at the switch level (DHCP reservation or manual). DHCP lease expiry in OT environments is a silent failure mode.
Install Mosquitto
sudo apt update && sudo apt install -y mosquitto mosquitto-clients
sudo systemctl enable mosquitto
sudo systemctl start mosquitto
Minimal broker config at /etc/mosquitto/conf.d/broker.conf:
# Internal OT network — authenticated, plaintext
listener 1883
allow_anonymous false
password_file /etc/mosquitto/passwd
# Cloud-facing — TLS required
listener 8883
cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/broker.crt
keyfile /etc/mosquitto/certs/broker.key
require_certificate false
Create an OT client credential:
sudo mosquitto_passwd -c /etc/mosquitto/passwd ot_client
sudo systemctl restart mosquitto
Do not expose port 1883 outside the OT network segment. It is authenticated but not encrypted — suitable only on a trusted L2 segment behind a firewall.
Modbus-to-MQTT bridge
Read Modbus registers with pymodbus, publish to MQTT with paho-mqtt:
# modbus_bridge.py
from pymodbus.client import ModbusTcpClient
import paho.mqtt.client as mqtt
import json, time
DEVICE_IP = '192.168.10.5'
BROKER_HOST = 'localhost'
TOPIC_BASE = 'factory/cell-01/drive-01'
modbus = ModbusTcpClient(DEVICE_IP, port=502)
mq = mqtt.Client(client_id='bridge-drive-01')
mq.username_pw_set('ot_client', 'YOUR_PASSWORD')
mq.connect(BROKER_HOST, 1883)
modbus.connect()
mq.loop_start()
while True:
result = modbus.read_holding_registers(address=0, count=4, slave=1)
if not result.isError():
payload = {
'speed_rpm': result.registers[0],
'current_a': result.registers[1] / 10.0,
'temp_c': result.registers[2] / 10.0,
'status_word': result.registers[3],
'ts': time.time()
}
mq.publish(TOPIC_BASE, json.dumps(payload), qos=1)
time.sleep(1)
Use QoS 1 (at-least-once). QoS 0 drops messages silently on a broker restart or client disconnect — unacceptable for production telemetry. Run this as a systemd service with Restart=always.
Cloud bridge
Mosquitto's built-in bridge forwards topics to a remote broker. For AWS IoT Core:
# /etc/mosquitto/conf.d/cloud_bridge.conf
connection cloud-aws
address YOUR_ENDPOINT.iot.us-east-1.amazonaws.com:8883
bridge_cafile /etc/mosquitto/certs/AmazonRootCA1.pem
bridge_certfile /etc/mosquitto/certs/device-cert.pem
bridge_keyfile /etc/mosquitto/certs/device-private.pem
topic factory/# out 1
cleansession false
start_type automatic
cleansession false tells the remote broker to retain the session and queue messages during disconnections. Set max_queued_messages 10000 in the main config to cap RAM usage when the cloud link is down for extended periods.
Operational monitoring
Mosquitto exposes internal metrics on the $SYS topic tree:
mosquitto_sub -v -t '$SYS/#'
Key metrics to watch:
| Topic | Meaning |
|---|---|
$SYS/broker/clients/connected |
Active subscriber count |
$SYS/broker/messages/received |
Inbound message rate |
$SYS/broker/publish/messages/dropped |
Messages dropped due to queue overflow |
$SYS/broker/connection/cloud-aws/state |
Bridge connection state (0/1) |
A non-zero dropped message count means the queue limit was hit — either the cloud link was down too long or the queue ceiling is too low. Address the root cause before raising the ceiling.