> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cyberwave.com/llms.txt
> Use this file to discover all available pages before exploring further.

# ROS 2 Driver

> Docker images, configuration, and ROS 2 topic reference for the Go2 navigation driver.

## Docker Images

The full robot stack is published as four images on Docker Hub, each available in three tags (`jazzy`, `humble`, `jetson-humble`):

| Image                                | Description                                                   |
| ------------------------------------ | ------------------------------------------------------------- |
| `cyberwaveos/go2-ros2-driver`        | Go2 driver, streaming bridges, edge bridge                    |
| `cyberwaveos/ros2-nav2`              | Nav2 navigation stack (robot-agnostic)                        |
| `cyberwaveos/ros2-slam`              | SLAM — RTAB-Map, slam\_toolbox, Cartographer (robot-agnostic) |
| `cyberwaveos/ros2-elevation-mapping` | GPU-accelerated elevation mapping (robot-agnostic)            |

The `jetson-humble` driver image ships with optimized defaults for constrained hardware.

```bash theme={null}
docker pull cyberwaveos/go2-ros2-driver:jazzy
docker pull cyberwaveos/ros2-nav2:jazzy
docker pull cyberwaveos/ros2-slam:jazzy
docker pull cyberwaveos/ros2-elevation-mapping:jazzy
```

## Quick Start

When connected to the Cyberwave platform, the **edge-core** service automatically pulls and starts all required containers based on the asset metadata configured for your twin.

For local development with Docker Compose, see the driver [README](https://github.com/cyberwave-os/cyberwave/tree/main/cyberwave-edge-runtime/runtime-services/drivers/ros2/robots/unitree/go2).

## ROS 2 Topics

The driver publishes standard ROS 2 topics that any ROS 2 node can consume independently:

| Topic               | Type                      | Description                      |
| ------------------- | ------------------------- | -------------------------------- |
| `/odom`             | `nav_msgs/Odometry`       | LiDAR-based odometry             |
| `/point_cloud2`     | `sensor_msgs/PointCloud2` | 3D point cloud from LiDAR        |
| `/scan`             | `sensor_msgs/LaserScan`   | 2D laser scan (from point cloud) |
| `/camera/image_raw` | `sensor_msgs/Image`       | Front camera frames              |
| `/joint_states`     | `sensor_msgs/JointState`  | Joint positions and velocities   |
| `/imu/data`         | `sensor_msgs/Imu`         | IMU measurements                 |
| `/map`              | `nav_msgs/OccupancyGrid`  | Occupancy map (from SLAM)        |

**All robot-specific topics are namespaced as `twin_<your-twin-uuid>` by default**.

## Key Environment Variables

| Variable                                     | Default                        | Description                                                          |
| -------------------------------------------- | ------------------------------ | -------------------------------------------------------------------- |
| `CYBERWAVE_API_KEY`                          | —                              | Cyberwave API token                                                  |
| `CYBERWAVE_TWIN_UUID`                        | —                              | Digital twin UUID                                                    |
| `CYBERWAVE_MQTT_HOST`                        | `mqtt.cyberwave.com`           | MQTT broker                                                          |
| `GO2_AES_128_KEY`                            | —                              | Per-device AES-128 key for firmware ≥ 1.1.15 (see below)             |
| `ROBOT_IP`                                   | (auto-discovered)              | Go2 IP — validated on startup; see below                             |
| `ROBOT_IP_FALLBACKS`                         | `192.168.12.1,192.168.123.161` | Comma-separated IPs to probe when `ROBOT_IP` is unset or unreachable |
| `CONFIG_PROFILE`                             | (auto)                         | Config profile: `jazzy`, `humble`, or `jetson`                       |
| `CYBERWAVE_PERIODIC_MAP_UPLOAD_INTERVAL_SEC` | `120`                          | Periodic map cloud sync interval (0 to disable)                      |
| `GO2_USE_CPP_VOXEL_DECODER`                  | `true`                         | Use C++ LiDAR decoder (faster)                                       |
| `GO2_OBSTACLE_AVOIDANCE`                     | `true`                         | Enable onboard obstacle avoidance (see below)                        |
| `CYBERWAVE_ENABLE_RECORDING`                 | *(see below)*                  | Global recording toggle for video streams                            |
| `CYBERWAVE_ENABLE_RECORDING_PREVIEW`         | `false`                        | Record preview streams (occupancy, costmaps)                         |

### Obstacle Avoidance

When `GO2_OBSTACLE_AVOIDANCE=true` (the default), velocity commands are routed
through the Go2's firmware obstacle avoidance pipeline instead of the standard
Sport API. The firmware fuses ultrasonic and LiDAR sensors to detect obstacles
and will override velocity commands to prevent collisions.

On startup the driver sends `SwitchSet` and `UseRemoteCommandFromApi` to the
robot so the firmware accepts velocity commands from the ROS 2 stack rather than
only the physical remote controller.

<Warning>
  Onboard obstacle avoidance operates independently from Nav2's path planner.
  The firmware may intervene — slowing down or stopping the robot — even when
  Nav2 considers the path clear. This can cause Nav2 goals to take longer or be
  aborted if the firmware repeatedly blocks planned motion. If you need full
  control over obstacle handling through Nav2's costmaps and local planner, set
  `GO2_OBSTACLE_AVOIDANCE=false`.
</Warning>

To disable:

```bash theme={null}
GO2_OBSTACLE_AVOIDANCE=false
```

### Obstacle Avoidance & Teleoperation

There are two layers of obstacle avoidance on the Go2:

1. **Onboard** — the robot's built-in ultrasonic + LiDAR avoidance at the
   firmware level.
2. **Driver-level** — path planning avoidance through costmaps constructed
   in real time by the navigation stack.

When `GO2_OBSTACLE_AVOIDANCE=true` (configurable in the twin metadata;
enabled by default), the driver activates **both layers** for autonomous
navigation.

<Info>
  **Recommended teleoperation during mapping:**

  * **Cyberwave keyboard controller** — obstacle avoidance is active
    (onboard + driver-level). This is the recommended way to drive the robot
    while building a map of your environment.
  * **Unitree physical remote** — always retains onboard obstacle avoidance
    regardless of driver configuration.

  Both options are safe for teleoperation during mapping.
</Info>

<Warning>
  Bluetooth gamepad obstacle avoidance now depends on the active controller
  policy:

  * **Local Bluetooth controller (`local`)**: obstacle avoidance stays on the
    robot-side local controller path.
  * **Keyboard teleop** and **autonomous navigation**: the driver switches to the
    autonomy/remote-command path, and the gamepad does not keep independent
    obstacle avoidance.
</Warning>

### AES-128 Key (firmware ≥ 1.1.15)

Go2 firmware **1.1.15+** uses a per-device AES-128 key (`data2=3` protocol) to
encrypt the WebRTC LAN handshake. Without this key, both the driver node and
native video node will fail to connect.

Fetch the key using the `unitree_webrtc_connect` CLI (requires the Unitree
account used in the Unitree Go/Explore mobile app):

```bash theme={null}
pip install unitree_webrtc_connect
unitree-fetch-aes-key --email your@email.com --password 'your-password' --device-type Go2
```

Set the 32-hex-char key in your env file or Edge Core `shared_env`:

```bash theme={null}
GO2_AES_128_KEY=abcdef0123456789abcdef0123456789
```

<Info>
  Older firmware (below 1.1.15) uses a static key built into the driver and does
  not require this variable. If `GO2_AES_128_KEY` is unset and the robot returns
  `data2=3`, the driver will log a clear error with instructions.
</Info>

### Robot IP discovery

The driver validates that the Go2 is reachable **before** starting any ROS 2
node, so you get a fast failure instead of a silent WebRTC hang.

**Resolution order:**

1. `ROBOT_IP` (from env or Edge Core metadata) — probed via TCP on port 9991.
2. If unreachable (or unset), each address in `ROBOT_IP_FALLBACKS` is tried.
3. First address that responds is used for the entire stack.
4. If nothing responds, the container exits with an error and pushes a
   `robot_unreachable` alert to Cyberwave (if SDK credentials are available).

The default fallback list covers the two most common Go2 network configurations:

| Address           | Scenario                |
| ----------------- | ----------------------- |
| `192.168.12.1`    | Wi-Fi AP / USB-Ethernet |
| `192.168.123.161` | Ethernet direct-connect |

**Custom networks:** If your Go2 is on a different subnet (e.g. behind a
router), override the fallback list:

```bash theme={null}
# .env file or Edge Core shared_env
ROBOT_IP_FALLBACKS=10.0.0.50,10.0.0.51
```

You can set both `ROBOT_IP` (preferred address) and `ROBOT_IP_FALLBACKS`
(safety net). If the preferred IP is unreachable, the driver automatically
falls through to the fallback list.

<Info>
  The probe uses a TCP socket connect on port 9991 (Go2 WebRTC signaling) with a
  2-second timeout per address. No WebRTC handshake is attempted. Simulation
  launches skip the check entirely.
</Info>

## Config Profiles

| Profile  | When to use                                                                            |
| -------- | -------------------------------------------------------------------------------------- |
| `jazzy`  | Default for ROS 2 Jazzy on desktop/server hardware                                     |
| `humble` | ROS 2 Humble ecosystem compatibility                                                   |
| `jetson` | NVIDIA Jetson: reduced costmap resolution, increased transform tolerances, lower rates |

Set `CONFIG_PROFILE=jetson` or use the `jetson-humble` Docker image which sets this automatically.

## Edge Core metadata

When using **Cyberwave Edge Core** for managed deployment, the driver stack is
configured through twin/asset metadata. Edge Core reads this metadata, pulls
images, and injects environment variables automatically.

### Multi-container configuration

The Go2 ROS 2 stack runs as multiple cooperating containers. Define the stack in
`metadata.drivers` using the `services` array:

```json theme={null}
{
  "drivers": {
    "linux-aarch64-jetson": {
      "services": [
        {
          "image": "cyberwaveos/go2-ros2-driver:jetson-humble",
          "name": "driver",
          "command": [
            "ros2",
            "launch",
            "cyberwave_go2_driver",
            "robot_driver.launch.py"
          ]
        },
        {
          "image": "cyberwaveos/go2-ros2-driver:jetson-humble",
          "name": "bridges",
          "command": [
            "ros2",
            "launch",
            "cyberwave_go2_driver",
            "robot_bridges.launch.py"
          ]
        },
        { "image": "cyberwaveos/ros2-nav2:jetson-humble", "name": "nav2" },
        { "image": "cyberwaveos/ros2-slam:jetson-humble", "name": "slam" }
      ],
      "shared_env": {
        "CONFIG_PROFILE": "jetson",
        "ROS_DOMAIN_ID": "0"
      },
      "shared_params": ["--network", "host", "-v", "/data:/data"]
    },
    "default": {
      "docker_image": "cyberwaveos/go2-ros2-driver:jazzy",
      "prefer_gpu": true
    }
  }
}
```

To add a service (e.g. elevation mapping), append to the `services` array. To
set the robot IP or fallback list, add them to `shared_env`:

```json theme={null}
"shared_env": {
  "ROBOT_IP_FALLBACKS": "10.0.0.50,10.0.0.51",
  "CONFIG_PROFILE": "jetson"
}
```

See [Drivers overview](/edge/drivers/overview) for the full metadata schema and
[Writing compatible drivers](/edge/drivers/writing-compatible-drivers) for the
`edge_configs` per-device pattern.

## Stream Recording

The Go2 driver streams video to the Cyberwave media service via WebRTC. Each
stream can request server-side recording (producing MP4 artifacts for later
replay).

| Variable                             | Default | Applies to                                                   |
| ------------------------------------ | ------- | ------------------------------------------------------------ |
| `CYBERWAVE_ENABLE_RECORDING`         | —       | All streams (global override)                                |
| `CYBERWAVE_ENABLE_RECORDING_PREVIEW` | `false` | Preview streams only (occupancy grid, local/global costmaps) |

**Default behavior** (no env vars set):

* `camera_bridge` — records by default (`true`).
* `occupancy_grid_bridge`, `local_costmap_bridge`, `global_costmap_bridge` — do **not** record by default.

**Resolution order** per bridge:

1. `CYBERWAVE_ENABLE_RECORDING` — if set, overrides everything.
2. Per-bridge env var (e.g. `CYBERWAVE_ENABLE_RECORDING_PREVIEW`) — overrides the built-in default.
3. Built-in default.

```bash theme={null}
# Record everything including previews
CYBERWAVE_ENABLE_RECORDING_PREVIEW=true

# Disable all recording (e.g. for development)
CYBERWAVE_ENABLE_RECORDING=false
```

<Info>
  Recording is handled by the media service SFU, not on the edge device.
  Enabling recording adds negligible load to the robot's hardware.
</Info>

## Jetson Performance Tuning

Running the full Go2 stack on a Jetson Orin Nano (or similar constrained hardware) requires tuning runtime rates and
encoder settings. Add these to your `.env` file or Edge Core `shared_env`:

```bash theme={null}
# ── Runtime rates (overrides vs. defaults) ───────────────────────────
GO2_STATE_PUBLISH_HZ=15          # default 10
GO2_POINTCLOUD_MAX_POINTS=4000   # default 25000
GO2_POINTCLOUD_STREAM_FPS=1      # default 5
GO2_SCAN_ANGLE_INCREMENT=0.01745 # default 0.00872
GO2_TRAV_COSTMAP_HZ=5            # default 10
GO2_TRAV_COSTMAP_GLOBAL_HZ=0.5   # default 1
GO2_LOCAL_COSTMAP_FPS=10         # default 5
GO2_GLOBAL_COSTMAP_FPS=10        # default 2

# ── Obstacle avoidance ──────────────────────────────────────────────
# Disable if Nav2 handles obstacle avoidance to save CPU.
GO2_OBSTACLE_AVOIDANCE=false     # default true

# ── H.264 encoding ──────────────────────────────────────────────────
# Jetson Orin Nano has NO hardware H.264 encoder (no NVENC).
# Skip codec probing and constrain libx264 to a single thread so it
# doesn't starve SLAM/Nav2. At 424x240@1fps ultrafast this uses <5%
# of one core.
CYBERWAVE_H264_ENCODER=libx264   # default auto
CYBERWAVE_H264_PRESET=ultrafast  # default veryfast
CYBERWAVE_H264_BITRATE_KBPS=800  # default 2500
CYBERWAVE_H264_THREADS=1         # default 0 (unlimited)
```

<Info>
  These values are tuned for a Jetson Orin Nano running the full stack (driver +
  Nav2 + SLAM). If your board has more headroom (e.g. Orin NX) or less (e.g.
  other services running), you can adjust camera and pointcloud rates
  accordingly.
</Info>

## NVIDIA Container Toolkit (Jetson / GPU)

GPU-accelerated containers (simulation, video decode, elevation mapping) require the **NVIDIA Container Toolkit**. This is especially important on Jetson (L4T / JetPack) where the toolkit must be installed and the runtime registered with Docker.

```bash theme={null}
# Install
distribution=$(. /etc/os-release; echo $ID$VERSION_ID)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \
  sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
  sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
  sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit

# Register with Docker
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
```

On Jetson, set the NVIDIA runtime as the default so all containers get GPU access automatically:

```bash theme={null}
sudo tee /etc/docker/daemon.json <<'EOF'
{
    "default-runtime": "nvidia",
    "runtimes": {
        "nvidia": {
            "args": [],
            "path": "nvidia-container-runtime"
        }
    }
}
EOF
sudo systemctl restart docker
```

<Tip>
  If `nvidia-container-toolkit` is already installed but containers cannot
  access the GPU, re-run the `nvidia-ctk runtime configure` step, ensure
  `daemon.json` has the nvidia runtime, and restart Docker.
</Tip>
