diff --git a/source/Concepts/Intermediate/About-Composition.rst b/source/Concepts/Intermediate/About-Composition.rst index 4a6a9399ee3..a6cbdb5dcc1 100644 --- a/source/Concepts/Intermediate/About-Composition.rst +++ b/source/Concepts/Intermediate/About-Composition.rst @@ -54,6 +54,8 @@ Additionally, once a component is created, it must be registered with the index # To register multiple components in the same shared library, use multiple calls # rclcpp_components_register_nodes(talker_component "composition::Talker2") +For an example, :doc:`check out this tutorial <../../Tutorials/Intermediate/Writing-a-Composable-Node>` + .. note:: In order for the component_container to be able to find desired components, it must be executed or launched from a shell that has sourced the corresponding workspace. diff --git a/source/Releases/Release-Dashing-Diademata.rst b/source/Releases/Release-Dashing-Diademata.rst index 1b715590163..9a26bb7699e 100644 --- a/source/Releases/Release-Dashing-Diademata.rst +++ b/source/Releases/Release-Dashing-Diademata.rst @@ -499,7 +499,7 @@ If not present, registration macros must be added to the project's CMake. add_library(listener src/listener.cpp) rclcpp_components_register_nodes(listener "composition::Listener") -For more information on composition, see `the tutorial `__ +For more information on composition, see `the tutorial <../Tutorials/Intermediate/Writing-a-Composable-Node>` rclpy ^^^^^ diff --git a/source/Tutorials/Intermediate.rst b/source/Tutorials/Intermediate.rst index 4db678e5f3a..4b1125e482e 100644 --- a/source/Tutorials/Intermediate.rst +++ b/source/Tutorials/Intermediate.rst @@ -8,6 +8,7 @@ Intermediate Intermediate/Creating-an-Action Intermediate/Writing-an-Action-Server-Client/Cpp Intermediate/Writing-an-Action-Server-Client/Py + Intermediate/Writing-a-Composable-Node Intermediate/Composition Intermediate/Monitoring-For-Parameter-Changes-CPP Intermediate/Launch/Launch-Main diff --git a/source/Tutorials/Intermediate/Composition.rst b/source/Tutorials/Intermediate/Composition.rst index b143d60aba3..4e91bebb8b1 100644 --- a/source/Tutorials/Intermediate/Composition.rst +++ b/source/Tutorials/Intermediate/Composition.rst @@ -21,6 +21,8 @@ Background See the :doc:`conceptual article <../../Concepts/Intermediate/About-Composition>`. +For information on how to write a composable node, :doc:`check out this tutorial `. + Run the demos ------------- diff --git a/source/Tutorials/Intermediate/Writing-a-Composable-Node.rst b/source/Tutorials/Intermediate/Writing-a-Composable-Node.rst new file mode 100644 index 00000000000..9f559aa2444 --- /dev/null +++ b/source/Tutorials/Intermediate/Writing-a-Composable-Node.rst @@ -0,0 +1,170 @@ +Writing a Composable Node (C++) +=============================== + +.. contents:: Table of Contents + :depth: 2 + :local: + +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. + +.. code-block:: c++ + + namespace palomino + { + class VincentDriver : public rclcpp::Node + { + // ... + }; + } + + int main(int argc, char * argv[]) + { + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; + } + +This will typically be compiled as an executable in your Cmake. + +.. code-block:: 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 + +.. code-block:: xml + + rclcpp_components + +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. + +.. code-block:: c++ + + VincentDriver(const rclcpp::NodeOptions & options) : Node("vincent_driver", options) + { + // ... + } + +No More Main Method +^^^^^^^^^^^^^^^^^^^ + +Replace your main method with a ``pluginlib``-style macro invocation. + +.. code-block:: c++ + + #include + 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: + +.. code-block:: cmake + + find_package(rclcpp_components REQUIRED) + +Second, we're going to replace our ``add_executable`` with a ``add_library`` with a new target name. + +.. code-block:: cmake + + 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. + +.. code-block:: cmake + + 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. + +.. code-block:: cmake + + 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 :doc:`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, + +.. code-block:: python + + from launch_ros.actions import Node + + # .. + + ld.add_action(Node( + package='palomino', + executable='vincent_driver', + # .. + )) + +you can replace it with + +.. code-block:: python + + 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``