Skip to content

节点组合实践

目标: 将多个节点组合到单个进程中。

教程级别: 中级

预计用时: 20 分钟

请参阅概念文章。

有关如何编写可组合节点的信息,请参阅编写可组合节点的教程。

本教程使用 rclcpp_components、ros2component、composition 和 image_tools 包中的可执行文件。 如果你已按照所用平台的安装说明进行操作,这些包应该已经安装好了。

要查看工作空间中已注册并可用的组件,请在终端中执行以下命令:

Terminal window
$ ros2 component types
(... components of other packages here)
composition
composition::Talker
composition::Listener
composition::NodeLikeListener
composition::Server
composition::Client
(... components of other packages here)

使用 ROS 服务进行运行时组合(发布者和订阅者)

Section titled “使用 ROS 服务进行运行时组合(发布者和订阅者)”

在第一个终端中,启动组件容器:

Terminal window
$ ros2 run rclcpp_components component_container

打开第二个终端,通过 ros2 命令行工具验证容器是否正在运行。 你应该能看到组件的名称:

Terminal window
$ ros2 component list
/ComponentManager

在第二个终端中加载 talker 组件(参见 talker 源代码)。 该命令会返回已加载组件的唯一 ID 以及节点名称:

Terminal window
$ ros2 component load /ComponentManager composition composition::Talker
Loaded component 1 into '/ComponentManager' container node as '/talker'

现在第一个终端应该会显示一条组件已加载的消息,以及重复发布的消息。

在第二个终端中运行另一个命令来加载 listener 组件(参见 listener 源代码):

Terminal window
$ ros2 component load /ComponentManager composition composition::Listener
Loaded component 2 into '/ComponentManager' container node as '/listener'

现在可以使用 ros2 命令行工具来检查容器的状态:

Terminal window
$ ros2 component list
/ComponentManager
1 /talker
2 /listener

现在第一个终端应该会显示每条接收到的消息的重复输出。

使用 ROS 服务进行运行时组合(服务端和客户端)

Section titled “使用 ROS 服务进行运行时组合(服务端和客户端)”

使用服务端和客户端的示例非常相似。

在第一个终端中:

Terminal window
$ ros2 run rclcpp_components component_container

在第二个终端中(参见 server 和 client 源代码):

Terminal window
$ ros2 component load /ComponentManager composition composition::Server
$ ros2 component load /ComponentManager composition composition::Client

在这种情况下,客户端向服务端发送请求,服务端处理请求并返回响应,客户端打印接收到的响应。

本示例展示了如何复用相同的共享库来编译一个运行多个组件的可执行文件,而无需使用 ROS 接口。 该可执行文件包含上述所有四个组件:talker 和 listener,以及 server 和 client,它们被硬编码在 main 函数中。

在终端中调用(参见 源代码):

Terminal window
$ ros2 run composition manual_composition

这应该会显示来自两对组件的重复消息:talker 和 listener,以及 server 和 client。

注意:手动组合的组件不会显示在 ros2 component list 命令行工具的输出中。

本示例展示了运行时组合的另一种方案:通过创建一个通用容器进程并显式传入要加载的库,而无需使用 ROS 接口。 该进程会打开每个库,并在库中创建每个 rclcpp::Node 类的一个实例(源代码)。

Linux:

Terminal window
$ ros2 run composition dlopen_composition `ros2 pkg prefix composition`/lib/libtalker_component.so `ros2 pkg prefix composition`/lib/liblistener_component.so

macOS:

Terminal window
$ ros2 run composition dlopen_composition `ros2 pkg prefix composition`/lib/libtalker_component.dylib `ros2 pkg prefix composition`/lib/liblistener_component.dylib

Windows:

Terminal window
$ ros2 pkg prefix composition

获取 composition 安装路径。然后调用:

Terminal window
$ ros2 run composition dlopen_composition <path_to_composition_install>\bin\talker_component.dll <path_to_composition_install>\bin\listener_component.dll

现在终端应该会显示每条发送和接收消息的重复输出。

注意:通过 dlopen 组合的组件不会显示在 ros2 component list 命令行工具的输出中。

虽然命令行工具对于调试和诊断组件配置很有用,但同时启动一组组件通常更加方便。 为了自动化这个操作,我们可以使用一个 launch 文件:

Terminal window
$ ros2 launch composition composition_demo_launch.py

现在我们已经了解了组件的基本操作,接下来可以讨论一些更高级的主题。

如 ComponentContainer 中所介绍的,有几种具有不同选项的组件容器类型。 你可以根据需要选择最合适的组件容器类型。

  • 使用 SingleThreadedExecutor 的 component_container。

    Terminal window
    $ ros2 run rclcpp_components component_container
  • 使用由 4 个线程组成的 MultiThreadedExecutor 的 component_container。

    • thread_num 参数选项可用于指定 MultiThreadedExecutor 中的线程数。 如果省略,executor 将使用系统上最大可用线程数运行。
    Terminal window
    $ ros2 run rclcpp_components component_container --executor-type multi-threaded --ros-args -p thread_num:=4
  • 使用由单个线程组成的 EventsCBGExecutor 的 component_container。

    • thread_num 参数选项可用于指定 EventsCBGExecutor 中的线程数。 如果省略,executor 将使用系统上最大可用线程数运行。
    Terminal window
    $ ros2 run rclcpp_components component_container --executor-type events-cbg --ros-args -p thread_num:=1
  • 为每个组件使用专用 MultiThreadedExecutor 的 component_container。

    Terminal window
    $ ros2 run rclcpp_components component_container --executor-type multi-threaded --isolated

在第一个终端中,启动组件容器:

Terminal window
$ ros2 run rclcpp_components component_container

通过 ros2 命令行工具验证容器是否正在运行:

Terminal window
$ ros2 component list
/ComponentManager

在第二个终端中,像之前一样加载 talker 和 listener:

Terminal window
$ ros2 component load /ComponentManager composition composition::Talker
Loaded component 1 into '/ComponentManager' container node as '/talker'
$ ros2 component load /ComponentManager composition composition::Listener
Loaded component 2 into '/ComponentManager' container node as '/listener'

组件加载时会打印其唯一 ID。 你也可以通过列出已加载的组件来获取所有组件的唯一 ID:

Terminal window
$ ros2 component list
/ComponentManager
1 /talker
2 /listener

使用唯一 ID 从组件容器中卸载组件。

Terminal window
$ ros2 component unload /ComponentManager 1 2
Unloaded component 1 from '/ComponentManager' container
Unloaded component 2 from '/ComponentManager' container

在第一个终端中,验证 talker 和 listener 的重复消息是否已停止。

组件管理器的名称和命名空间可以通过标准命令行参数进行重映射:

Terminal window
$ ros2 run rclcpp_components component_container --ros-args -r __node:=MyContainer -r __ns:=/ns

在第二个终端中,可以使用更新后的容器名称来加载组件:

Terminal window
$ ros2 component load /ns/MyContainer composition composition::Listener

注意:容器的命名空间重映射不会影响已加载的组件。

组件的名称和命名空间可以通过 load 命令的参数进行调整。

在第一个终端中,启动组件容器:

Terminal window
$ ros2 run rclcpp_components component_container

以下是一些重映射名称和命名空间的示例。

重映射节点名称:

Terminal window
$ ros2 component load /ComponentManager composition composition::Talker --node-name talker2

重映射命名空间:

Terminal window
$ ros2 component load /ComponentManager composition composition::Talker --node-namespace /ns

同时重映射两者:

Terminal window
$ ros2 component load /ComponentManager composition composition::Talker --node-name talker3 --node-namespace /ns2

现在使用 ros2 命令行工具:

Terminal window
$ ros2 component list
/ComponentManager
1 /talker2
2 /ns/talker
3 /ns2/talker3

注意:容器的命名空间重映射不会影响已加载的组件。

ros2 component load 命令行支持在构造节点时向其传递任意参数。 该功能的使用方式如下:

Terminal window
$ ros2 component load /ComponentManager image_tools image_tools::Cam2Img -p burger_mode:=true
$ ros2 run rqt_image_view rqt_image_view # Shows burgers bouncing, instead of image from camera

ros2 component load 命令行支持向组件管理器传递特定选项,供构造节点时使用。

以下示例展示了额外参数 use_intra_process_comms 和 forward_global_arguments 的使用:

Terminal window
$ ros2 component load /ComponentManager composition composition::Talker -e use_intra_process_comms:=true -e forward_global_arguments:=false

支持以下额外参数。

参数类型默认值描述
forward_global_argumentsBooleanTrue加载时将全局参数应用到组件节点。
enable_rosoutBooleanTrue在组件节点中启用 rosout 话题发布者。
use_intra_process_commsBooleanFalse在组件节点中启用进程内通信。
enable_topic_statisticsBooleanFalse在组件节点中启用话题统计信息发布者。
start_parameter_servicesBooleanTrue在组件节点中启用管理参数的服务。
start_parameter_event_publisherBooleanTrue在组件节点中启用参数事件发布者。
use_clock_threadBooleanTrue在组件节点中启用专用时钟线程。
enable_logger_serviceBooleanFalse在组件节点中启用日志级别管理服务。

如果你想将某个包中的可组合节点导出为共享库,并在另一个执行链接时组合的包中使用它,请在 CMake 文件中添加代码,以便下游包可以导入实际目标。

然后安装生成的文件并导出该文件。

可以在此处查看实际示例:ROS Discourse - Ament best practice for sharing libraries

在 ROS 2 中,组件可以更高效地利用系统资源。它还提供了一项强大的功能:让你能够创建与特定节点无关的可复用功能。

使用组件的一个优势在于,你可以将非节点派生的功能封装为独立的可执行文件或共享库,并根据需要加载到 ROS 系统中。

要创建一个不从节点派生的组件,请遵循以下指导原则:

  1. 实现一个接受 const rclcpp::NodeOptions& 作为参数的构造函数。
  2. 实现 get_node_base_interface() 方法,该方法应返回一个 NodeBaseInterface::SharedPtr。 你可以使用在构造函数中创建的节点的 get_node_base_interface() 方法来提供此接口。

以下是一个不从节点派生的组件示例,它监听一个 ROS 话题:node_like_listener_component。

有关此主题的更多信息,你可以参阅此 讨论。