This manual page aims to introduce the reader to the process of creating a custom ROS handler for use with Chrono::ROS.
ROS is a flexible framework for writing robot software. It is a collection of tools, libraries, and conventions that aim to simplify the task of creating complex and robust robot behavior across a wide variety of robotic platforms. This manual page does not go into detail about ROS, so please refer to the ROS website for more detailed information. It is assumed that the reader is familiar with the basic concepts of ROS before continuing.
What is a Handler?
A handler is a class that is responsible for converting messages between ROS and Chrono. It is the interface between the ROS world and the Chrono world. A handler is essentially a higher level abstraction that encapsulates all ROS entities, such as publishers, subscribers, and services, and provides a simple interface to interact with them. Any logic which interacts with ROS should be encapsulated in a handler.
Chrono::ROS Handler Implementation Guide
This guide explains how to implement new handlers for the Chrono::ROS interface.
Architecture Overview
Chrono::ROS uses a two-process architecture to ensure separation and avoid symbol conflicts (particularly between VSG visualization and ROS 2 libraries as of Chrono 9.0).
- Main Process (Chrono Simulation): Runs the physics simulation. It extracts data from Chrono objects and serializes it into raw bytes. It contains NO ROS symbols.
- Subprocess (ROS Node): Runs the ROS node. It receives raw bytes via IPC (Inter-Process Communication), deserializes them, and publishes standard ROS messages. It contains NO Chrono physics symbols.
The "Handler" Concept
A "Handler" is the bridge between these two worlds. To add a new feature (e.g., publishing a new sensor type), you must implement a Handler that spans both processes.
A complete Handler consists of 4 files:
ChROS<Name>Handler.h: The class definition (Main Process).ChROS<Name>Handler.cpp: The data extraction and serialization logic (Main Process).ChROS<Name>Handler_ipc.h: The shared data structure definition (Both Processes).ChROS<Name>Handler_ros.cpp: The ROS publishing/subscribing logic (Subprocess).
The Shared IPC Header (*_ipc.h)
This is the most critical file. It defines the "contract" between the Chrono process and the ROS process.
- Location: Typically alongside your handler files (e.g.,
src/chrono_ros/handlers/sensor/). - Content: Plain C++ structs (POD - Plain Old Data).
- Restrictions:
- NO pointers.
- NO
std::string,std::vector, or other dynamic containers. - NO ROS or Chrono headers (please use primitive types like
double,float,char[]).
**Example (ChROSCameraHandler_ipc.h):**
Implementation Pattern: Publisher (Chrono → ROS)
Use this pattern for sensors or state reporting (e.g., Camera, GPS, Body State).
1. Main Process (.h / .cpp)
Inherit from ChROSHandler. Your job is to implement GetSerializedData.
**Header (ChROSMyHandler.h):**
**Implementation (ChROSMyHandler.cpp):**
2. Subprocess (_ros.cpp)
This file is only compiled into the ROS node executable. It uses rclcpp to publish the data.
**Implementation (ChROSMyHandler_ros.cpp):**
Implementation Pattern: Subscriber (ROS → Chrono)
Use this pattern for control inputs (e.g., Driver Inputs, Robot Control). This is a Bidirectional flow.
- Setup Phase: Main Process sends a "Setup" message (containing the topic name) to Subprocess.
- Runtime Phase: Subprocess receives ROS message → Sends IPC message to Main Process → Main Process applies to Chrono object.
1. Main Process (.h / .cpp)
**Header (ChROSMySubscriber.h):**
**Implementation (ChROSMySubscriber.cpp):**
2. Subprocess (_ros.cpp)
**Implementation (ChROSMySubscriber_ros.cpp):**
Registration Steps
To integrate your new handler into the build system and IPC framework, follow these 2 steps.
1. Add Message Type Enum
File: src/chrono_ros/ipc/ChROSIPCMessage.h
Add a new value to enum class MessageType.
Why? This unique ID allows the Subprocess to know which callback function to execute when it receives a message.
2. Update CMakeLists.txt
File: src/chrono_ros/CMakeLists.txt
Add your _ros.cpp file to the chrono_ros_node target sources.
Why? The ROS-side logic (publishing/subscribing) must be compiled into the separate chrono_ros_node executable, not the main Chrono library.
CRITICAL: If you forget this step, your handler will compile, but you will see "No handler registered for message type" warnings at runtime because the registration macro in your _ros.cpp file was never executed in the subprocess.
3. Update SWIG Interface (Optional)
File: src/chrono_swig/interface/ros/ChModuleROS.i
If you want to use your handler in Python (PyChrono), you must register it in the SWIG interface file.
Why? SWIG needs to know about your C++ class to generate the Python wrapper.
- Include Header: Add
#include "chrono_ros/handlers/sensor/ChROSMyHandler.h"in the C++ block (inside%{ ... %}). - Enable Shared Pointer: Add
shared_ptr(chrono::ros::ChROSMyHandler)in the shared pointers section. - Include SWIG Definition: Add
include "../../../chrono_ros/handlers/sensor/ChROSMyHandler.h"in the include section.
Note: You do NOT need to modify
ChROSManager.cpp. The manager is generic and will automatically handle your new class as long as it inherits fromChROSHandler.
Usage (In Your Application)
Finally, to use your new handler in a simulation:
Best Practices
- Throttling: Implement rate limiting in
GetSerializedDatausingm_update_rate. Return an empty vector if it's not time to publish. This ease the stress on ROS(its DDS middleware) since simulation can run many times faster than realtime in some cases. - Memory: Avoid reallocating vectors in the loop. Use a member variable
std::vector<uint8_t> m_bufferandresize()it. - Thread Safety: In bidirectional handlers,
HandleIncomingMessageis called from the main thread, but ensure your Chrono object modifications are safe if you are using multi-threaded stepping. - Examples:
- See
ChROSCameraHandlerfor complex data (images). - See
ChROSDriverInputsHandlerfor bidirectional control.
- See