A high-performance ROS2 bridge server that forwards ROS2 topic content over WebSocket, without requiring DDS on the client side.
pj_ros_bridge enables clients to subscribe to ROS2 topics and receive aggregated messages at 50 Hz without needing a full ROS2/DDS installation. This is particularly useful for visualization tools like PlotJuggler, remote monitoring applications, and lightweight clients that need access to ROS2 data.
- No DDS Required: Clients connect via WebSocket (single port) without needing ROS2/DDS installed
- High Performance: 50 Hz message aggregation with ZSTD compression
- Multi-Client Support: Multiple clients can connect simultaneously with shared subscriptions
- Session Management: Automatic cleanup of disconnected clients via heartbeat monitoring and WebSocket close events
- Runtime Schema Discovery: Automatic extraction of message schemas from installed ROS2 packages
- Zero-Copy Design: Efficient message handling using shared pointers and move semantics
- Large Message Stripping: Automatic stripping of large array fields (Image, PointCloud2, LaserScan, OccupancyGrid) to reduce bandwidth while preserving metadata
- Type-Safe Error Handling: Comprehensive error reporting using
tl::expected
For detailed architecture documentation, see docs/ARCHITECTURE.md.
rclcpp- ROS2 C++ client libraryament_index_cpp- Locate ROS2 package share directories
- ZSTD (libzstd):
sudo apt install libzstd-dev
- IXWebSocket (
v11.4.6): WebSocket server/client
- nlohmann/json - JSON library
- tl/expected - Type-safe error handling
# 1. Create workspace (if needed)
mkdir -p ~/ws_plotjuggler/src
cd ~/ws_plotjuggler/src
# 2. Clone the repository
git clone <repository_url> pj_ros_bridge
# 3. Install system dependencies
sudo apt install libzstd-dev
# 4. Source ROS2
source /opt/ros/humble/setup.bash
# 5. Build the package
cd ~/ws_plotjuggler
colcon build --packages-select pj_ros_bridge --cmake-args -DCMAKE_BUILD_TYPE=Release
# 6. Source the workspace
source install/setup.bashNote: IXWebSocket is automatically downloaded and built via CMake FetchContent during the first build.
# Run all unit tests (150 tests)
colcon test --packages-select pj_ros_bridge
# View test results
colcon test-result --verboseThe project uses pre-commit hooks for code formatting:
# Install pre-commit (if needed)
pip3 install pre-commit
# Install hooks
cd ~/ws_plotjuggler/src/pj_ros_bridge
pre-commit install
# Run manually on all files
pre-commit run -asource /opt/ros/humble/setup.bash
source ~/ws_plotjuggler/install/setup.bash
ros2 run pj_ros_bridge pj_ros_bridge_nodeDefault configuration:
- WebSocket port: 8080
- Publish rate: 50 Hz
- Session timeout: 10 seconds
ros2 run pj_ros_bridge pj_ros_bridge_node --ros-args \
-p port:=9090 \
-p publish_rate:=30.0 \
-p session_timeout:=15.0| Parameter | Type | Default | Description |
|---|---|---|---|
port |
int | 8080 | WebSocket server port |
publish_rate |
double | 50.0 | Aggregation publish rate in Hz |
session_timeout |
double | 10.0 | Client timeout duration in seconds |
strip_large_messages |
bool | true | Strip large arrays from Image, PointCloud2, LaserScan, OccupancyGrid messages |
# Terminal 1: Play rosbag
source /opt/ros/humble/setup.bash
ros2 bag play /path/to/sample.mcap --loop
# Terminal 2: Run server
source /opt/ros/humble/setup.bash
source ~/ws_plotjuggler/install/setup.bash
ros2 run pj_ros_bridge pj_ros_bridge_node
# Terminal 3: Run Python test client
cd ~/ws_plotjuggler/src/pj_ros_bridge
python3 tests/integration/test_client.py --subscribe /topic1 /topic2The package includes a Python test client for testing and demonstration.
Install dependencies:
pip install websocket-client zstandardUsage:
# Discover available topics
python3 tests/integration/test_client.py --command get_topics
# Subscribe to specific topics
python3 tests/integration/test_client.py --subscribe /imu /points
# Subscribe with custom server address
python3 tests/integration/test_client.py \
--server 192.168.1.100 \
--port 9090 \
--subscribe /topic1
# Run for specific duration with verbose output
python3 tests/integration/test_client.py --subscribe /topic1 --duration 60 --verboseFor the full API protocol documentation (commands, responses, binary wire format), see docs/API.md.
Another process is using the port. Either kill the conflicting process or use a custom port:
ros2 run pj_ros_bridge pj_ros_bridge_node --ros-args -p port:=9090- Verify server is running:
ps aux | grep pj_ros_bridge - Check topics are being published:
ros2 topic list - Verify heartbeat is being sent (required every 1 second)
- Check server logs:
ros2 run pj_ros_bridge pj_ros_bridge_node --ros-args --log-level debug
The message type's .msg file was not found. Ensure the ROS2 package containing the message type is installed and sourced:
ros2 interface show <package_name>/msg/<MessageType>The client stopped sending heartbeats. Ensure the client sends a heartbeat every 1 second. The default timeout is 10 seconds. Increase if needed:
ros2 run pj_ros_bridge pj_ros_bridge_node --ros-args -p session_timeout:=20.0pj_ros_bridge/
├── include/pj_ros_bridge/
│ ├── middleware/
│ │ ├── middleware_interface.hpp
│ │ └── websocket_middleware.hpp
│ ├── topic_discovery.hpp
│ ├── schema_extractor.hpp
│ ├── message_buffer.hpp
│ ├── message_serializer.hpp
│ ├── message_stripper.hpp
│ ├── session_manager.hpp
│ ├── generic_subscription_manager.hpp
│ ├── time_utils.hpp
│ └── bridge_server.hpp
├── src/
│ ├── middleware/
│ │ └── websocket_middleware.cpp
│ ├── topic_discovery.cpp
│ ├── schema_extractor.cpp
│ ├── message_buffer.cpp
│ ├── message_serializer.cpp
│ ├── message_stripper.cpp
│ ├── session_manager.cpp
│ ├── generic_subscription_manager.cpp
│ ├── bridge_server.cpp
│ └── main.cpp
├── tests/
│ ├── unit/ # 150 unit tests (gtest)
│ └── integration/
│ └── test_client.py
├── 3rdparty/ # Header-only dependencies
├── DATA/ # Test data (rosbag, reference schemas)
├── cmake/ # CMake modules
├── CMakeLists.txt
├── package.xml
├── .clang-tidy
└── .pre-commit-config.yaml
Copyright 2025 Davide Faconti
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.