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

# Reference

> Settings, services, recording, cameras, and policy mode for the ROS 2 bridge.

The bridge runs as a ROS node called `zrt_teleop_bridge`. The leader runs on the
operator machine, under `/leader`. The follower runs on the robot machine, under
`/follower`.

## Settings

Set these in your params file. One file can serve both machines when the joint
ranges come from `calibration_profile`, because each machine reads its own
calibration. With `joint_min` and `joint_max`, each machine needs its own values
for its own arm.

| Setting | Default | Description |
| - | - | - |
| `joint_names` | the six SO-101 joints | Your joint names. Must match on both machines, in the same order. |
| `joint_min`, `joint_max` | none | How far each joint on this machine's arm can move. |
| `calibration_profile` | none | Set to `lerobot_so101_rad` to read the joint ranges from a LeRobot calibration instead. |
| `robot_id` | `ros2` | A name for this machine. With `calibration_profile`, it's required: the LeRobot calibration id. |
| `calibration_file` | none | The path to a LeRobot calibration file, instead of looking it up by `robot_id`. |
| `leader_states_topic` | `/leader/joint_states` | The topic your leader driver publishes. |
| `follower_states_topic` | `/follower/joint_states` | The topic your robot driver publishes. |
| `follower_commands_topic` | `/follower/forward_controller/commands` | Where the bridge sends commands to your robot. |
| `control_hz` | `200` | How many times per second the bridge runs. Match your controller's `update_rate`. |
| `max_norm_step` | `0.00375` | How far a joint can move per step, as a fraction of its range. The default makes a full sweep in about 1.3 seconds. Raise it if the robot lags behind the leader. If you change `control_hz`, change this too. |
| `max_misalignment` | `0.05` | Block `take_control` until the two arms are this close, as a fraction of each joint's range. `0.0` turns it off. |
| `watchdog_timeout_s` | `0.5` | If commands stop for this many seconds, the robot holds its position. |

The room id and token come from the `ZERORUNTIME_MEETING_ID` and
`ZERORUNTIME_AUTH_TOKEN` environment variables.

## Services

All services are `std_srvs/srv/Trigger`. The few that need input read it from
parameters, so set those first with `ros2 param set`. The full name is
`/<namespace>/zrt_teleop_bridge/<service>`. For example:

```bash theme={null}
ros2 service call /leader/zrt_teleop_bridge/take_control std_srvs/srv/Trigger
```

| Service | Run on | Description |
| - | - | - |
| `take_control` | operator | Take control. Refused while the robot is e-stopped, or while the arms are further apart than `max_misalignment`. |
| `release_control` | operator | Stop sending. The robot holds its position. Never refused. |
| `estop` | either | Emergency stop. The robot stays stopped until it's cleared. |
| `clear_estop` | robot | Clear an emergency stop. Then call `take_control` again. |
| `leave` | either | Leave the room. The bridge keeps running, and the robot holds its position. Before the robot leaves, call `release_control` on the operator. |
| `join` | either | Rejoin after `leave`. An emergency stop stays on across `leave` and `join`. |
| `start_recording`, `stop_recording` | robot | Start or stop recording. |
| `start_episode`, `end_episode` | either | Start or end an episode by hand. On the operator, this asks the robot through the room, so it works across networks. It only takes effect while the robot is recording, and the operator's reply only confirms the request was sent: watch the robot's `events` to see it happen. |
| `end_episode_success`, `end_episode_fail` | either | End the episode, and mark it a success or a failure. |
| `set_active_operator` | either | Hand control to one operator. Set `active_operator_id` to their id (their `peer_id` topic) first. |
| `clear_active_operator` | either | Go back to first come, first served. |
| `perform_rpc` | either | Call a method on another machine in the room. Set `rpc_peer` (its id; on the operator, empty means the robot), `rpc_method`, `rpc_payload` (JSON, default `null`) and `rpc_timeout_s` (default `2.0`) first. The reply holds the result. Refused while the robot is being controlled. |

To answer calls on the robot, list the method names in its params file as
`rpc_methods`. Each call is published on `rpc/requests`, and the reply is the
`rpc_reply_<method>` parameter (JSON, default `{"ok": true}`).

## Recording

Recording runs on the robot machine and goes to the cloud (billed).
Start it with `start_recording`, and wait until the log shows
`cloud recording: ready`. While it's on, each `take_control` starts an episode
and each `release_control` ends it. If the cloud isn't ready in time, it
records on the robot machine only; a local copy is kept there either way.

| Setting | Default | Description |
| - | - | - |
| `recording_dir` | `./sessions` | Where the local copy is saved (and the whole recording, if the cloud isn't available), relative to the folder you launched from. The `start_recording` reply shows the full path. |
| `episode_task` | none | A task description saved with episodes you start by hand, read on the machine you call `start_episode` on. To change it between episodes: `ros2 param set /<namespace>/zrt_teleop_bridge episode_task "pick up the orange cube"`. |
| `cloud_recording` | `true` | Record to the cloud (billed). Set `false` to record only on this machine. See [Cloud recording](/robotics/guides/cloud-recording). |
| `cloud_recording_timeout_s` | `90` | With cloud recording on, episodes are refused until it's ready (the `start_episode` reply says so). After this many seconds, the bridge gives up on the cloud and records locally only. |

<Note>
  `leave` stops the recording. After `join`, call `start_recording` again.
</Note>

## Cameras

Connect cameras to the robot machine. The operator sees them as video.

By default the follower bridge streams every USB camera, as `cam0`, `cam1`, and
so on. To choose, set `ZERORUNTIME_CAMERAS` before you start it: camera names,
comma-separated, or `none` for no cameras. To list the names, run this on the
robot machine:

```bash theme={null}
python -m zeroruntime.teleops.devices
```

It ends with a ready line to copy, for example:

```bash theme={null}
ZERORUNTIME_CAMERAS=platform-3610000.usb-usb-0:2.2:1.0-video-index0,platform-3610000.usb-usb-0:2.4:1.0-video-index0
```

Or list the cameras in the robot machine's params file. This wins over
`ZERORUNTIME_CAMERAS`:

```yaml theme={null}
/**:
  ros__parameters:
    camera_handles: ["/dev/v4l/by-path/platform-3610000.usb-usb-0:2.2:1.0-video-index0", "/dev/video2"]
    camera_labels: [front, wrist]
```

Handles are device paths: `/dev/v4l/by-path/...`, `/dev/v4l/by-id/...`, or
`/dev/videoN` (which can change after a reboot). A bare number like `'0'` is
not a camera: it streams a test pattern. `camera_labels` is optional. You can
also set `camera_width`, `camera_height`, and `camera_fps` (defaults 640, 480,
and 30).

## Policy mode

A policy, such as a trained model, can control the robot instead of a leader
arm. Start the leader bridge in policy mode:

```bash theme={null}
zrt-teleops-ros2-leader --ros-args --params-file params.yaml -r __ns:=/leader -p leader_source:=policy
```

Then call `take_control` as usual, and publish
`trajectory_msgs/JointTrajectory` messages to
`/leader/zrt_teleop_bridge/action_chunk`.

Each message is a short list of joint positions, evenly spaced in time, in the
same units as the leader's topic. The robot plays them in order, and a new
message replaces the current one. Read the robot's position from
`/leader/zrt_teleop_bridge/remote/follower_states`. `max_misalignment` is
ignored in policy mode, and `release_control` and `estop` work as usual.

The [example project](https://github.com/ZeroRuntimeAI/zrt-teleops-examples/tree/main/ros2#policy-mode)
shows how to start policy mode, and its `fake_chunk` shortcut sends one test
chunk.

## Monitoring

| Topic | Description |
| - | - |
| `events` | Events as they happen, such as safety changes and control granted. |
| `stats` | Connection stats, once a second. |
| `safe_state` (robot only) | The robot's state: `disconnected`, `holding`, `active`, or `estopped`. |
| `active_operator` | The operator set with `set_active_operator`. Empty means first come, first served. |
| `peer_id` (operator only) | This operator's id, for `set_active_operator` and `rpc_peer`. |
| `observation_id` (operator only) | The id of each observation from the robot. A policy can put it in a chunk's `header.frame_id`. |
| `applied` (robot only) | Each command the robot applied. Off unless you set `publish_applied: true`. |
| `rpc/requests` (robot only) | Each incoming `rpc_methods` call. |
| `/diagnostics` (robot only) | One status per camera: a warning on missed reads, an error when it reconnects. Published as `/diagnostics`, not under the bridge. |

```bash theme={null}
ros2 topic echo --field data /follower/zrt_teleop_bridge/events
```

## Limitations

* The bridge can't turn off the motors. When it stops, the robot holds its
  position.
* Only position control is supported.

## What's next

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/robotics/integrations/ros2/quickstart">
    Connect your own ROS 2 robot.
  </Card>

  <Card title="ROS 2 adapters" icon="code" href="/robotics/api-reference/python/adapters/ros2">
    Build your own ROS 2 node with the SDK.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.