Overview
The YAML configuration file defines how the LiveKit Portal for ROS bridges your ROS graph to and from a LiveKit room. It sets which topics and services to expose, including image topics that stream as video. LiveKit Portal for ROS loads the file when the node starts. Specify the file path with the node's config_path ROS parameter. Changes to the file are applied when the node restarts.
The configuration file is optional. Without one, the LiveKit Portal for ROS forwards all topics bidirectionally, which is convenient for development but not recommended for production: declare only the routes your app needs.
The file declares routes. There are two kinds of routes:
- Topic routes bridge ROS topics to LiveKit DataTracks. Image topics use LiveKit video tracks instead. The
directioncontrols the flow:outpublishes a local topic into the room,insubscribes to a remote topic and republishes it on the local graph, andbidirectionaldoes both. - Service routes expose a local ROS service that forwards each call to a specific LiveKit participant. Service routes are always
out.
Declare only the topics and services you need, and only in the direction you need them: every route adds traffic to the LiveKit connection, so a narrow configuration keeps bandwidth and latency down. For outbound topics, max_rate_hz throttles how often samples are forwarded, and encoding selects how each message is serialized on the DataTrack.
Example configuration
This example configures three topic routes and one service route:
- Send and receive all topics in the
/test/namespace. - Send out a rate-limited
/imu/data_rawtopic as JSON Schema. - Receive
/remote/cmd_velcommands. - Expose a service for
robot-ato navigate to a pose.
ros_portal:version: "0.0.1"topics:- topic: "/test/.*"direction: "bidirectional"- topic: "/imu/data_raw"direction: "out"max_rate_hz: 10encoding: "jsonschema"- topic: "/remote/cmd_vel"direction: "in"services:- service: "/navigate_to_pose"direction: "out"participant: "robot-a"msg_type: "nav2_msgs/srv/NavigateToPose"
Credentials
ROS Portal supports literal tokens, an endpoint token server, and a development token server. Select one of the following options:
| Token source | Environment variables | Use case | Token issuer |
|---|---|---|---|
| Literal token | LIVEKIT_URL + LIVEKIT_TOKEN<br><br>LIVEKIT_API_KEY + LIVEKIT_API_SECRET (optional - LiveKit Cloud) | Local LiveKit development, or direct control over token grants and lifetime | User, via lk token create or another token-generation service |
| Endpoint token server | LIVEKIT_TOKEN_ENDPOINT | Production, server authenticates requests and generates tokens | User-developed backend |
| Development token server | LIVEKIT_TOKEN_SERVER_ID | Quick prototypes and testing with LiveKit Cloud only; never use in production | LiveKit Cloud |
The following rules apply to these environment variables:
- ROS Portal fails to start if more than one of
LIVEKIT_TOKEN,LIVEKIT_TOKEN_ENDPOINT, andLIVEKIT_TOKEN_SERVER_IDis set. LIVEKIT_API_KEYandLIVEKIT_API_SECRETare used by LiveKit CLI to generate literal tokens against a LiveKit Cloud project. ROS Portal does not read either variable, only the resultingLIVEKIT_TOKEN.
Token grants
A token that you or your endpoint generates must have the following grants:
| Grant | lk token create flag | Description |
|---|---|---|
| Room join | --join | Lets the node connect to the room. |
| Room name | --room <name> | Sets the room to join. |
| Identity | --identity <identity> | Sets the participant identity of the node, for example ros-portal-robot. |
canUpdateOwnMetadata | --allow-update-metadata | Lets the node set the lk.robot participant attribute to "true". |
The development token server adds these grants for you.
Literal token
Set LIVEKIT_URL and a participant JWT in LIVEKIT_TOKEN. To generate a token for a LiveKit Cloud project with the LiveKit CLI, set LIVEKIT_API_KEY and LIVEKIT_API_SECRET first. The node does not read these two variables.
export LIVEKIT_URL=wss://<project-subdomain>.livekit.cloudexport LIVEKIT_API_KEY=<api-key>export LIVEKIT_API_SECRET=<api-secret>export LIVEKIT_TOKEN="$(lk token create \--join \--room robo_room \--identity ros-portal \--allow-update-metadata \--valid-for 24h \--token-only \--yes)"
Endpoint token server
For production, set LIVEKIT_TOKEN_ENDPOINT to a backend token endpoint. The endpoint returns the LiveKit server URL and a new participant JWT.
export LIVEKIT_TOKEN_ENDPOINT=https://example.com/livekit/token
Development token server
For development with LiveKit Cloud, enable a development token server in your project settings. Then set its ID:
export LIVEKIT_TOKEN_SERVER_ID=token-server-xxxxxx
Do not use the development token server in production. Any app can request a token from it with any permissions and no restrictions.
Top-level fields
Every field below is nested under a single top-level ros_portal key, as shown in the example above.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
version | string | yes | Must match ROS Portal's expected config schema version. | |
ros_threads | integer | 0 | ROS executor thread count. The default 0 uses the number of available CPU cores. Ignored in composition, where the container owns the executor. | |
enable_all_ros_topic_stats | boolean | false | Enables ROS 2 topic statistics for every outbound topic subscription. When true, it overrides each topic's value. | |
services | list | [] | Service route declarations. | |
topics | list | [] | Topic route declarations. | |
video_sources | list | [] | Capture sources published as LiveKit video tracks. See Video sources. |
JSON schema
To validate a configuration file in your editor, add the optional $schema field at the top level of the file, next to ros_portal. An IDE YAML plugin uses the schema for validation and autocomplete. The node ignores this field.
$schema: https://raw.githubusercontent.com/livekit/ros-portal/main/src/ros_portal_config/schema/ros_portal_config.schema.jsonros_portal:version: "0.0.1"
Topics
Each entry in the topics list declares a topic route with the following fields:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
topic | string | yes | ROS topic pattern. Must be non-empty. Treated as an ECMAScript regex for the DataTrack path. Matched as a literal name for max_rate_hz, latched, encoding, and inbound video tracks. | |
direction | string | yes | Valid values are: in, out, or bidirectional. | |
preserve_id | boolean | false | Inbound topics only. Prefix the republished ROS topic with the publishing participant's identity. Applies to data tracks and video tracks. | |
max_rate_hz | number | Outbound topics only. Cap (in Hz) on the rate samples are forwarded to LiveKit. Samples that arrive within one period of the last forwarded one are dropped (like topic_tools throttle messages). Literal topic names only. | ||
latched | boolean | false | Treat the topic as latched. Literal topic names only. | |
enable_ros_topic_stats | boolean | false | Outbound topics only. Enables ROS 2 topic statistics for subscriptions that match this topic pattern. | |
encoding | string | ros2msg | Selects how data is encoded on the DataTrack. Outbound topics only. Literal topic names only. Valid values are: ros2msg, ros2idl, or jsonschema. |
Topic patterns are ECMAScript regular expressions matched against the full topic name. Only forwarding the streams you actually need (and only in the required direction) keeps unnecessary traffic off the LiveKit connection.
Topic statistics
Set enable_ros_topic_stats: true on an outbound or bidirectional topic to publish ROS 2 topic statistics for its subscription. The node publishes them on <topic>/statistics, for example /imu/statistics.
ros_portal:version: "0.0.1"topics:- topic: "/imu"direction: "out"enable_ros_topic_stats: true
To enable statistics for every outbound subscription, set enable_all_ros_topic_stats: true at the top level. This setting overrides the value on each topic.
Topic statistics have the following limitations:
- Inbound-only topics have ROS publishers, not subscriptions, so these settings do not apply to them.
- The node never enables statistics on a topic whose name ends in
/statistics. This prevents recursive statistics topics when you use a broad pattern such as.*. - Topic statistics require ROS 2 Jazzy or later. Humble does not support topic statistics for generic serialized subscriptions.
Video topics
The LiveKit Portal for ROS forwards image topics as LiveKit video tracks, not as DataTracks. The track name is the same as the topic name.
Outbound video
An outbound topic of type sensor_msgs/Image or sensor_msgs/CompressedImage becomes a LiveKit video track:
sensor_msgs/Imagesupports thergba8,rgb8,bgr8,bgra8, andmono8encodings.sensor_msgs/CompressedImagesupports JPEG only. Theformatfield must containjpegorjpg(case-insensitive). The node decodes each frame on the CPU before LiveKit encodes it as video.
The size of the first frame sets the track resolution. The track is not recreated if the resolution changes later. The node attaches the ROS header.stamp and header.frame_id to each video frame as LiveKit frame metadata.
ros_portal:version: "0.0.1"topics:# Sends the LiveKit video track /camera/image_raw/compressed.- topic: "/camera/image_raw/compressed"direction: "out"
Inbound video
The node republishes a remote LiveKit video track as a sensor_msgs/CompressedImage topic with JPEG data. It forwards a video track only when the track name exactly matches the topic of an in or bidirectional entry. Video tracks never match by regex. For example, the entry /camera/.* forwards matching DataTracks but no video tracks.
On the receiving host, add an entry with the track name:
ros_portal:version: "0.0.1"topics:# Receives the edge camera as JPEG.- topic: "/camera/image_raw"direction: "in"
LiveKit video track: /camera/image_raw (from participant "edge-robot")ROS topic: /camera/image_rawROS type: sensor_msgs/msg/CompressedImage (format "jpeg")
With preserve_id: true, the ROS topic is /edge_robot/camera/image_raw. The node does not add or remove a /compressed suffix. Configure consumers for sensor_msgs/CompressedImage, even when the topic name has no /compressed suffix.
The receiving node copies header.stamp and header.frame_id from the frame metadata into the message header. If a frame has no metadata, for example because a browser sent it, the node uses fallback values. The stamp comes from the receiver's ROS clock when the frame arrives. The frame_id is the track name without the leading /. The inbound.video_stamp_fallbacks diagnostic counts these frames.
The receiving node handles each inbound video topic as follows:
- LiveKit delivers decoded frames only. The receiving node encodes each frame again as JPEG at quality 90. This uses CPU on the receiving host and adds JPEG loss to the video codec loss.
- If JPEG encoding is slower than the frame rate, the node drops the oldest buffered frame. Frames do not queue without limit.
- While the ROS topic has no subscribers, the node stops encoding frames. Within 0.5 seconds, it also pauses the LiveKit track, so the server stops sending it. A
rosbag2recorder counts as a subscriber. - When a subscriber appears, the node resumes the track within 0.5 seconds. The first image arrives about one network round trip later, after the sender produces a key frame.
- The publisher uses reliable QoS with depth 10. If the topic matches the
best_effort_qos_topicsROS parameter, the publisher uses the sensor data QoS profile (best effort, depth 5).
The node unsubscribes from all remote audio tracks and from all video tracks that match no inbound topic. The LiveKit SDK decodes every subscribed track, so this saves bandwidth and CPU.
Services
Each entry in the services list declares a service route with the following fields:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
service | string | yes | ROS service name. Must be non-empty. | |
direction | string | yes | Valid value: out | |
participant | string | yes | LiveKit participant identity. Must be non-empty. | |
msg_type | string | yes | ROS service type, such as std_srvs/srv/SetBool. Must be non-empty. |
Setting direction: "out" creates a local ROS service server that forwards calls to the configured LiveKit participant using msg_type. Services support only out: a service route always exposes a local ROS server that proxies calls to a remote participant, so unlike topics, there's no inbound or bidirectional direction.
Video sources
Video sources require v0.3.0 or later of LiveKit Portal for ROS.
Each entry in video_sources publishes one LiveKit video track that doesn't depend on the ROS graph. The source can be a GStreamer pipeline, a built-in test pattern, or a built-in wall clock.
The ROS Portal creates, publishes, and starts each source after it connects to the room. A failure affects only that entry. The node and the other sources keep running, and the source's diagnostic status reports the error.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
track_name | string | yes | Non-empty LiveKit video track name. | |
source | map | yes | Source configuration. Its type is gstreamer, pattern, or clock. | |
simulcast | boolean | false | Enables simulcast for pattern and clock sources. GStreamer sources ignore this field. | |
publish_options | map | Optional max_bitrate_bps and max_framerate limits. Both values must be positive integers. |
The publish_options limits set what the track advertises to LiveKit. They don't configure a GStreamer encoder. To control the encoder, set its properties in the pipeline or use rate_control.
GStreamer source
A GStreamer source publishes pre-encoded video. The pipeline must end in appsink name=lk_appsink or leave one encoded video source pad unlinked. The encoder in the pipeline sets the codec, so the track always publishes a single layer without simulcast.
| Field | Required | Description |
|---|---|---|
pipeline | yes | GStreamer launch description. |
codec | h264, h265, vp8, vp9, or av1. Inferred from the negotiated caps when omitted. | |
resolution | Positive width and height. Inferred from the negotiated caps when omitted, and checked against the stream when set. | |
rate_control | Connects WebRTC bitrate targets to an encoder property. |
If the node can't determine the codec or resolution, the source fails to start.
The optional rate_control block has three required fields: element names the encoder element, property names a writable integer bitrate property, and unit is bps or kbps to match that property. Without rate_control, the encoder keeps the fixed bitrate from the pipeline. The node still forwards keyframe requests to the pipeline.
ros_portal:version: "0.0.1"video_sources:- track_name: "demo_camera"source:type: "gstreamer"# videotestsrc pattern=smpte is the classic SMPTE 100% color bars with# the television-snow block in the bottom right — an instantly# recognizable reference for checking colors, scaling and tearing.# clockoverlay stamps wall-clock date + time on every frame so uplink# latency and frozen/stale video are obvious at a glance.pipeline: >-videotestsrc is-live=true do-timestamp=true pattern=smpte! video/x-raw,width=640,height=480,framerate=30/1! clockoverlay time-format="%Y-%m-%d %H:%M:%S" halignment=centervalignment=top shaded-background=true font-desc="Sans Bold 20"! videoconvert ! video/x-raw,format=I420! vp8enc name=lk_encoder deadline=1 cpu-used=8 keyframe-max-dist=30lag-in-frames=0 target-bitrate=2500000! video/x-vp8! appsink name=lk_appsink sync=false max-buffers=2 drop=truecodec: "vp8"resolution:width: 640height: 480rate_control:element: "lk_encoder"property: "target-bitrate"unit: "bps"publish_options:max_bitrate_bps: 2500000max_framerate: 30
To read a camera with v4l2src in Docker, pass the device into the container, for example --device /dev/video0.
GStreamer plugins
The Docker images and the Debian package include the gstreamer1.0-plugins-base, -good, -bad, and -ugly packages, and gstreamer1.0-libav. The Docker images also include gstreamer1.0-x, which provides the clockoverlay, textoverlay, and timeoverlay elements. The Debian package doesn't depend on gstreamer1.0-x, so it doesn't install X11 libraries on a headless robot.
If a pipeline uses an element from a missing plugin set, the source fails at startup:
[ERROR] [ros_portal]: Video source 'front_camera' failed to start: invalidrequest: GStreamer pipeline error: failed to create pipeline: no element"clockoverlay"
To use the overlay elements with a Debian install, install the package:
sudo apt install gstreamer1.0-x
Test Pattern and Clock Sources
Use a pattern or clock source to stub out camera streams during development, debugging, or simulations.
Use a pattern source to replace camera hardware during debugging or demonstrations. Reuse the track_name of the source you're replacing, so subscribers don't need any configuration changes.
ros_portal:version: "0.0.1"video_sources:- track_name: "front_camera"source:type: "pattern" # or "clock"
Additional resources
The following resource provides more information about configuration.
LiveKit Portal for ROS configuration guide
The complete configuration reference in the LiveKit Portal for ROS repository, covering every field, token sources, and full example configs.