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

 目录

  •  背景

  •  先决条件

  •  运行演示

    • 发现可用组件

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

    • 运行时使用 ROS 服务的服务器和客户端组合

    • 编译时与硬编码节点组合

    • 运行时使用 dlopen 进行组合

    • 使用启动动作的组合

  •  高级主题

    •  卸载组件

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

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

    • 传递参数值到组件中

    • 向组件传递额外的参数

  • 可组合节点作为共享库

  • 组成非节点派生组件

目标:将多个节点组合成单一进程。

教程级别:中级

 时间:20 分钟

 背景

查看概念性文章 https://docs.ros.org/en/jazzy/Concepts/Intermediate/About-Composition.html 。

有关如何编写可组合节点的信息,请查看本教程 https://docs.ros.org/en/jazzy/Tutorials/Intermediate/Writing-a-Composable-Node.html 。

 先决条件

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

运行演示

发现可用组件 

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

ros2 component types

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

cxy@ubuntu2404-cxy:~/ros2_ws$ ros2 component types
rosbag2_transport
  rosbag2_transport::Player
  rosbag2_transport::Recorder
robot_state_publisher
  robot_state_publisher::RobotStatePublisher
tf2_ros
  tf2_ros::StaticTransformBroadcasterNode
quality_of_service_demo_cpp
  quality_of_service_demo::MessageLostListener
  quality_of_service_demo::MessageLostTalker
  quality_of_service_demo::QosOverridesListener
  quality_of_service_demo::QosOverridesTalker
point_cloud_transport
  point_cloud_transport::Republisher
  point_cloud_transport::Republisher
image_transport
  image_transport::Republisher
image_tools
  image_tools::Cam2Image
  image_tools::ShowImage
logging_demo
  logging_demo::LoggerConfig
  logging_demo::LoggerUsage
examples_rclcpp_wait_set
  Talker
  Listener
examples_rclcpp_minimal_subscriber
  WaitSetSubscriber
  StaticWaitSetSubscriber
  TimeTriggeredWaitSetSubscriber
demo_nodes_cpp_native
  demo_nodes_cpp_native::Talker
composition
  composition::Talker
  composition::Listener
  composition::NodeLikeListener
  composition::Server
  composition::Client
action_tutorials_cpp
  action_tutorials_cpp::FibonacciActionClient
  action_tutorials_cpp::FibonacciActionServer

使用 ROS 服务进行运行时组合,包括一个发布者和一个订阅者

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

ros2 run rclcpp_components component_container

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

ros2 component list

您应该看到组件的名称:

/ComponentManager

在第二个 shell 中加载 talker 组件(参见 talker 源代码 https://github.com/ros2/demos/blob/jazzy/composition/src/talker_component.cpp ):

ros2 component load /ComponentManager composition composition::Talker

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

cxy@ubuntu2404-cxy:~/ros2_ws$ ros2 component load /ComponentManager composition composition::Talker
Loaded component 1 into '/ComponentManager' container node as '/talker'

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

67ec9a68bbd84d187827ea08240d4f73.png

在第二个 shell 中运行另一个命令以加载监听器组件(参见监听器 https://github.com/ros2/demos/blob/jazzy/composition/src/listener_component.cpp 源代码):

 终端将返回:

cxy@ubuntu2404-cxy:~/ros2_ws$ ros2 component load /ComponentManager composition composition::Listener
Loaded component 2 into '/ComponentManager' container node as '/listener'

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

cxy@ubuntu2404-cxy:~/ros2_ws$ ros2 component list
/ComponentManager
  1  /talker
  2  /listener

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

8c9a8aa42a7c36c1e3fe7e9a0626bcad.png

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

示例中的服务器和客户端非常相似。

在第一个shell中:

ros2 run rclcpp_components component_container

在第二个壳中(见服务器https://github.com/ros2/demos/blob/jazzy/composition/src/server_component.cpp 和客户端https://github.com/ros2/demos/blob/jazzy/composition/src/client_component.cpp 源代码):

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

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

编译时与硬编码节点的组合

这个演示表明,相同的共享库可以重复使用,以编译一个单一的可执行文件,该文件运行多个组件,而不使用 ROS 接口。该可执行文件包含上述所有四个组件:讲述者和监听者以及服务器和客户端,这些都在主函数中硬编码。

在 shell 调用中(见源代码https://github.com/ros2/demos/blob/jazzy/composition/src/manual_composition.cpp ):

ros2 run composition manual_composition

这应该显示两对之间的重复消息,即说话者和听者以及服务器和客户端。

 便条

手动编写的组件不会反映在 ros2 component list 命令行工具输出中。

运行时使用 dlopen 进行组合

这个演示提供了一个运行时组合的替代方案,通过创建一个通用容器进程并显式传递要加载的库而不使用 ROS 接口。该过程将打开每个库并在库中创建一个“rclcpp::Node”类的实例(源代码 https://github.com/ros2/demos/blob/jazzy/composition/src/dlopen_composition.cpp )。

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

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

 便条

使用 dlopen 组合的组件不会在 ros2 component list 命令行工具输出中反映。

使用启动动作的组合

命令行工具对于调试和诊断组件配置很有用,但通常同时启动一组组件更为方便。为了自动化这个操作,我们可以使用一个启动文件https://github.com/ros2/demos/blob/jazzy/composition/launch/composition_demo_launch.py :

ros2 launch composition composition_demo_launch.py
# 版权归2019年开源机器人基金会所有。
#
# 根据Apache License 2.0("许可")授权;
# 除非遵守许可,否则您不得使用此文件。
# 您可以在以下位置获得许可的副本:
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# 除非适用法律要求或书面同意,否则根据许可分发的软件
# 将按“原样”基础提供,不附带任何形式的保证或条件。
# 请参阅许可证以了解管理权限和限制的规定。


"""在组件容器中启动一个talker和一个listener。"""


import launch  # 导入launch模块,用于启动和管理ROS节点。
from launch_ros.actions import ComposableNodeContainer  # 从launch_ros.actions导入ComposableNodeContainer类。
from launch_ros.descriptions import ComposableNode  # 从launch_ros.descriptions导入ComposableNode类。




def generate_launch_description():  # 定义一个函数,用于生成启动描述。
    """生成具有多个组件的启动描述。"""
    container = ComposableNodeContainer(  # 创建一个ComposableNodeContainer实例。
            name='my_container',  # 容器的名称。
            namespace='',  # 容器的命名空间。
            package='rclcpp_components',  # 容器所属的包。
            executable='component_container',  # 容器的可执行文件。
            composable_node_descriptions=[  # 容器中包含的组件节点描述列表。
                ComposableNode(  # 创建一个ComposableNode实例,代表一个talker组件。
                    package='composition',  # 组件所属的包。
                    plugin='composition::Talker',  # 组件的插件名称。
                    name='talker'),  # 组件的名称。
                ComposableNode(  # 创建另一个ComposableNode实例,代表一个listener组件。
                    package='composition',  # 组件所属的包。
                    plugin='composition::Listener',  # 组件的插件名称。
                    name='listener')  # 组件的名称。
            ],
            output='screen',  # 输出到屏幕。
    )


    return launch.LaunchDescription([container])  # 返回一个LaunchDescription实例,包含上面定义的容器。

 高级主题 

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

 卸载组件

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

ros2 run rclcpp_components component_container

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

ros2 component list

您应该看到组件的名称:

/ComponentManager

在第二个 shell 中,像以前一样加载 talker 和 listener:

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

使用唯一标识符从组件容器中卸载节点。

ros2 component unload /ComponentManager 1 2

终端应该返回:

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

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

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

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

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

6a1fe14a440ad2cf4b87a5c1abc02a59.png

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

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

d664a55696d54a60ef2f600b8e69c840.png

 便条

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

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

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

在第一个 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 /ComponentManager image_tools image_tools::Cam2Image -p burger_mode:=true

508439e08f41df1912fb985c0a7b05f7.png

将额外的参数传递到组件中

命令行支持向组件管理器传递特定选项,用于构建节点。目前,唯一支持的命令行选项是使用进程内通信来实例化一个节点。此功能可按以下方式使用:

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

59631d0d361456baad579e0c6fa6382e.png

可组合节点作为共享库

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

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

实际的例子可以在这里看到:ROS 论坛 - Ament 共享库的最佳实践https://discourse.ros.org/t/ament-best-practice-for-sharing-libraries/3602

组成非节点派生组件

在 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/jazzy/composition/src/node_like_listener_component.cpp

有关此主题的更多信息,您可以参考这次讨论。https://github.com/ros2/rclcpp/issues/2110#issuecomment-1454228192

<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
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值