【ROS2】Intermediate - 单个进程中组合多个节点

在单个进程中组合多个节点

目录

  • 背景
  • 运行演示
    • 发现可用组件
    • 使用ROS服务的运行时组合(发布者和订阅者)
    • 使用ROS服务的运行时组合(服务端和客户端)
    • 使用ROS服务的编译时组合
    • 使用dlopen的运行时组合
    • 使用launch动作的组合
  • 高级主题
    • 卸载组件
    • 重映射容器名称和命名空间
    • 重映射组件名称和命名空间
    • 向组件传递参数值
    • 向组件传递额外参数
  • 作为共享库的可组合节点
  • 组合非节点派生的组件

目标

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

难度级别:中级
预计时间:20分钟

背景

参见概念文章。

运行演示

演示使用来自 rclcpp_componentsros2componentcomposition 包的可执行文件,可以用以下命令运行。

发现可用组件

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

ros2 component types

终端会返回所有可用组件的列表:

(... 其他包的组件在此)
composition
  composition::Talker
  composition::Listener
  composition::Server
  composition::Client

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

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

ros2 run rclcpp_components component_container

在第二个终端中,使用ros2命令行工具验证容器是否正在运行:

ros2 component list

你应该会看到组件名称:

/ComponentManager

在第二个终端中加载talker组件(见talker源代码):

ros2 component load /ComponentManager composition composition::Talker

该命令会返回已加载组件的唯一ID以及节点名称:

Loaded component 1 into '/ComponentManager' container node as '/talker'

现在第一个终端应该会显示组件已加载的消息,以及不断发布消息的输出。

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

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

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

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

服务端和客户端的例子非常类似。

在第一个终端中:

ros2 run rclcpp_components component_container

在第二个终端中(见服务端和客户端源代码):

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

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

使用ROS服务的编译时组合

这个演示展示了相同的共享库可以被重用来编译一个运行多个组件的单一可执行文件。该可执行文件包含了上述所有四个组件:talker、listener以及server和client。

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

ros2 run composition manual_composition

这应该会同时显示talker/listener对和server/client对的重复消息。

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

使用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.so

macOS

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

Windows

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动作的组合

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

ros2 launch composition composition_demo.launch.py

高级主题

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

卸载组件

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

ros2 run rclcpp_components component_container

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

ros2 component list

你应该会看到组件名称:

/ComponentManager

在第二个终端中(见talker源代码),加载组件:

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

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

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

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

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

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

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

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

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

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

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

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 Discourse - Ament最佳实践:共享库

组合非节点派生的组件

在ROS 2中,组件允许更高效地使用系统资源,并提供了一个强大的特性,使你能够创建不依赖于特定节点的可重用功能。

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

要创建一个非节点派生的组件,请遵循以下指南:

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

以下是一个非节点派生组件的示例,它监听一个ROS话题:node_like_listener_component。
https://github.com/ros2/demos/blob/foxy/composition/src/node_like_listener_component.cpp

<think> 首先,用户的问题是:“我想查询ROS2是否可以在一个主函数中创建多个节点。请问ROS2能否在一个主函数中创建多个节点?” 我需要参考用户提供的引用: - 引用[1]:关于ROS2节点的基本信息,比如如何使用`ros2 node list`命令查看节点。这与创建节点间接相关。 - 引用[2]:展示了在ROS1中创建一个节点的C++代码示例(`node2.cpp`),其中使用了`ros::init`和`ros::NodeHandle`。注意,这是ROS1的代码,因为用的是`ros::`命名空间而不是`rclcpp::`或其他ROS2的。 - 引用[3]:关于ROS2 Jazzy中在单个进程组合多个节点,使用`ros2 component load`命令传递选项如`use_intra_process_comms`。 - 引用[4]:类似引用[3],关于在单个进程组合多个节点,使用命令行加载组件并传递参数。 用户的问题是针对ROS2的。引用[2]是ROS1的,但引用[3]和[4]直接涉及ROS2单个进程中处理多个节点。 关键点:用户问的是“在一个主函数中创建多个节点”,意思是用户想在一个C++程序(主函数)中启动多个节点实例。 在ROS2中,节点是基本的执行单元。通常,每个节点有自己的主函数,但ROS2支持在单个进程组合多个节点,以提高效率和减少资源使用。这被称为“组合节点”或“组件”。 根据引用[3]和[4]: - 引用[3]提到“在单个进程组合多个节点”,使用`ros2 component load`命令。 - 引用[4]也讨论了“在单个进程组合多个节点”,并展示了命令行用法。 在ROS2中,你可以使用“组件”模型在一个进程中创建多个节点。这可以通过命令行工具(如`ros2 component load`)或直接在代码中实现。 ROS2提供了`rclcpp::Node`类,你可以在一个应用程序中创建多个实例。具体来说: - 在一个主函数中,你可以创建多个`rclcpp::Node`对象,每个代表一个节点- 但是,节点需要在ROS2上下文(Context)中初始化。通常,使用`rclcpp::init`初始化上下文,然后创建节点-同一个进程中,可以有多个节点共享同一个上下文。 所以,是的,ROS2允许在一个主函数中创建多个节点。 例如,在C++中: ```cpp #include "rclcpp/rclcpp.hpp" int main(int argc, char * argv[]) { rclcpp::init(argc, argv); auto node1 = std::make_shared<rclcpp::Node>("node1"); auto node2 = std::make_shared<rclcpp::Node>("node2"); rclcpp::spin(node1); // Or use an executor to spin multiple nodes rclcpp::shutdown(); return 0; } ``` 在上面的代码中,我创建了两个节点(node1和node2),但在使用`rclcpp::spin`时,它只针对一个节点。要同时运行多个节点,你需要使用一个执行器(Executor),如`SingleThreadedExecutor`或`MultiThreadedExecutor`,来添加节点并运行它们。 正确的方式是使用执行器: ```cpp #include "rclcpp/rclcpp.hpp" int main(int argc, char * argv[]) { rclcpp::init(argc, argv); auto node1 = std::make_shared<rclcpp::Node>("node1"); auto node2 = std::make_shared<rclcpp::Node>("node2"); rclcpp::executors::SingleThreadedExecutor executor; executor.add_node(node1); executor.add_node(node2); executor.spin(); rclcpp::shutdown(); return 0; } ``` 这样,两个节点就在同一个进程中被创建和执行了。 此外,引用[3]和[4]提到的“组合节点”或“组件”模型是基于ROS2的组件系统,它允许动态加载和组合节点,但核心概念相同:在单个进程中运行多个节点。 因此,答案是肯定的:ROS2可以在一个主函数中创建多个节点。 现在,关于回答结构: - 系统指令要求:使用中文回答,结构清晰,帮助用户逐步解决问题。 - 另外,需要生成相关问题(§§相关问题§§),并在回答末尾添加。 - 数学表达式使用$...$或$$...$$,但这个问题不涉及数学,所以可能不需要。 - 引用的段落末尾添加引用标识。 我的回答应该: 1. 直接回答问题:是的,可以。 2. 解释如何实现:简要描述在代码中创建多个节点并使用执行器。 3. 参考提供的引用:提及引用[3]和[4],因为它们直接相关,而引用[2]是ROS1的,不那么相关。 4. 生成相关问题。 最终回答结构: - 开头明确回答。 - 解释机制。 - 提供简单代码示例(如果需要,但用户问题主要是确认是否可以)。 - 添加引用。 - 生成相关问题。 引用添加:在相关句子末尾,如“在单个进程组合多个节点[^3][^4]”。 相关问题:基于用户的问题,生成一些相关问题,如ROS2组件的用法、执行器的类型等。 </think> 是的,ROS2 允许在一个主函数中创建多个节点。这是通过 ROS2 的“组合节点”(Composable Nodes)机制实现的,它可以显著提高系统性能和资源利用率,尤其在资源受限的嵌入式系统中。下面我将逐步解释其工作原理和实现方式。 ### 1. **ROS2 节点创建的基本原理** -ROS2 中,每个节点都是一个独立的执行单元,通常有自己的生命周期和线程。 - 通过 `rclcpp` 库(ROS2 的 C++ 客户端库),您可以在单个进程中初始化多个节点实例。 - 关键组件包括: - **ROS2 上下文(Context)**:使用 `rclcpp::init()` 初始化全局上下文。 - **执行器(Executor)**:用于管理多个节点的执行(如 `SingleThreadedExecutor` 或 `MultiThreadedExecutor`),确保节点能并发运行。 - **节点对象**:通过 `rclcpp::Node` 类创建节点实例。 - 这种方法避免了传统 ROS1 中每个节点需要独立进程的限制,从而减少了通信开销和内存占用[^3][^4]。 ### 2. **如何在一个主函数中创建多个节点** 以下是一个简单的 C++ 代码示例,演示了如何在一个主函数中创建并运行两个节点(`node1` 和 `node2`)。代码使用了 ROS2 的 `rclcpp` 库: ```cpp #include "rclcpp/rclcpp.hpp" int main(int argc, char * argv[]) { // 初始化 ROS2 上下文 rclcpp::init(argc, argv); // 创建两个节点对象 auto node1 = std::make_shared<rclcpp::Node>("node1"); auto node2 = std::make_shared<rclcpp::Node>("node2"); // 使用单线程执行器管理多个节点 rclcpp::executors::SingleThreadedExecutor executor; executor.add_node(node1); executor.add_node(node2); // 运行所有节点 executor.spin(); // 关闭 ROS2 上下文 rclcpp::shutdown(); return 0; } ``` - **关键点解释**: - `rclcpp::init()` 初始化全局上下文,确保所有节点共享同一个 ROS2 环境。 - `executor.add_node()` 将节点添加到执行器队列,允许它们在同一个线程中交替执行(或使用多线程执行器并行执行)。 - `executor.spin()` 启动事件循环,处理节点的订阅、服务和定时器等。 - 当运行此程序时,您可以使用 `ros2 node list` 命令查看所有活动节点(如 `node1` 和 `node2`)[^1]。 ### 3. **ROS2 组件模型的高级支持** - ROS2 提供了“组件”(Component)机制来简化多个节点组合。这允许动态加载和管理节点,而无需手动编写执行器逻辑。 - **命令行方式**:使用 `ros2 component load` 加载组件并传递参数。例如: ```bash ros2 component load /ComponentManager image_tools image_tools::Cam2Image -p burger_mode:=true ``` 这会在单个进程中加载多个节点组件[^3][^4]。 - **代码方式**:您可以将节点封装为组件类(继承自 `rclcpp::Node`),然后使用 `rclcpp_components` 工具注册和加载它们。这支持参数传递和进程内通信优化(如 `use_intra_process_comms`)[^3]。 - **优势**: - **性能提升**:节点间通过进程内通信(IPC)传输数据,避免了网络延迟[^3]。 - **灵活性**:可以在运行时动态添加或移除节点,适用于模块化系统设计[^4]。 ### 4. **注意事项** - **节点命名**:确保每个节点有唯一的名称,避免冲突(如 `"node1"` 和 `"node2"`)。 - **线程安全**:如果节点涉及高频率操作,建议使用 `MultiThreadedExecutor` 替代 `SingleThreadedExecutor`。 - **ROS1 与 ROS2 区别**:ROS1(如引用[2]所示)通常需要每个节点独立进程,而 ROS2 支持更灵活的进程组合[^2][^3]。 - **资源管理**:在单个进程中运行多个节点时,需监控资源使用,以防单个节点故障影响整体进程。 总之,ROS2 不仅支持在一个主函数中创建多个节点,还通过组件模型提供了高效的工具链。这在机器人系统的传感器融合、实时控制等场景中广泛应用[^3][^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ray.so

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值