# ROS 2 RMW drop-in transport

> rmw_clutchcall_cpp: a real ROS 2 middleware that replaces DDS/Zenoh so unmodified nodes talk over WAN-grade QUIC. One apt install, one env var, no broker.

`rmw_clutchcall_cpp` is a native **ROS 2 middleware (RMW) implementation**.
The [bridge](/modalities/robotics/details#drop-in-ros-2-transport) runs
*beside* DDS and mirrors selected topics. The RMW is different: it
**replaces** the middleware layer entirely. Set one environment variable.
Then **every** topic, service, and QoS event from an unmodified ROS 2 node
uses FirstByte's single-port QUIC/MoQT transport instead of DDS multicast.
You need no DDS, no Zenoh router, no broker, and no code change.

> **NOTE:**
> The RMW is the strongest form of the "drop-in ROS 2 transport" idea. It sits
> at the `rmw` seam, the same seam that `rmw_fastrtps` and `rmw_zenoh` plug
> into. Thus `rclcpp` / `rclpy` and every node above them stay untouched.
> Target distro: **Jazzy**.

## Why an RMW instead of DDS

Teams use this when DDS stops working at the network edge. This is the exact
teleop / fleet case:

  - **Crosses WAN + NAT** — DDS discovery is multicast. It does not cross the open internet or NAT.
    The RMW rendezvous occurs at the relay. A robot and a cloud operator
    connect over one QUIC session, whatever the network topology.
  - **No broker to run** — You operate no DDS discovery domain and no Zenoh router. The relay mesh
    is the fan-out. The tenant token that authorizes voice / streams also
    authorizes the fleet.
  - **No head-of-line blocking** — `BEST_EFFORT` sensor topics use QUIC datagrams. A lost frame never stalls
    the frames behind it. A reliable DDS stream under loss does stall.
  - **Reconnects + fans out** — One session reconnects automatically across drops. The relay fans each
    topic out to every subscriber across hosts. DDS cannot do many-viewer
    teleop.

## Install

The RMW ships as a signed Debian package for ROS 2 Jazzy on the FirstByte
artifact registry.

```bash
# Add the signed apt repo
curl -fsSL https://artifacts.clutchcall.dev/apt/clutchcall-archive-keyring.asc \
  | sudo gpg --dearmor -o /usr/share/keyrings/clutchcall-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/clutchcall-archive-keyring.gpg] \
https://artifacts.clutchcall.dev/apt noble main" \
  | sudo tee /etc/apt/sources.list.d/clutchcall.list

sudo apt update
sudo apt install ros-jazzy-rmw-clutchcall-cpp
```

> **NOTE:**
> The package bundles the QUIC/MoQT engine (`libclutchcall_moqt_ffi.so`) and
> resolves it with an `$ORIGIN` rpath. Thus **no `LD_LIBRARY_PATH` is needed**.
> A clean `apt install` is self-contained. There is no ROS build-farm
> dependency. The engine is prebuilt.

## Configure

Three environment variables select the RMW and point it at your relay:

```bash
export RMW_IMPLEMENTATION=rmw_clutchcall_cpp
export CLUTCHCALL_RELAY_URL=quic://relay.your-tenant.clutchcall.dev:443/moq
export CLUTCHCALL_AUTH_TOKEN=<tenant-token>   # authorizes the session
```

`CLUTCHCALL_RELAY_URL` is a single QUIC port. MoQT uses WebTransport on
`:443` beside H3. Thus you open one port, and each node has one connection.
The [tenant token](/concepts/authentication) is the same credential that
authorizes your other FirstByte modalities.

## Quickstart

Export the three variables. Then run **stock, unmodified** ROS 2 nodes on
hosts anywhere: different machines, different clouds, behind NAT:

```bash
# host A (the robot), anywhere
ros2 run demo_nodes_cpp talker

# host B (the operator), across the planet
ros2 run demo_nodes_cpp listener
# [INFO] [listener]: I heard: [Hello World: 1]
```

`ros2 topic list`, `ros2 node list`, `ros2 service call`, and
`ros2 topic info -v` all work. The RMW feeds a graph cache from liveliness
announcements over the relay.

## What maps to what

  1. **Topics → MoQT tracks**
Each ROS topic becomes a MoQT namespace + track
    (`ros2 \x1f <domain> \x1f <topic>`, track `<type>__<hash>`). One published
    message = one MoQT
    group. The raw FastRTPS **CDR** travels in the object with a small
    in-band header. The header carries the sequence number, source timestamp,
    and publisher GID (for `rmw_take_with_info` and message-lost detection).
  2. **Services → request/response tracks**
`create_service` / `create_client` map to a pair of unidirectional tracks
    (`…rq` / `…rr`). The RMW correlates request↔response in-band by
    `{client GID, seq}`. `add_two_ints` → `5` works end-to-end.
  3. **Discovery → liveliness tokens**
Each entity publishes a small liveliness track. One namespace
    subscription feeds a graph cache. This is the WAN-friendly analogue of
    DDS discovery, with no multicast.

## QoS → transport

The RMW honours ROS 2 QoS. It picks the matching MoQT delivery mode. The
network then behaves the way each policy promises:

| ROS 2 QoS | RMW behaviour |
|---|---|
| **`RELIABLE`** | Reliable MoQT subgroup streams (QUIC loss recovery). |
| **`BEST_EFFORT`** | QUIC/WebTransport **datagrams**: drop-old, **no head-of-line blocking**. Good for high-rate sensor topics. |
| **`BEST_EFFORT`, large msg** | The RMW **fragments** a message larger than the path datagram cap (point cloud, camera frame) across datagrams. The subscriber reassembles it (drop-whole-on-loss). |
| **`TRANSIENT_LOCAL`** | Late-joining subscribers **replay** the relay's retained window. Latched `/map`, static TF, and `/robot_description` arrive on connect. |
| **`KEEP_LAST` depth** | Honoured on the subscriber receive queue. |
| **Liveliness / matched** | `SUBSCRIPTION_MATCHED` / `PUBLICATION_MATCHED` / `LIVELINESS_CHANGED` events fire as peers appear and leave. This is the "did I lose the robot?" signal. |

> **NOTE:**
> All of the above is verified end-to-end on ROS 2 Jazzy against a single-port
> relay: reliable pub/sub, best-effort datagrams, large-message fragmentation,
> transient-local replay, and matched/liveliness events.

## RMW vs. the bridge

Both put ROS topics on the mesh. Pick by how much of the stack you want to
replace.

  - **RMW (this page)** — **Replaces** DDS/Zenoh. Every topic, service, and QoS event uses QUIC.
    There is no local middleware and no per-topic config. Set
    `RMW_IMPLEMENTATION=rmw_clutchcall_cpp`. Best for a full ROS 2 stack that
    goes WAN-native.
  - **Bridge** — Runs **beside** DDS/Zenoh and mirrors *selected* topics onto MoQT. Keeps
    local DDS for on-robot traffic. Bridges only the topics that leave the
    box. See
    [Details → Drop-in ROS 2 transport](/modalities/robotics/details#drop-in-ros-2-transport).

## Related

- [Robotics — Details](/modalities/robotics/details) — wire model, namespaces, QoS lanes.
- [SDK Methods](/modalities/robotics/sdk-methods) — the typed `Robotics` surface for bridging topics yourself.
- [Authentication](/concepts/authentication) — the tenant token.
