Skip to main content

ROS Tool

A generic ROS tool providing a React API for subscribing, publishing, and making service calls from the web. This is very similar to rosbridge + roslibjs, but based on Transitive's MQTTSync protocol which has several advantages over rosbridge:

  • deduplication,
  • incremental, field-based updates, rather than sending whole messages every time one field changes,
  • caching in the cloud,
  • React API.

Front-end API

On the web, the capability exposes a React API via a CapabilityContext:

  • subscribe(rosVersion, topic): subscribe to ROS topics on the robot, e.g., subscribe(1, '/odom'). This will cause the robot component of this capability to subscribe to that topic on the robot and publish the latest values on MQTTSync under the /device/ros/[rosVersion]/messages/[topic] namespace, and it will subscribe to that MQTTSync topic.

  • unsubscribe(rosVersion, topic): unsubscribe from a previously subscribed topic. Does nothing if not subscribed.

  • deviceData: the MQTTSync data for this device and capability including the latest values for the subscribed topics.

  • publish(rosVersion, topic, type, message): publish a message on a ROS topic, e.g.: publish(1, '/chatter', 'std_msgs/String', {data: 'hello from web'})

  • callService(rosVersion, service, type, request, callback): call a service with the given request payload. The callback will be called with two arguments:

    • err: an error message, if any,
    • response: the response payload from the service.


    callService(2, '/spawn', 'turtlesim/srv/Spawn',
    {x: 2, y: 8, theta: 1, name: 'logo'},
    (err, response) => {
    if (err) {
    console.warn('Service call failed', err);
    } else {
    console.log('New turtle spawned with name',;


import { CapabilityContext, CapabilityContextProvider } from '@transitive-sdk/utils-web'

const MyROSComponent = () => {
// import the API exposed by the ros-tool capability
const { ready, subscribe, deviceData } = useContext(CapabilityContext)

useEffect(() => {
if (ready) {
// subscribe to a ROS 1 topic on the robot
subscribe(1, '/amcl_pose');
}, [ready, subscribe])

// get the pose from the reactively updating device data
const pose = deviceData?.ros[1].messages?.amcl_pose?.pose.pose;
if (!pose) return <div>Connecting..</div>;

// Show pose x and y on page as text:
return <div>
x: {pose.position.x},
y: {pose.position.y}

const MyPage = () => <CapabilityContextProvider jwt={jwt}>
<MyROSComponent />

Where jwt is a valid JWT for this capability and the device you are trying to connect to, i.e., with a payload like this:

"id": "",
"device": "your-robot's-id",
"capability": "@transitive-robotics/ros-tool",
"userId": "user123", // a string that uniquely identifies a user in your context
"validity": 86400 // number of seconds this authentication should remain valid

If the JWT library you use doesn't automatically add an iat field, you'll need to make sure to add it yourself in the payload, setting its value to the current unix time in seconds (e.g., Math.floor( / 1000)).

Fleet API

In order to interact with and receive data from multiple robots simultaneously, e.g., for showing them on the same map, use the fleet component by using a JWT that has the device set to _fleet. The provided context then contains this function:

  • getAPI(deviceId): get the API for the named device. The resulting API is the same as the device-specific described API above.


    bot1api = context.getAPI('d_superbot1')
    bot1api.subscribe(1, '/amcl_pose')

    bot2api = context.getAPI('d_superbot2')
    bot2api.subscribe(1, '/amcl_pose')

Back-end API (node.js)

You can use the same API from your back-end, in node.js, in your cloud or other deployment.


import { importCapability } from '@transitive-sdk/utils';

const run = async () => {
const rosTool = await importCapability({

// Use ros-tool to subscribe to the ROS /odom topic on the device of the JWT.
// Here "subscribe" is a function exported by the ros-tool capability.
rosTool.subscribe(1, '/odom');

// print data as it changes in the local cache:
const pose = rosTool.deviceData.ros?.[1]?.messages?.odom?.pose?.pose;
() => console.log(JSON.stringify(pose, true, 2)),


Custom Message Types

This capability supports arbitrary custom ROS message types.


Make sure your custom message packages are in your ROS_PACKAGE_PATH. Also keep in mind that all capabilities run in a sandbox on the robot which hides the home directory of the user, for security. Hence, custom packages in your home directly will not be found, even if added to your ROS_PACKAGE_PATH. The easiest way, which also avoids the need to update your ROS_PACKAGE_PATH, is to add your custom packages directly to /opt/ros/YOUR_ROS1_DISTRO/share.


The capability will find and use any custom message definitions it finds in your AMENT_PREFIX_PATH. That path is rescanned every time the capability (re-)starts.

Free vs. Premium

The free version of this capability includes 100 MB of data transfer per month, throttles updates from the robot to the cloud/web to 1 Hz and limits the maximum message size to 10 KB. These limitations are lifted when you have a valid payment method on your account, allowing you to increase the rate to up to 10 Hz and increasing the maximum message size to 10 MB.

About Heavy Payloads

This capability is not ideal for heavy payloads such as video streams and high-resolution pointclouds. For those we recommend our webrtc-based capabilities, e.g., WebRTC Video and Foxglove WebRTC, which use UDP and video compression, among other essential features to handle these kinds of payloads.

Version 0.3.8, published 7/8/2024, 3:28:29 PM
Free Includes 100 MB of MQTT data, $0.2/GB after that.