# Details

> Connect a robot fleet: stream telemetry up, send commands down. ROS 2 CDR, Zenoh-over-QUIC, MQTT bridge, per-QoS lanes.

<img src="/images/diagrams/robotics.png" alt="robotics data flow" />

Connect a robot fleet to the cloud: stream telemetry *up* from every robot,
and send commands *down* to any one robot. The connection uses WAN-grade
QUIC, works across NAT, uses one auth token, and needs no broker. Each
client speaks for one `(tenant, robot)` pair. Two namespaces are built in.
The two directions never collide, and a controller cannot accidentally
subscribe to the robot's own announce:

```
robot/<id>          telemetry topics   (robot → cloud)
robot/<id>/ctl      command topics     (cloud → robot)
```

Payloads are opaque bytes, typically ROS 2 **CDR**. The SDK prefixes the
message type name on the wire. A cross-language subscriber can then pick the
correct deserializer without an out-of-band schema exchange.

## What you can build

  - **Fleet telemetry** — Stream odometry, LIDAR, battery, and pose from hundreds of robots to a
    cloud dashboard or a recorder. Each robot uses its own `robot/<id>`
    namespace.
  - **Teleoperation** — Drive a robot from the cloud. `cmd_vel` and goal commands flow down
    `robot/<id>/ctl` on a low-latency lane. Telemetry flows back up the same
    session.
  - **Cross-language demux** — A TypeScript dashboard and a Python analytics worker subscribe to the same
    CDR track. The type-name prefix lets each one pick its own decoder.
  - **Drop-in ROS 2 transport** — Replace the local middleware with a FirstByte-backed transport. A
    ROS 2 node then reaches the fleet over WAN-grade QUIC with no code change.

## Three transports onto the mesh

A robot can reach the relay mesh in three different ways. All three use the
same namespaces and the same type-name-prefixed wire frame. Producers and
consumers interoperate, whatever way each one connected.

  
    This is the native path. A ROS 2 node serializes its messages to **CDR**
    (the DDS Common Data Representation). The raw CDR bytes travel on the
    track unchanged. The SDK prefixes the type name
    (`nav_msgs/msg/Odometry`). A subscriber anywhere can then give the
    payload directly to a CDR decoder.
  
  
    A robot that already uses **Zenoh** routes its key-expressions onto MoQT
    namespaces over the same QUIC connection. The bridge compiles Zenoh
    key-expressions to `robot/<id>/...` track names. Matching and fan-out
    then occur at the relay.
  
  
    Constrained or legacy devices that use only **MQTT 3.1.1** connect
    through the bridge. An MQTT topic maps to a `robot/<id>` track. Retained
    messages map onto the transient-local lane. QoS 0/1 map onto the
    best-effort / reliable lanes.
  

> **NOTE:**
> The on-the-wire frame is identical for each type of connection. The transport
> is a property of *how a peer reached the mesh*, not of the data. Thus a CDR
> publisher, a Zenoh node, and an MQTT sensor can all feed one subscriber.

## Wire model & track conventions

Each robot maps to a pair of MoQT namespaces. A **topic** becomes a **track
name** under the relevant namespace. One MoQT track carries one topic.

| Direction        | Namespace        | Example track                         |
| ---------------- | ---------------- | ------------------------------------- |
| Telemetry (up)   | `robot/<id>`     | `robot/spot-12` · `odom`              |
| Commands (down)  | `robot/<id>/ctl` | `robot/spot-12/ctl` · `cmd_vel`       |

Each published message opens its own MoQT group (one discrete ROS message per
group). The relay holds a small recent-group window, so a late subscriber
gets the latest frames. This window is the closest analog to a ROS 2 history
depth.

### Type-name-prefixed CDR frame

Every object on the wire is a self-describing envelope:

```
┌────────────────────┬──────────────────────┬─────────────────────┐
│ u16 BE type_len    │ type_name (UTF-8)    │ CDR payload         │
│ (2 bytes)          │ e.g. nav_msgs/msg/…  │ opaque bytes        │
└────────────────────┴──────────────────────┴─────────────────────┘
```

- The 2-byte big-endian length limits type names to 65 535 bytes. Real ROS 2
  names fit in approximately 64 bytes.
- The CDR payload is **opaque to the SDK**. You give the SDK the bytes that
  your DDS, Zenoh, or `serialize_message` already produced.
- The prefix makes the stream **cross-language**. A subscriber gets
  `(cdr, typeName)` and routes the payload to the matching decoder with no
  schema registry.

> **TIP:**
> The frame is byte-identical across the TypeScript, Python, Go, and native
> bridges. A frame that a Python robot publishes decodes unchanged in a browser
> dashboard.

## QoS → lanes

ROS 2 QoS hints map onto MoQT lanes at the relay. You pass a `qos` profile
when you create a publication. The relay routes and admits the track by the
resulting capability.

| QoS hint                            | Lane                       | Capability        |
| ----------------------------------- | -------------------------- | ----------------- |
| `reliability: "reliable"`           | subgroup stream (ordered)  | `ros.reliable`    |
| `reliability: "best_effort"`        | QUIC datagram (lossy)      | `ros.best_effort` |
| `durability: "transient_local"`     | adds late-join retain      | `ros.tl_*`        |
| `depth: N`                          | per-track group window     | —                 |

- **Reliable** uses subgroup streams. Delivery is guaranteed and in order per
  publisher. Use it for commands and for telemetry that you cannot drop (a
  map update, a goal).
- **Best-effort** uses QUIC datagrams. Latency is lowest, but delivery is
  lossy. Use it for high-rate sensor streams (pose, LIDAR), where the
  freshest sample wins.
- **transient_local** makes the relay retain the last group(s). A subscriber
  that joins late immediately receives the most recent value. This is the
  analog of a latched MQTT retained message.
- **depth** limits the per-track group window that the relay holds for late
  subscribers. This matches ROS 2 keep-last history.

Reliable publications also default to a higher send priority (lower priority
number) than best-effort. You can override the priority per message on
`write`.

## Drop-in ROS 2 transport

A robot does not have to know that the relay exists. Its nodes publish and
subscribe exactly as before, while their topics travel off-box on
FirstByte. There are two ways to do this. They differ in how much of the
local stack you replace:

- **[The RMW](/modalities/robotics/rmw)** (`rmw_clutchcall_cpp`) — a real
  ROS 2 middleware that **replaces** DDS/Zenoh. After one `apt install` and
  one env var, *every* topic, service, and QoS event uses QUIC, with no local
  DDS at all. This is the recommended path for a full ROS 2 stack that goes
  WAN-native.
- **The bridge** (below) — a small native process that runs **beside**
  DDS/Zenoh and mirrors *selected* topics onto MoQT. Keep local DDS for
  on-robot traffic. Bridge only the topics that leave the box.

### The bridge

The bridge presents itself as an ordinary middleware transport. Local nodes
publish and subscribe their topics exactly as before. The bridge mirrors
those topics into and out of MoQT:

  1. **Local nodes stay local**
Nodes talk to the bridge over the standard middleware interface: DDS or
    Zenoh on the robot's loopback. No application code changes.
  2. **Bridge mirrors topics**
For each configured topic, the bridge subscribes locally and republishes
    the raw CDR onto a `robot/<id>` telemetry track. The bridge also
    subscribes to a `robot/<id>/ctl` command track and republishes onto the
    local graph.
  3. **Mesh fan-out**
The relay fans each telemetry frame out to every cloud subscriber. It
    routes each command down to the addressed robot. Transport is WAN-grade
    QUIC, across NAT, with one auth token.

> **NOTE:**
> This page describes the bridge by **role**, not by file. From the robot's
> point of view, it is "the transport that carries my topics off-box". From the
> cloud's point of view, it is "the fleet ingress". The SDK in
> [SDK Methods](/modalities/robotics/sdk-methods) gives you the same pub/sub
> surface programmatically when you want to bridge selected topics yourself.

## How it works

You get one connection per robot, one token, and no broker to operate. A
single host still fans a high-rate fleet out at line rate.

- **One connection, many tracks.** Telemetry and commands for a robot share
  one QUIC session. You open and authorize a single connection. **MoQT**
  multiplexes every track over it. The relay mesh fans the tracks out to
  subscribers across hosts.
- **One token.** The tenant token that authorizes voice / streams / games
  authorizes the fleet. You need no separate broker credentials.
- **No broker to run.** The relay mesh is the fan-out. There is no MQTT
  broker or DDS discovery domain to operate in the cloud.
- **Line-rate fan-out.** High-rate fleets stay fast. The engine is
  **shard-per-core** with a **kernel-bypass** NIC fast path, **lock-free
  mcache / dcache rings** between shards, and **io_uring** for async I/O. A
  single host fans out without copies through the kernel network stack.

## Related

- [SDK Methods](/modalities/robotics/sdk-methods) — the typed `Robotics` surface.
- [Cookbook](/modalities/robotics/cookbook) — short task-oriented snippets.
- [Recipes](/modalities/robotics/recipes) — end-to-end worked examples.
- [Realtime Tracks](/concepts/realtime-tracks) — the MoQT primitive underneath.
