ROS专题 · 2024年3月11日 0

ROS 2基础概念#9:节点组合(Node Composition)| ROS 2学习笔记

节点组合(Node Composition)是ROS 2中一个重要且强大的特性,它允许开发者以更灵活和高效的方式来构建和管理机器人软件组件。尽管节点的独立性带来了许多优点,但在某些情况下,将多个节点运行在同一个进程中可能更为高效。这是因为跨进程通信比进程内通信要消耗更多的资源,特别是在消息传递频繁或对延迟要求较高的应用场景中。节点组合(也称为组合节点或节点静态组合)正是为了解决这一问题而引入的。

节点组合的概念

节点组合允许将多个节点加载到同一个进程中,每个节点仍然保持独立的逻辑和功能,但它们可以更高效地共享数据和资源。这种方法不仅减少了通信延迟,而且降低了系统的整体资源消耗(例如,CPU时间和内存使用)。

节点组合的优势

  1. 提高性能:减少了进程间通信的需要,降低了通信延迟,特别是在高频消息交换的场景中。
  2. 资源高效:多个节点共享同一个进程资源,减少了资源的冗余使用。
  3. 部署简化:简化了节点的管理和部署,尤其是在资源受限的环境下,如嵌入式系统或移动机器人。
  4. 灵活性和可重用性:组合节点可以根据需要灵活组合,易于重构和重用,加速开发过程。

实现节点组合

在ROS 2中实现节点组合通常涉及以下几个步骤:

  1. 设计可组合的节点:开发时,需将节点设计为可组合的 Component ,即可以单独运行也可以作为组合的一部分运行。
  2. 创建容器节点:容器节点是一个特殊的节点,其角色是加载和运行其他节点。ROS 2提供了标准容器节点,也允许开发者自定义。
  3. 加载节点到容器:通过ROS 2工具或在代码中指定,将单个或多个节点动态加载到容器节点中。
  4. 运行组合节点:启动容器节点,自动初始化并运行加载的节点。

ROS 2 Unified API

ROS 2 中,推荐的编写代码的方式类似于 ROS 1 Nodelet – 我们称之为组件 ( Component )。 这使得向现有代码添加常见概念(例如生命周期)变得很容易。 ROS 1 中最大的缺点是 Node 和 Nodelet 拥有不同的 API,而 ROS 2 则避免了这种情况,因为 Node 和 Node Composition 两种方法都使用相同的 API。

通过将流程布局作在部署时决策,用户可以选择:

  • 在单独的进程中运行多个节点,具有进程/故障隔离的优点以及更容易调试各个节点和
  • 在单个进程中运行多个节点,具有较低的开销和可选的更高效的通信。

此外,ros2 launch 可用于通过专门的启动操作来自动执行这些操作。

编写 Component

由于组件仅内置于共享库中,因此它没有 main 函数。 组件通常是 rclcpp::Node 的子类。 由于它不控制线程,因此它不应该在其构造函数中执行任何长时间运行或阻塞的任务。 相反,它可以使用计时器来获取定期通知。 此外,它还可以创建发布者、订阅、服务器和客户端。使该类成为组件的一个重要方面是该类使用 rclcpp_components 包中的宏来注册自身。 这使得当其库被加载到正在运行的进程中时该组件是可发现的——它充当某种入口点。

此外,一旦创建了组件,就必须使用索引进行注册,以便工具可以发现它。

add_library(talker_component SHARED src/talker_component.cpp)
rclcpp_components_register_nodes(talker_component "composition::Talker")
# To register multiple components in the same shared library, use multiple calls
# rclcpp_components_register_nodes(talker_component "composition::Talker2")

使用 Component

节点组合包包含几种关于如何使用组件的不同方法。 最常见的三个是:

  • 启动一个(通用容器进程)并调用容器提供的ROS服务load_node。 然后,ROS 服务将加载由传递的包名称和库名称指定的组件,并开始在运行的进程中执行它。 您还可以使用命令行工具通过传递的命令行参数来调用 ROS 服务,而不是以编程方式调用 ROS 服务
  • 创建一个包含编译时已知的多个节点的自定义可执行文件。 这种方法要求每个组件都有一个头文件(第一种情况并不严格需要)。
  • 创建launch文件,使用ros2 launch创建一个加载多个组件的容器进程。

节点组合 Demo 上手实践

本文 demo 使用 rclcpp_componentsros2componentcomposition 包中的可执行文件,并且可以使用以下命令运行。

发现可用组件

要查看工作区中已注册和可用的组件,请在 shell 中执行以下命令:

ros2 component types

将返回类似如下的结果:

(... components of other packages here)
composition
  composition::Talker
  composition::Listener
  composition::Server
  composition::Client

使用带发布和订阅的 ROS 2 服务进行运行时组合

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

ros2 run rclcpp_components component_container

打开第二个 shell 并通过 ros2 命令行工具验证容器是否正在运行:

ros2 component list

将看到如下的组件正在运行:

/ComponentManager

在第二个 shell 中加载 talker 组件:

ros2 component load /ComponentManager composition composition::Talker

现在,第一个 shell 应该显示一条消息,表明组件已加载,以及用于发布消息的重复消息。

在第二个 shell 中运行另一个命令来加载侦听器组件:

ros2 component load /ComponentManager composition composition::Listener

将返回如下的信息:

Loaded component 2 into '/ComponentManager' container node as '/listener'

接下来可以用 ros2 命令行实用程序检查容器的状态:

ros2 component list

将看到如下的结果:

/ComponentManager
   1  /talker
   2  /listener

现在,第一个 shell 应该为每条收到的消息显示重复的输出。

使用带服务端和客户端的 ROS 2 服务进行运行时组合

服务器和客户端的示例非常相似。在第一个 shell 中运行如下命令:

ros2 run rclcpp_components component_container

在第二个 shell 中运行如下命令:

ros2 component load /ComponentManager composition composition::Server
ros2 component load /ComponentManager composition composition::Client

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

使用 ROS 2 服务实现编译时节点组合

本演示表明,可以重用相同的共享库来编译运行多个组件的单个可执行文件。 该可执行文件包含上述所有四个组件:发送者和接收者以及服务器和客户端。

在 shell 中调用:

ros2 run composition manual_composition

这应该显示来自两对(Talker / Listener和服务端 / 客户端)的重复消息。

使用 dlopen 实现运行时节点组合

该演示通过创建通用容器进程并显式传递要加载的库而不使用 ROS 接口,提供了运行时组合的替代方案。 该过程将打开每个库并在库中创建一个 rclcpp::Node 类实例。

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

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

使用 launch 文件实现节点组合

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

ros2 launch composition composition_demo.launch.py

卸载组件

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

ros2 run rclcpp_components component_container

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

ros2 component list

将看到如下的组件名:

/ComponentManager

在第二个 shell 中运行如下的命令。 该命令将返回已加载组件的唯一 ID 以及节点名称。

ros2 component load /ComponentManager composition composition::Talker
ros2 component load /ComponentManager composition composition::Listener

使用唯一 ID 从组件容器中卸载节点:

ros2 component unload /ComponentManager 1 2

将返回如下的信息:

Unloaded component 1 from '/ComponentManager' container
Unloaded component 2 from '/ComponentManager' container

在第一个 shell 中,验证来自发送者和接收者的重复消息是否已停止。

重新映射容器名称和命名空间

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

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

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

ros2 component load /ns/MyContainer composition composition::Listener

重新映射组件名称和命名空间

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

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

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

将参数值传递给组件

使用 ros2 component load 命令行在构建节点时将任意参数传递给节点。 此功能可以按如下方式使用:

ros2 component load /ComponentManager image_tools image_tools::Cam2Image -p burger_mode:=true

将附加参数传递给组件

可以使用 ros2 component load 命令行将特定选项传递给组件管理器以在构建节点时使用。 到目前为止,唯一支持的命令行选项是使用进程内通信来实例化节点。 此功能可以按如下方式使用:

ros2 component load /ComponentManager composition composition::Talker -e use_intra_process_comms:=true

可组合节点作为共享库

如果要将可组合节点作为共享库从包中导出,并在执行链接时组合的另一个包中使用该节点,请将代码添加到 CMake 文件中,以导入下游包中的实际目标。然后安装并导出生成的文件。

组合非节点派生组件

在 ROS 2 中,组件可以更有效地使用系统资源,并提供强大的功能,使您能够创建不依赖于特定节点的可重用功能。

使用组件的优点之一是,它们允许您将非节点派生功能创建为独立的可执行文件或共享库,可以根据需要加载到 ROS 系统中。

要创建不是从节点派生的组件,请遵循以下准则:

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

结论

ROS 2的节点组合是一种强大的特性,它通过在同一进程中运行多个节点来提高性能和资源利用率。这种方法特别适合于那些需要高效数据处理和低延迟通信的机器人应用。通过节点组合,开发者可以更灵活地构建和优化他们的机器人系统,同时保持代码的清晰和模块化。然而,正确实施节点组合需要细致的设计和考虑,包括模块化、性能测试和错误管理等方面。随着ROS 2社区的成长和发展,节点组合无疑将继续成为机器人开发中的一个重要工具。