MQTT-based communication gateway for physical robots. Receives telemetry, dispatches commands, and tracks robot/module activity. Runs as its own container alongside a Mosquitto MQTT broker.
Firmware developers: See MQTT_PROTOCOL.md for the exact topic hierarchy and JSON payload schemas the gateway expects.
Robot (physical device)
├── Module A (sensor) ──┐
├── Module B (actuator) ─┤── MQTT ──→ Mosquitto Broker ──→ Robot Gateway ──→ PostgreSQL
└── Module C (camera) ──┘ ↑
Frontend/Middleware
(HTTP API with service key)
Each robot has multiple modules. Each module subscribes as a separate MQTT entity under robot/{robot_id}/module/{module_id}/....
| Topic | Direction | QoS | Purpose |
|---|---|---|---|
robot/{robot_id}/module/{module_id}/telemetry |
Robot → Gateway | 0 | Sensor data |
robot/{robot_id}/module/{module_id}/status |
Robot → Gateway | 1 | Module online/offline/error |
robot/{robot_id}/module/{module_id}/command |
Gateway → Robot | 1 | Commands to module |
robot/{robot_id}/module/{module_id}/command/ack |
Robot → Gateway | 1 | Command acknowledgement |
robot/{robot_id}/module/{module_id}/media |
Reserved | - | Future: image/audio binary payloads |
All endpoints require x-service-api-key header (shared ORCHESTRATOR_API_KEY).
POST /robots— Register a robotGET /robots— List all robots with modulesGET /robots/{robot_id}— Robot detailPUT /robots/{robot_id}— Update robotDELETE /robots/{robot_id}— Deregister (cascades)POST /robots/{robot_id}/modules— Register moduleGET /robots/{robot_id}/modules— List modulesPUT /robots/{robot_id}/modules/{module_id}— Update moduleDELETE /robots/{robot_id}/modules/{module_id}— Deregister module
GET /robots/{robot_id}/telemetry— Query telemetry (all modules)GET /robots/{robot_id}/modules/{module_id}/telemetry— Query by moduleGET /robots/{robot_id}/modules/{module_id}/telemetry/latest— Latest reading
Query params: since, until, payload_type, limit (max 1000), offset
POST /robots/{robot_id}/modules/{module_id}/commands— Send commandGET /robots/{robot_id}/commands— List commandsGET /commands/{command_id}— Command status
POST /api/capture/streams— Create or reuse a relay session for one moduleGET /api/capture/streams/{session_id}/status— Read relay state and viewer pathsGET /api/capture/streams/{session_id}/camera.mjpg— Viewer MJPEG streamWS /api/capture/streams/{session_id}/audio.pcm— Viewer PCM audio streamWS /api/capture/relay/connect— Device upstream relay socket using a short-lived Bearer token
GET /health— MQTT connection + DB reachability
Uses the same PostgreSQL instance as the orchestrator. Own migration table (robot_gateway_migrations).
| Table | Purpose |
|---|---|
robots |
Robot registry + aggregate last-seen tracking |
robot_modules |
Modules per robot (composite PK: robot_id + module_id) |
robot_telemetry |
Time-series telemetry with JSONB payloads |
robot_commands |
Command lifecycle tracking (pending → sent → acknowledged) |
- Robots do not have a first-class status field. The gateway tracks only robot metadata plus aggregate
last_seen_atbased on module traffic. - Module
statusin API responses is derived, not copied directly from MQTT. - A module is
offlinewhen it has not sent telemetry or a module status message for more than 60 seconds. - A fresh module is
onlineby default, even if it never published an explicitonlinestatus message. - A fresh explicit module
errorstatus is surfaced aserror. - A fresh explicit module
offlinestatus is surfaced asofflineuntil newer telemetry/status activity arrives.
Robots publish JSON to the telemetry topic:
{
"measured_at": "2026-04-17T14:30:00Z",
"payload_type": "temperature",
"data": {
"temperature_c": 22.5,
"humidity_pct": 45
}
}payload_type is a free-form discriminator used for filtering queries. data is stored as JSONB.
- Frontend sends
POST /robots/{robot_id}/modules/{module_id}/commands - Gateway creates DB record (status:
pending) - Gateway publishes to MQTT topic
robot/{robot_id}/module/{module_id}/command - Status updates to
sent - Robot processes and publishes ACK to
robot/{robot_id}/module/{module_id}/command/ack - Gateway receives ACK, updates status to
acknowledged
If a robot or module sends data before being registered via the HTTP API:
- Telemetry: Dropped with a warning log (FK constraint prevents storage)
- Status updates: Silently ignored (UPDATE matches 0 rows), warning logged
- Command ACKs: No-op (commands can only target registered modules)
Uses shared backend/.env. Gateway-specific variables:
# MQTT broker (required — broker rejects anonymous connections)
MQTT_BROKER_HOST=mosquitto
MQTT_BROKER_PORT=1883
MQTT_USERNAME=gateway
MQTT_PASSWORD=change-me-mqtt
# HTTP port
ROBOT_GATEWAY_PORT=8001
# Module presence
# ROBOT_MODULE_OFFLINE_AFTER_SECONDS=60
# Capture relay
CAPTURE_RELAY_PUBLIC_BASE_URL=https://brain.example.com
CAPTURE_RELAY_TOKEN_SECRET=change-me-relay-secret
# CAPTURE_RELAY_TOKEN_TTL_SECONDS=300
# CAPTURE_RELAY_SESSION_TTL_SECONDS=300
# CAPTURE_RELAY_IDLE_TIMEOUT_SECONDS=30
# CAPTURE_RELAY_HEARTBEAT_TIMEOUT_SECONDS=20
# CAPTURE_RELAY_AUDIO_BUFFER_CHUNKS=32
# Migrations (default: true)
# ROBOT_GATEWAY_AUTO_MIGRATE=true
# ROBOT_GATEWAY_AUTO_MIGRATE_FAIL_FAST=trueReuses: POSTGRES_*, ORCHESTRATOR_API_KEY, TZ
# With Docker Compose (starts mosquitto + gateway + dependencies)
docker compose up mosquitto robot-gateway
# Locally
pip install -r requirements.txt
uvicorn app:api --host 0.0.0.0 --port 8001 --reloadThe middleware is pre-configured to bypass session auth for /api/robot-gateway/mobile/ paths with Bearer tokens. When mobile routes are needed:
- Add
google-authtorequirements.txt - Implement
get_current_userinauth.py(followbackend/orchestrator/auth.pypattern) - Add dual-decorated routes (
/mobile/robots, etc.)
The gateway is designed to grow its own intelligence. When LLM processing is needed:
- Add
ollama/httpxto requirements - Reuse
LLM_BASE_URL,LLM_API_KEY,LLM_CHAT_MODEL_*env vars frombackend/.env - The
extra_hosts: host.docker.internal:host-gatewayDocker mapping provides local Ollama access
robot_modules.module_typeincludescameraandmicrophone- MQTT topic
robot/{id}/module/{module_id}/mediais reserved for binary media payloads (cameras/mics are just modules) - Transport mechanism TBD (MQTT binary, HTTP upload, pre-signed URLs)