Architecture

RosFit is split into two halves — a lightweight bridge that runs on each robot, and a set of cloud services that ingest telemetry, manage state, and expose everything through a REST API and real-time dashboard. MQTT is the transport layer that connects the two.

Overview

The system is designed around a clear separation between the robot side and the cloud side, connected by an MQTT broker.

Robot Side — Each robot runs a RosFit Bridge process (Python, built on rclpy and paho-mqtt). The bridge subscribes to local ROS 2 topics, serializes messages to JSON, and publishes them to the MQTT broker. It also subscribes to command topics on the broker and injects them back into the ROS 2 graph.

Cloud Side — An EMQX 5.x broker receives all device traffic. Behind the broker, a Message Handler service routes messages to the appropriate backend: telemetry goes to TimescaleDB, device shadow updates go to Redis, and firmware artefacts are stored in MinIO. A FastAPI backend exposes a unified REST + WebSocket API that the React dashboard (and your own integrations) consume.

System diagram

┌─────────────────────────────────────────────────────────────────────────────┐
│                              ROBOT  SIDE                                    │
│                                                                             │
│   ┌───────────┐    ┌───────────────┐    ┌──────────────┐                    │
│   │  ROS 2    │───▶│  RosFit       │───▶│  MQTT Client │──┐                │
│   │  Nodes    │◀───│  Bridge       │◀───│  (paho-mqtt) │  │                │
│   │           │    │  (rclpy)      │    │              │  │                │
│   └───────────┘    └───────────────┘    └──────────────┘  │                │
│                                                            │                │
└────────────────────────────────────────────────────────────┼────────────────┘

                          MQTT (TLS 8883 / WS 8084)          │

┌────────────────────────────────────────────────────────────┼────────────────┐
│                              CLOUD  SIDE                   │                │
│                                                            ▼                │
│                     ┌──────────────────────┐                                │
│                     │   EMQX 5.x Broker    │                                │
│                     │   (MQTT + WebSocket)  │                                │
│                     └──────────┬───────────┘                                │
│                                │                                            │
│              ┌─────────────────┼─────────────────┐                          │
│              ▼                 ▼                  ▼                          │
│   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐                     │
│   │   Message    │  │   Shadow     │  │   OTA        │                     │
│   │   Handler    │  │   Service    │  │   Service    │                     │
│   └──────┬───────┘  └──────┬───────┘  └──────┬───────┘                     │
│          │                 │                  │                              │
│          ▼                 ▼                  ▼                              │
│   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐                     │
│   │ TimescaleDB  │  │   Redis 7    │  │    MinIO     │                     │
│   │ (Postgres)   │  │   (Cache)    │  │  (Objects)   │                     │
│   └──────┬───────┘  └──────┬───────┘  └──────┬───────┘                     │
│          │                 │                  │                              │
│          └─────────────────┼──────────────────┘                             │
│                            ▼                                                │
│                     ┌──────────────┐                                        │
│                     │   FastAPI    │                                        │
│                     │   Backend    │                                        │
│                     └──────┬───────┘                                        │
│                            │                                                │
│                            ▼                                                │
│                     ┌──────────────┐                                        │
│                     │    React     │                                        │
│                     │  Dashboard   │                                        │
│                     └──────────────┘                                        │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Data flow

Telemetry upstream (device to dashboard)

Data flows from the robot to the dashboard through a well-defined pipeline:

  1. A ROS 2 node publishes a message (e.g. /odom, /battery_state).
  2. The RosFit Bridge serializes the message to JSON and publishes it to rosfit/{device_id}/telemetry/status.
  3. EMQX receives the message and forwards it to the Message Handler via a shared subscription.
  4. The Message Handler validates the envelope, inserts the payload into TimescaleDB, and updates the device's last-seen timestamp in Redis.
  5. The FastAPI backend serves the data via REST (GET /api/v1/telemetry/{device_id}) or pushes it to connected WebSocket clients.
  6. The React dashboard renders the telemetry in real-time charts and fleet overview panels.

Commands downstream (dashboard to device)

  1. An operator clicks "Send Command" in the dashboard (or calls POST /api/v1/commands/{device_id}).
  2. The FastAPI backend wraps the command in a message envelope with a unique correlation_id and publishes it to rosfit/{device_id}/cmd/request.
  3. EMQX delivers the message to the device's bridge.
  4. The bridge deserializes the command and publishes the corresponding ROS 2 message (e.g. a Twist on /cmd_vel).
  5. The bridge publishes an acknowledgement to rosfit/{device_id}/cmd/response with the same correlation_id.
  6. The Message Handler records the result, and the dashboard updates the command status.

Docker Compose stack

The entire platform runs as a set of Docker containers orchestrated by Docker Compose. A single docker compose up -d brings up every service.

ServiceImagePortsPurpose
emqxemqx/emqx:5.x1883, 8883, 8083, 8084, 18083MQTT broker with TLS and WebSocket support
timescaledbtimescale/timescaledb:latest-pg155432Time-series telemetry storage (PostgreSQL extension)
redisredis:7-alpine6379Device shadows, caching, pub/sub for real-time events
miniominio/minio:latest9000, 9001S3-compatible object storage for firmware artefacts
apirosfit/api:latest8000FastAPI backend (REST + WebSocket)
handlerrosfit/handler:latestMQTT message consumer and router
dashboardrosfit/dashboard:latest3000React + Vite frontend

All services share a Docker network and communicate over internal DNS. External access is through the published ports listed above.

Technology stack

LayerTechnologyVersionRole
MQTT BrokerEMQX5.xHigh-performance broker with built-in auth, ACLs, and WebSocket
APIFastAPI (Python)0.110+REST and WebSocket API, JWT auth, background tasks
DatabaseTimescaleDB (PostgreSQL)15Hypertable-backed time-series storage for telemetry
CacheRedis7.xDevice shadow state, session cache, real-time pub/sub
Object StorageMinIOlatestS3-compatible storage for OTA firmware and blob data
FrontendReact + Vite + Tailwind CSSReact 18, Vite 5Real-time fleet dashboard with charts and command panel
BridgePython (rclpy + paho-mqtt)ROS 2 Humble / IronBidirectional ROS 2 ↔ MQTT message bridge
AuthJWT + X.509Device and user authentication with role-based access