节点组合实践
目标: 将多个节点组合到单个进程中。
教程级别: 中级
预计用时: 20 分钟
请参阅概念文章。
有关如何编写可组合节点的信息,请参阅编写可组合节点的教程。
本教程使用 rclcpp_components、ros2component、composition 和 image_tools 包中的可执行文件。
如果你已按照所用平台的安装说明进行操作,这些包应该已经安装好了。
发现可用组件
Section titled “发现可用组件”要查看工作空间中已注册并可用的组件,请在终端中执行以下命令:
$ 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 服务进行运行时组合(发布者和订阅者)”在第一个终端中,启动组件容器:
$ ros2 run rclcpp_components component_container打开第二个终端,通过 ros2 命令行工具验证容器是否正在运行。
你应该能看到组件的名称:
$ ros2 component list/ComponentManager在第二个终端中加载 talker 组件(参见 talker 源代码)。 该命令会返回已加载组件的唯一 ID 以及节点名称:
$ ros2 component load /ComponentManager composition composition::TalkerLoaded component 1 into '/ComponentManager' container node as '/talker'现在第一个终端应该会显示一条组件已加载的消息,以及重复发布的消息。
在第二个终端中运行另一个命令来加载 listener 组件(参见 listener 源代码):
$ ros2 component load /ComponentManager composition composition::ListenerLoaded component 2 into '/ComponentManager' container node as '/listener'现在可以使用 ros2 命令行工具来检查容器的状态:
$ ros2 component list/ComponentManager 1 /talker 2 /listener现在第一个终端应该会显示每条接收到的消息的重复输出。
使用 ROS 服务进行运行时组合(服务端和客户端)
Section titled “使用 ROS 服务进行运行时组合(服务端和客户端)”使用服务端和客户端的示例非常相似。
在第一个终端中:
$ ros2 run rclcpp_components component_container在第二个终端中(参见 server 和 client 源代码):
$ ros2 component load /ComponentManager composition composition::Server$ ros2 component load /ComponentManager composition composition::Client在这种情况下,客户端向服务端发送请求,服务端处理请求并返回响应,客户端打印接收到的响应。
编译时组合(硬编码节点)
Section titled “编译时组合(硬编码节点)”本示例展示了如何复用相同的共享库来编译一个运行多个组件的可执行文件,而无需使用 ROS 接口。 该可执行文件包含上述所有四个组件:talker 和 listener,以及 server 和 client,它们被硬编码在 main 函数中。
在终端中调用(参见 源代码):
$ ros2 run composition manual_composition这应该会显示来自两对组件的重复消息:talker 和 listener,以及 server 和 client。
注意:手动组合的组件不会显示在
ros2 component list命令行工具的输出中。
使用 dlopen 进行运行时组合
Section titled “使用 dlopen 进行运行时组合”本示例展示了运行时组合的另一种方案:通过创建一个通用容器进程并显式传入要加载的库,而无需使用 ROS 接口。
该进程会打开每个库,并在库中创建每个 rclcpp::Node 类的一个实例(源代码)。
Linux:
$ ros2 run composition dlopen_composition `ros2 pkg prefix composition`/lib/libtalker_component.so `ros2 pkg prefix composition`/lib/liblistener_component.somacOS:
$ ros2 run composition dlopen_composition `ros2 pkg prefix composition`/lib/libtalker_component.dylib `ros2 pkg prefix composition`/lib/liblistener_component.dylibWindows:
$ ros2 pkg prefix composition获取 composition 安装路径。然后调用:
$ 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 动作进行组合
Section titled “使用 launch 动作进行组合”虽然命令行工具对于调试和诊断组件配置很有用,但同时启动一组组件通常更加方便。 为了自动化这个操作,我们可以使用一个 launch 文件:
$ ros2 launch composition composition_demo_launch.py现在我们已经了解了组件的基本操作,接下来可以讨论一些更高级的主题。
组件容器类型
Section titled “组件容器类型”如 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
在第一个终端中,启动组件容器:
$ ros2 run rclcpp_components component_container通过 ros2 命令行工具验证容器是否正在运行:
$ ros2 component list/ComponentManager在第二个终端中,像之前一样加载 talker 和 listener:
$ ros2 component load /ComponentManager composition composition::TalkerLoaded component 1 into '/ComponentManager' container node as '/talker'$ ros2 component load /ComponentManager composition composition::ListenerLoaded component 2 into '/ComponentManager' container node as '/listener'组件加载时会打印其唯一 ID。 你也可以通过列出已加载的组件来获取所有组件的唯一 ID:
$ ros2 component list/ComponentManager 1 /talker 2 /listener使用唯一 ID 从组件容器中卸载组件。
$ ros2 component unload /ComponentManager 1 2Unloaded component 1 from '/ComponentManager' containerUnloaded component 2 from '/ComponentManager' container在第一个终端中,验证 talker 和 listener 的重复消息是否已停止。
重映射容器名称和命名空间
Section titled “重映射容器名称和命名空间”组件管理器的名称和命名空间可以通过标准命令行参数进行重映射:
$ ros2 run rclcpp_components component_container --ros-args -r __node:=MyContainer -r __ns:=/ns在第二个终端中,可以使用更新后的容器名称来加载组件:
$ ros2 component load /ns/MyContainer composition composition::Listener注意:容器的命名空间重映射不会影响已加载的组件。
重映射组件名称和命名空间
Section titled “重映射组件名称和命名空间”组件的名称和命名空间可以通过 load 命令的参数进行调整。
在第一个终端中,启动组件容器:
$ ros2 run rclcpp_components component_container以下是一些重映射名称和命名空间的示例。
重映射节点名称:
$ ros2 component load /ComponentManager composition composition::Talker --node-name talker2重映射命名空间:
$ ros2 component load /ComponentManager composition composition::Talker --node-namespace /ns同时重映射两者:
$ ros2 component load /ComponentManager composition composition::Talker --node-name talker3 --node-namespace /ns2现在使用 ros2 命令行工具:
$ ros2 component list/ComponentManager 1 /talker2 2 /ns/talker 3 /ns2/talker3注意:容器的命名空间重映射不会影响已加载的组件。
向组件传递参数值
Section titled “向组件传递参数值”ros2 component load 命令行支持在构造节点时向其传递任意参数。
该功能的使用方式如下:
$ 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向组件传递额外参数
Section titled “向组件传递额外参数”ros2 component load 命令行支持向组件管理器传递特定选项,供构造节点时使用。
以下示例展示了额外参数 use_intra_process_comms 和 forward_global_arguments 的使用:
$ ros2 component load /ComponentManager composition composition::Talker -e use_intra_process_comms:=true -e forward_global_arguments:=false支持以下额外参数。
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
forward_global_arguments | Boolean | True | 加载时将全局参数应用到组件节点。 |
enable_rosout | Boolean | True | 在组件节点中启用 rosout 话题发布者。 |
use_intra_process_comms | Boolean | False | 在组件节点中启用进程内通信。 |
enable_topic_statistics | Boolean | False | 在组件节点中启用话题统计信息发布者。 |
start_parameter_services | Boolean | True | 在组件节点中启用管理参数的服务。 |
start_parameter_event_publisher | Boolean | True | 在组件节点中启用参数事件发布者。 |
use_clock_thread | Boolean | True | 在组件节点中启用专用时钟线程。 |
enable_logger_service | Boolean | False | 在组件节点中启用日志级别管理服务。 |
作为共享库的可组合节点
Section titled “作为共享库的可组合节点”如果你想将某个包中的可组合节点导出为共享库,并在另一个执行链接时组合的包中使用它,请在 CMake 文件中添加代码,以便下游包可以导入实际目标。
然后安装生成的文件并导出该文件。
可以在此处查看实际示例:ROS Discourse - Ament best practice for sharing libraries
组合非节点派生组件
Section titled “组合非节点派生组件”在 ROS 2 中,组件可以更高效地利用系统资源。它还提供了一项强大的功能:让你能够创建与特定节点无关的可复用功能。
使用组件的一个优势在于,你可以将非节点派生的功能封装为独立的可执行文件或共享库,并根据需要加载到 ROS 系统中。
要创建一个不从节点派生的组件,请遵循以下指导原则:
- 实现一个接受
const rclcpp::NodeOptions&作为参数的构造函数。 - 实现
get_node_base_interface()方法,该方法应返回一个NodeBaseInterface::SharedPtr。 你可以使用在构造函数中创建的节点的get_node_base_interface()方法来提供此接口。
以下是一个不从节点派生的组件示例,它监听一个 ROS 话题:node_like_listener_component。
有关此主题的更多信息,你可以参阅此 讨论。