ROS2

ROS Composition

ROS composition combines multiple nodes (ROS components) into a single process, enhancing performance and efficiency.

This way, you get Intra-process Communications inside ROS and thus enable Zero-Copy for message transport.

Can't you just use the ROS Executor and call add_node multiple times?

Yes, you can manually adding nodes to an executor, and not have to register nodes as components. This is actually just static composition.

However, if you want dynamic composition by using ROS Launch Files, you want to register them into a shared library first (more on this later).

This is why you generally always see the RCLCPP_COMPONENTS_REGISTER_NODE macro to register a node as a component.

Enabling Zero-Copy

ROS Composition enables intra-process communcations, and thus zero-copy operations since data is shared across a process. This is one of the most important things that you need to understand and master to get better performance out of ROS.

Resources

There is a demo package: https://github.com/ros2/demos/tree/rolling/composition, which is what the slides is based on.

We also make use of composition in our launch files.

Ashwin first taught me about Composable Nodes. Apparently this is how data can be shared across CPU memory, instead of having to transmit the data across different CPUs.

Components DON'T have a main function

Since a component is only built into a shared library, it doesn’t have a main function.

Developing a ROS Component

These slides from ROSCon are the most helpful.

â—Ź Make your class dynamically loadable via registration macro in C++ source

There are 2 ways to do composition:

  1. Via inheritance from rclcpp::Node
  2. Via composition

The inheritance way

namespace composition
{
 
class Talker : public rclcpp::Node
{
public:
  COMPOSITION_PUBLIC
  explicit Talker(const rclcpp::NodeOptions & options);
 
protected:
  void on_timer();
 
private:
  size_t count_;
  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr pub_;
  rclcpp::TimerBase::SharedPtr timer_;
};
 
}  // namespace composition

The composition way

namespace composition
{
 
class NodeLikeListener
{
public:
  COMPOSITION_PUBLIC
  explicit NodeLikeListener(const rclcpp::NodeOptions & options);
 
  COMPOSITION_PUBLIC
  rclcpp::node_interfaces::NodeBaseInterface::SharedPtr
  get_node_base_interface() const;
 
private:
  rclcpp::Node::SharedPtr node_;
  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr sub_;
};
 
}  // namespace composition

What is the COMPOSITION_PUBLIC keyword?

I don’t know this macro, and I haven’t seen it in our Isaac ROS codebase.

In the source, you need to add the RCLCPP_COMPONENTS_REGISTER_NODE macro to register it

#include "rclcpp_components/register_node_macro.hpp"
// Register the component with class_loader.
// This acts as a sort of entry point, allowing the component to be discoverable when its library
// is being loaded into a running process.
RCLCPP_COMPONENTS_REGISTER_NODE(composition::Talker)

Then, add them to the CmakeLists.txt:

add_library(talker_component SHARED src/talker_component.cpp)
rclcpp_components_register_nodes(talker_component "composition::Talker")

Composition does NOT work in Python

I only realized this recently after Andrew Saba told me about it, it’s probably due to the way Python works, since it’s a interpreter that goes through files and executes line by line.

Launching

There are 3 main ways to use ROS2 components (based on this tutorial):

  1. Create a launch file and use ros2 launch to create a container process with multiple components loaded.
  2. Start a (generic container process) and call the ROS service load_node offered by the container. The ROS service will then load the component specified by the passed package name and library name and start executing it within the running process. Instead of calling the ROS service programmatically you can also use a command line tool to invoke the ROS service with the passed command line arguments
  3. Create a custom executable containing multiple nodes which are known at compile time. This approach requires that each component has a header file (which is not strictly needed for the first case).

The composition package contains a couple of different approaches on how to use components. The three most common ones

Launch File Launching

Use ComposableNodeContainer if they are all in the same launch file. Else, use LaunchComposableNode.

ROS components are built into a shared library.

NVIDIA

This stuff is actually fascinating, I really need to understand what is going on under the hood.

At NVIDIA, I learned this syntax. They actually use the RCLCPP_COMPONENTS_REGISTER_NODE macro to avoid writing the int main() thing? Need to build as shared.