You're reading the documentation for an older, but still supported, version of ROS 2. For information on the latest version, please have a look at Jazzy.
Writing a Composable Node (C++)
Starting Place
Let’s assume that you have a regular rclcpp::Node
executable that you want to run in the same process as other nodes to enable more efficient communication.
We’ll start from having a class that directly inherits from Node
, and that also has a main method defined.
namespace palomino
{
class VincentDriver : public rclcpp::Node
{
// ...
};
}
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<palomino::VincentDriver>());
rclcpp::shutdown();
return 0;
}
This will typically be compiled as an executable in your Cmake.
# ...
add_executable(vincent_driver src/vincent_driver.cpp)
# ...
install(TARGETS vincent_driver
DESTINATION lib/${PROJECT_NAME}
)
Code Updates
Add the Package Dependency
Your package.xml should have a dependency on rclcpp_components
, a la
<depend>rclcpp_components</depend>
Alternatively, you can independently add a build_depend/exec_depend
.
Class Definition
The only change to your class definition that you may have to do is ensure that the constructor for the class takes a NodeOptions
argument.
VincentDriver(const rclcpp::NodeOptions & options) : Node("vincent_driver", options)
{
// ...
}
No More Main Method
Replace your main method with a pluginlib
-style macro invocation.
#include <rclcpp_components/register_node_macro.hpp>
RCLCPP_COMPONENTS_REGISTER_NODE(palomino::VincentDriver)
Caution
If the main method you are replacing contains a MultiThreadedExecutor
, be sure to make note and ensure that your container node is multithreaded.
See section below.
CMake Changes
First, add rclcpp_components
as a dependency in your CMakeLists.txt with:
find_package(rclcpp_components REQUIRED)
Second, we’re going to replace our add_executable
with a add_library
with a new target name.
add_library(vincent_driver_component src/vincent_driver.cpp)
Third, replace other build commands that used the old target to act on the new target.
i.e. ament_target_dependencies(vincent_driver ...)
becomes ament_target_dependencies(vincent_driver_component ...)
Fourth, add a new command to declare your component.
rclcpp_components_register_node(
vincent_driver_component
PLUGIN "palomino::VincentDriver"
EXECUTABLE vincent_driver
)
Fifth and finally, change any installation commands in the CMake that operated on the old target to install the library version instead.
For instance, do not install either target into lib/${PROJECT_NAME}
.
Replace with the library installation.
ament_export_targets(export_vincent_driver_component)
install(TARGETS vincent_driver_component
EXPORT export_vincent_driver_component
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)
Running Your Node
See the Composition tutorial for an in-depth look at composing nodes. The quick and dirty version is that if you had the following in your Python launch file,
from launch_ros.actions import Node
# ..
ld.add_action(Node(
package='palomino',
executable='vincent_driver',
# ..
))
you can replace it with
from launch_ros.actions import ComposableNodeContainer
from launch_ros.descriptions import ComposableNode
# ..
ld.add_action(ComposableNodeContainer(
name='a_buncha_nodes',
namespace='',
package='rclcpp_components',
executable='component_container',
composable_node_descriptions=[
ComposableNode(
package='palomino',
plugin='palomino::VincentDriver',
name='vincent_driver',
# ..
extra_arguments=[{'use_intra_process_comms': True}],
),
]
))
Caution
If you need multi-threading, instead of setting your executable to component_container
, set it to component_container_mt