How to Create a Custom ROS Handler

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. All handlers inherit from the ChROSHandler class.

Creating a Custom Handler

To create a custom handler, you must create a new class that inherits from ChROSHandler. This class must implement the following methods:

  • Initialize: This method is called once when the handler is created. It is used to initialize the handler and set up any ROS entities that are needed. This is where you would create publishers, subscribers, and services, not in the constructor.
  • Tick: This method is called once per simulation step. It is used to update the handler and process any incoming messages.

Furthermore, the ChROSHandler takes a single argument in its constructor, which is the rate at which the Tick method is called. This is the rate at which the handler will process messages and update its state, relative to the simulation update rate.

C++ Example

Here is an example of a simple custom handler that publishes a message to a ROS topic:

class MyCustomHandler : public ChROSHandler {
public:
MyCustomHandler(const std::string& topic) : ChROSHandler(30), m_topic(topic), m_ticker(0) {}
virtual bool Initialize(std::shared_ptr<ChROSInterface> interface) override {
m_publisher = interface->GetNode()->create_publisher<std_msgs::msg::Int64>(m_topic, 1);
return true;
}
virtual void Tick(double time) override {
std_msgs::msg::Int64 msg;
msg.data = m_ticker;
m_publisher->publish(msg);
m_ticker++;
}
private:
const std::string m_topic;
rclcpp::Publisher<std_msgs::msg::Int64>::SharedPtr m_publisher;
int m_ticker;
};

You then need to register the handler with the ChROSManager:

// Create the ROS manager
auto ros_manager = chrono_types::make_shared<ChROSManager>();
// Create the custom handler
auto custom_handler = chrono_types::make_shared<MyCustomHandler>("/my_topic");
// Register the custom handler with the ROS manager
ros_manager->RegisterHandler(custom_handler);

Python Example

Here is an example of a simple custom handler that subscribes to a ROS topic:

class MyCustomHandler(chros.ChROSHandler):
"""This custom handler will just publish int messages to a topic."""
def __init__(self, topic):
super().__init__(30) # 30 Hz
self.topic = topic
self.publisher: rclpy.publisher.Publisher = None
self.ticker = 0
def Initialize(self, interface: chros.ChROSPythonInterface) -> bool:
self.publisher = interface.GetNode().create_publisher(Int64, self.topic, 1)
return True
def Tick(self, time: float):
msg = Int64()
msg.data = self.ticker
self.publisher.publish(msg)

You then need to register the handler with the ChROSManager; however, because python wrapping of ROS 2 is not supported, you will instead need to use rclpy, which is exposed to the user through the ChROSPythonManager:

# Create the ROS manager
ros_manager = chros.ChROSPythonManager()
# Create the custom handler
custom_handler = MyCustomHandler("/my_topic")
# Register the custom handler with the ROS manager
ros_manager.RegisterPythonHandler(custom_handler)

If you want to add one of the built-in C++ handlers to the manager, you can use the RegisterHandler method as usual:

# Add the built-in clock handler
ros_manager.RegisterHandler(chros.ChROSClockHandler())