Skip to main content
Version: v0.1.0

ROS integration

createRosbridgeAdapter connects to a rosbridge v2 WebSocket server and translates sensor_msgs/PointCloud2 messages into PointFlow chunks.

Prerequisites

Your ROS stack needs rosbridge_suite running. The default port is 9090.

# ROS 2
ros2 launch rosbridge_server rosbridge_websocket_launch.xml

Basic usage

import { createRosbridgeAdapter } from "pointflow";

const stop = createRosbridgeAdapter(
"ws://robot:9090",
"/velodyne_points",
{
fields: {
intensity: "intensity",
ring: "ring",
},
onChunk: (chunk) => api.current?.pushChunk(chunk),
onError: (e) => console.error("rosbridge error:", e),
},
);

// Later — unsubscribes and closes the socket
stop();

The fields map translates ROS field names (from the PointCloud2.fields array) to PointFlow attribute keys. x, y, z are always extracted automatically and don't need to be mapped.

Using it in a component

import { useEffect, useRef } from "react";
import { StreamedPointCloud, type StreamedPointCloudRef } from "pointflow";
import { createRosbridgeAdapter } from "pointflow";

export function RosScene() {
const api = useRef<StreamedPointCloudRef>(null);

useEffect(() => {
const stop = createRosbridgeAdapter(
"ws://localhost:9090",
"/scan",
{
fields: { intensity: "intensity" },
onChunk: (chunk) => api.current?.pushChunk(chunk),
},
);
return stop;
}, []);

return (
<StreamedPointCloud
maxPoints={500_000}
colorBy="intensity"
workerMode={true}
onReady={(ref) => { api.current = ref; }}
/>
);
}

Field mapping

A sensor_msgs/PointCloud2 message has a fields array that describes the layout of each point in the binary data. PointFlow extracts x, y, z automatically. You map other fields by their ROS name:

fields: {
"intensity": "intensity", // ROS field "intensity" → PointFlow attribute "intensity"
"ring": "ring",
"time": "timestamp", // rename if you want
"classification": "label",
}

Any ROS fields not in the map are ignored.

Connection lifecycle

The adapter opens the WebSocket, sends a rosbridge subscription message, and starts calling onChunk for each incoming message. When you call stop(), it sends an unsubscribe message and closes the socket.

If the connection drops unexpectedly, onError fires. You're responsible for reconnection logic if you need it:

function connect() {
const stop = createRosbridgeAdapter(ws, topic, {
onChunk: ...,
onError: () => {
stop();
setTimeout(connect, 2000); // retry after 2 seconds
},
});
}
connect();

Performance notes

sensor_msgs/PointCloud2 can carry large payloads. Velodyne VLP-16 at 10 Hz sends ~30,000 points per scan. HDL-64 sends ~130,000. For these rates, enable worker mode and consider worker-side culling if you're only interested in the current field of view:

<StreamedPointCloud
maxPoints={200_000}
workerMode={true}
workerCulling={true}
/>