mqtt-bridge

ROS 2 ↔ MQTT Bridge (IoT/Cloud Integration)

arm64amd64
ROS 2MQTTIoTCloud
Overview
Primary hardware
Generic Edge (arm64/amd64)
What it does

Bridges ROS 2 topics to MQTT (with TLS) for external dashboards, IoT brokers, or cloud services.

Why it saves time

Every robotics company eventually hacks this together for monitoring — we give it as a clean primitive.

Get access

Use StreamDeploy to manage OTA updates, versioned configs, and rollbacks across fleets.

Request access
Dockerfile
ARG BASE_IMAGE
FROM ${BASE_IMAGE:-"ubuntu:22.04"}

ENV DEBIAN_FRONTEND=noninteractive \
    LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 \
    ROS_DISTRO=humble

RUN apt-get update && apt-get install -y --no-install-recommends \
    python3 python3-pip curl ca-certificates gnupg lsb-release \
    && rm -rf /var/lib/apt/lists/*

# ROS 2
RUN mkdir -p /etc/apt/keyrings && \
    curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc \
    | gpg --dearmor -o /etc/apt/keyrings/ros-archive-keyring.gpg && \
    echo "deb [arch=arm64,amd64 signed-by=/etc/apt/keyrings/ros-archive-keyring.gpg] \
    http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" \
    > /etc/apt/sources.list.d/ros2.list && \
    apt-get update && apt-get install -y --no-install-recommends \
      ros-${ROS_DISTRO}-ros-base ros-${ROS_DISTRO}-std-msgs \
    && rm -rf /var/lib/apt/lists/*

RUN pip3 install --no-cache-dir paho-mqtt==1.6.1

WORKDIR /app
COPY entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh

ENV MQTT_HOST=mqtt \
    MQTT_PORT=1883 \
    MQTT_TLS=false \
    MQTT_USERNAME= \
    MQTT_PASSWORD= \
    ROS_SUBSCRIBE=/chatter \
    MQTT_PUB_TOPIC=robot/chatter \
    MQTT_SUB_TOPIC=robot/cmd \
    ROS_PUBLISH=/cmd \
    QOS=0

HEALTHCHECK --interval=30s --timeout=5s --start-period=15s \
  CMD bash -lc 'ros2 topic list | grep -q "${ROS_SUBSCRIBE}" || exit 0'

ENTRYPOINT ["/app/entrypoint.sh"]
entrypoint.sh
#!/usr/bin/env bash
set -euo pipefail
source /opt/ros/${ROS_DISTRO}/setup.bash

python3 - << 'PY'
import os, ssl, json, threading
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
import paho.mqtt.client as mqtt

host=os.getenv("MQTT_HOST","mqtt"); port=int(os.getenv("MQTT_PORT","1883"))
tls=os.getenv("MQTT_TLS","false")=="true"
user=os.getenv("MQTT_USERNAME") or None
pwd=os.getenv("MQTT_PASSWORD") or None
sub_topic=os.getenv("MQTT_SUB_TOPIC","robot/cmd")
pub_topic=os.getenv("MQTT_PUB_TOPIC","robot/chatter")
ros_sub=os.getenv("ROS_SUBSCRIBE","/chatter")
ros_pub=os.getenv("ROS_PUBLISH","/cmd")
qos=int(os.getenv("QOS","0"))

class Bridge(Node):
    def __init__(self, client):
        super().__init__('ros2_mqtt_bridge')
        self.client = client
        self.pub = self.create_publisher(String, ros_pub, 10)
        self.sub = self.create_subscription(String, ros_sub, self.on_ros_msg, 10)
    def on_ros_msg(self, msg):
        try:
            self.client.publish(pub_topic, msg.data, qos=qos)
        except Exception as e:
            self.get_logger().error(f"MQTT publish failed: {e}")

def on_connect(client, userdata, flags, rc):
    client.subscribe(sub_topic, qos=qos)
def on_message(client, userdata, msg):
    node: Bridge = userdata['node']
    m = String(); m.data = msg.payload.decode('utf-8', errors='ignore')
    node.pub.publish(m)

client = mqtt.Client(userdata={})
if user: client.username_pw_set(user, pwd)
if tls:
    client.tls_set(cert_reqs=ssl.CERT_NONE); client.tls_insecure_set(True)
client.on_connect = on_connect
client.on_message = on_message

rclpy.init()
node = Bridge(client)
client._userdata = {'node': node}

def mqtt_loop():
    client.connect(host, port, 60); client.loop_forever()
threading.Thread(target=mqtt_loop, daemon=True).start()

rclpy.spin(node)
PY