Skip to content

Commit

Permalink
Writing a Composable Node (C++) Tutorial (#4106)
Browse files Browse the repository at this point in the history
* Writing a Composable Node (C++) Tutorial

* Add Package.xml note

* Add links to demos

Signed-off-by: David V. Lu!! <[email protected]>
Co-authored-by: Chris Lalancette <[email protected]>
(cherry picked from commit 0c79b0f)
  • Loading branch information
DLu authored and mergify[bot] committed Feb 16, 2024
1 parent a1728fb commit 2eb99bc
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 1 deletion.
2 changes: 2 additions & 0 deletions source/Concepts/Intermediate/About-Composition.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion source/Releases/Release-Dashing-Diademata.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://index.ros.org/doc/ros2/Tutorials/Composition/>`__
For more information on composition, see `the tutorial <../Tutorials/Intermediate/Writing-a-Composable-Node>`

rclpy
^^^^^
Expand Down
1 change: 1 addition & 0 deletions source/Tutorials/Intermediate.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions source/Tutorials/Intermediate/Composition.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <Writing-a-Composable-Node>`.

Run the demos
-------------

Expand Down
170 changes: 170 additions & 0 deletions source/Tutorials/Intermediate/Writing-a-Composable-Node.rst
Original file line number Diff line number Diff line change
@@ -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<palomino::VincentDriver>());
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 <https://github.com/ros2/demos/tree/{REPOS_FILE_BRANCH}/composition/package.xml>`__ should have a dependency on ``rclcpp_components``, a la

.. code-block:: xml
<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 <https://github.com/ros2/demos/tree/{REPOS_FILE_BRANCH}/composition/src/talker_component.cpp>`__ 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_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:

.. 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 <Composition>` 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``

0 comments on commit 2eb99bc

Please sign in to comment.