目录
背景
先决条件
运行演示
发现可用组件
运行时使用 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 应该显示组件已加载的消息,以及发布消息的重复消息。
在第二个 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 应该会显示每个收到的消息的重复输出。
运行时使用 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
在第二个 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 /ComponentManager image_tools image_tools::Cam2Image -p burger_mode:=true
将额外的参数传递到组件中
命令行支持向组件管理器传递特定选项,用于构建节点。目前,唯一支持的命令行选项是使用进程内通信来实例化一个节点。此功能可按以下方式使用:
ros2 component load /ComponentManager composition composition::Talker -e use_intra_process_comms:=true
可组合节点作为共享库
如果您想要从一个包中导出一个可组合节点作为共享库,并在另一个执行链接时组合的包中使用该节点,请添加代码到 CMake 文件中,以导入下游包中的实际目标。
然后安装生成的文件并导出生成的文件。
实际的例子可以在这里看到:ROS 论坛 - Ament 共享库的最佳实践https://discourse.ros.org/t/ament-best-practice-for-sharing-libraries/3602
组成非节点派生组件
在 ROS 2 中,组件允许更有效地使用系统资源,并提供了一个强大的功能,使您能够创建不依赖于特定节点的可重用功能。
使用组件的一个优势是它们允许您创建非节点派生的功能,作为独立可执行文件或共享库,可以根据需要加载到 ROS 系统中。
要创建一个不是从节点派生的组件,请遵循以下指南:
实现一个构造函数,它将
const rclcpp::NodeOptions&
作为其参数。实现
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