【ROS2知识】如何管理大项目
迪丽瓦拉
2025-05-29 05:54:16
0

一、说明

        在ROS开发中,难免出现大规模的节点,如何管理这些节点,而构造出成规模的项目?且看ROS2的开发团队的建议。

        本教程介绍了为大型项目编写启动文件的一些技巧。重点是如何构建启动文件,以便它们可以在不同情况下尽可能多地重复使用。此外,它还涵盖了不同 ROS 2 启动工具的使用示例,例如参数、YAML 文件、重新映射、命名空间、默认参数和 RViz 配置。

二、大项目如何管理

2.1 先决条件

        本教程使用 turtlesim 和 turtle_tf2_py 包。本教程还假设您已经创建了一个名为 launch_tutorial 的构建类型为 ament_python 的新包。

2.2 介绍

        机器人上的大型应用程序通常涉及多个互连节点,每个节点都可以有很多参数。在海龟模拟器中模拟多只海龟可以作为一个很好的例子。海龟模拟由多个海龟节点、世界配置以及 TF 广播器和侦听器节点组成。在所有节点之间,存在大量影响这些节点的行为和外观的 ROS 参数。 ROS 2 启动文件允许我们启动所有节点并在一个地方设置相应的参数。在教程结束时,您将在 launch_tutorial 包中构建 launch_turtlesim.launch.py​​ 启动文件。这个启动文件将调出负责模拟两个 turtlesim 模拟的不同节点,启动 TF 广播器和监听器,加载参数,并启动 RViz 配置。在本教程中,我们将介绍此启动文件和使用的所有相关功能。

2.3 写launch文件

1)顶层管理

        编写启动文件过程中的目标之一应该是使它们尽可能可重用。这可以通过将相关节点和配置集群到单独的启动文件中来完成。之后,可以编写专用于特定配置的顶级启动文件。这将允许在完全不更改启动文件的情况下在相同的机器人之间移动。即使是从真实机器人转移到模拟机器人这样的变化,也只需进行少量更改即可完成。

        我们现在将讨论使这成为可能的顶级启动文件结构。首先,我们将创建一个启动文件,它将调用单独的启动文件。为此,让我们在 launch_tutorial 包的 /launch 文件夹中创建一个 launch_turtlesim.launch.py​​ 文件。

import osfrom ament_index_python.packages import get_package_share_directoryfrom launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSourcedef generate_launch_description():turtlesim_world_1 = IncludeLaunchDescription(PythonLaunchDescriptionSource([os.path.join(get_package_share_directory('launch_tutorial'), 'launch'),'/turtlesim_world_1.launch.py']))turtlesim_world_2 = IncludeLaunchDescription(PythonLaunchDescriptionSource([os.path.join(get_package_share_directory('launch_tutorial'), 'launch'),'/turtlesim_world_2.launch.py']))broadcaster_listener_nodes = IncludeLaunchDescription(PythonLaunchDescriptionSource([os.path.join(get_package_share_directory('launch_tutorial'), 'launch'),'/broadcaster_listener.launch.py']),launch_arguments={'target_frame': 'carrot1'}.items(),)mimic_node = IncludeLaunchDescription(PythonLaunchDescriptionSource([os.path.join(get_package_share_directory('launch_tutorial'), 'launch'),'/mimic.launch.py']))fixed_frame_node = IncludeLaunchDescription(PythonLaunchDescriptionSource([os.path.join(get_package_share_directory('launch_tutorial'), 'launch'),'/fixed_broadcaster.launch.py']))rviz_node = IncludeLaunchDescription(PythonLaunchDescriptionSource([os.path.join(get_package_share_directory('launch_tutorial'), 'launch'),'/turtlesim_rviz.launch.py']))return LaunchDescription([turtlesim_world_1,turtlesim_world_2,broadcaster_listener_nodes,mimic_node,fixed_frame_node,rviz_node])

此启动文件包括一组其他启动文件。这些包含的启动文件中的每一个都包含节点、参数,并且可能还包含嵌套的包含,这些包含属于系统的一部分。确切地说,我们启动了两个 turtlesim 模拟世界,TF broadcaster、TF listener、mimic、fixed frame broadcaster 和 RViz 节点。

注意1

设计提示:顶层启动文件应该简短,包含与应用程序子组件对应的其他文件的包含,以及经常更改的参数。

以下列方式编写启动文件可以很容易地换出系统的一部分,我们稍后会看到。但是,有时由于性能和使用原因,某些节点或启动文件必须单独启动。

注意2

设计提示:在决定您的应用程序需要多少顶级启动文件时,请注意权衡。

二、参数

2.1 在launch文件中设置参数

        我们将从编写一个启动文件开始我们的第一个 turtlesim 模拟。首先,创建一个名为 turtlesim_world_1.launch.py​​ 的新文件。

        我们将从编写一个启动文件开始我们的第一个 turtlesim 模拟。首先,创建一个名为 turtlesim_world_1.launch.py​​ 的新文件。

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration, TextSubstitutionfrom launch_ros.actions import Nodedef generate_launch_description():background_r_launch_arg = DeclareLaunchArgument('background_r', default_value=TextSubstitution(text='0'))background_g_launch_arg = DeclareLaunchArgument('background_g', default_value=TextSubstitution(text='84'))background_b_launch_arg = DeclareLaunchArgument('background_b', default_value=TextSubstitution(text='122'))return LaunchDescription([background_r_launch_arg,background_g_launch_arg,background_b_launch_arg,Node(package='turtlesim',executable='turtlesim_node',name='sim',parameters=[{'background_r': LaunchConfiguration('background_r'),'background_g': LaunchConfiguration('background_g'),'background_b': LaunchConfiguration('background_b'),}]),])

        此启动文件启动 turtlesim_node 节点,该节点启动 turtlesim 模拟,具有定义并传递给节点的模拟配置参数。

2.2 从YAML文件载入参数

        在第二次启动时,我们将使用不同的配置启动第二个 turtlesim 模拟。现在创建一个 turtlesim_world_2.launch.py​​ 文件。

import osfrom ament_index_python.packages import get_package_share_directoryfrom launch import LaunchDescription
from launch_ros.actions import Nodedef generate_launch_description():config = os.path.join(get_package_share_directory('launch_tutorial'),'config','turtlesim.yaml')return LaunchDescription([Node(package='turtlesim',executable='turtlesim_node',namespace='turtlesim2',name='sim',parameters=[config])])

        此启动文件将使用直接从 YAML 配置文件加载的参数值启动相同的 turtlesim_node。在 YAML 文件中定义参数和参数可以方便地存储和加载大量变量。此外,YAML 文件可以很容易地从当前的 ros2 参数列表中导出。要了解如何执行此操作,请参阅了解参数教程。

        现在让我们在包的 /config 文件夹中创建一个配置文件 turtlesim.yaml,它将由我们的启动文件加载。

/turtlesim2/sim:ros__parameters:background_b: 255background_g: 86background_r: 150
If we now start the turtlesim_world_2.launch.py launch file, we will start the turtlesim_node with preconfigured background colors.

        要了解有关使用参数和使用 YAML 文件的更多信息,请查看了解参数教程。

2.3  在 YAML 文件中使用通配符

        有时我们想在多个节点中设置相同的参数。这些节点可以有不同的名称空间或名称,但仍具有相同的参数。定义明确定义命名空间和节点名称的单独 YAML 文件效率不高。一种解决方案是使用通配符来替代文本值中的未知字符,以将参数应用于多个不同的节点。

        现在让我们创建一个类似于 turtlesim_world_2.launch.py​​ 的新 turtlesim_world_3.launch.py​​ 文件以包含一个 turtlesim_node 节点。

...
Node(package='turtlesim',executable='turtlesim_node',namespace='turtlesim3',name='sim',parameters=[config]
)

        然而,加载相同的 YAML 文件不会影响第三个 turtlesim 世界的外观。原因是它的参数存储在另一个命名空间下,如下所示:

/turtlesim3/sim:background_bbackground_gbackground_r

        因此,我们可以使用通配符语法,而不是为使用相同参数的同一节点创建新配置。/**
        将在每个节点中分配所有参数,尽管节点名称和名称空间不同。我们现在将按以下方式更新 /config 文件夹中的 turtlesim.yaml:

/**:ros__parameters:background_b: 255background_g: 86background_r: 150

        现在在我们的主启动文件中包含 turtlesim_world_3.launch.py​​ 启动描述。在我们的启动描述中使用该配置文件会将 background_b、background_g 和 background_r 参数分配给 turtlesim3/sim 和 turtlesim2/sim 节点中的指定值。

三、命名空间

        您可能已经注意到,我们在 turtlesim_world_2.launch.py​​ 文件中定义了 turlesim 世界的命名空间。独特的命名空间允许系统启动两个相似的节点,而不会出现节点名称或主题名称冲突。

    namespace='turtlesim2',

        但是,如果启动文件包含大量节点,则为每个节点定义命名空间可能会变得乏味。为解决该问题,可以使用 PushRosNamespace 操作为每个启动文件描述定义全局命名空间。每个嵌套节点都将自动继承该命名空间。

        为此,首先,我们需要从 turtlesim_world_2.launch.py​​ 文件中删除 namespace='turtlesim2' 行。之后,我们需要更新 launch_turtlesim.launch.py​​ 以包含以下几行:

from launch.actions import GroupAction
from launch_ros.actions import PushRosNamespace...turtlesim_world_2 = IncludeLaunchDescription(PythonLaunchDescriptionSource([os.path.join(get_package_share_directory('launch_tutorial'), 'launch'),'/turtlesim_world_2.launch.py']))turtlesim_world_2_with_namespace = GroupAction(actions=[PushRosNamespace('turtlesim2'),turtlesim_world_2,])

        最后,我们将 return LaunchDescription 语句中的 turtlesim_world_2 替换为 turtlesim_world_2_with_namespace。

        因此,turtlesim_world_2.launch.py​​ 启动描述中的每个节点都将有一个 turtlesim2 命名空间。

四、节点重用

        现在创建一个 broadcaster_listener.launch.py​​ 文件。

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfigurationfrom launch_ros.actions import Nodedef generate_launch_description():return LaunchDescription([DeclareLaunchArgument('target_frame', default_value='turtle1',description='Target frame name.'),Node(package='turtle_tf2_py',executable='turtle_tf2_broadcaster',name='broadcaster1',parameters=[{'turtlename': 'turtle1'}]),Node(package='turtle_tf2_py',executable='turtle_tf2_broadcaster',name='broadcaster2',parameters=[{'turtlename': 'turtle2'}]),Node(package='turtle_tf2_py',executable='turtle_tf2_listener',name='listener',parameters=[{'target_frame': LaunchConfiguration('target_frame')}]),])

        在此文件中,我们声明了 target_frame 启动参数,默认值为 turtle1。默认值意味着启动文件可以接收参数以转发到其节点,或者在未提供参数的情况下,它会将默认值传递到其节点。

        之后,我们在启动期间使用不同的名称和参数两次使用 turtle_tf2_broadcaster 节点。这允许我们复制相同的节点而不会发生冲突。

        我们还启动了一个 turtle_tf2_listener 节点并设置我们在上面声明和获取的 target_frame 参数。

五、参数超载

        回想一下,我们在顶级启动文件中调用了 broadcaster_listener.launch.py​​ 文件。除此之外,我们还向它传递了 target_frame 启动参数,如下所示:

broadcaster_listener_nodes = IncludeLaunchDescription(PythonLaunchDescriptionSource([os.path.join(get_package_share_directory('launch_tutorial'), 'launch'),'/broadcaster_listener.launch.py']),launch_arguments={'target_frame': 'carrot1'}.items(),)

        此语法允许我们将默认目标目标框架更改为 carrot1。如果您希望 turtle2 跟随 turtle1 而不是 carrot1,只需删除定义 launch_arguments 的行。这将为 target_frame 分配其默认值,即 turtle1。

六、重映射

        现在创建一个 mimic.launch.py​​ 文件。

from launch import LaunchDescription
from launch_ros.actions import Nodedef generate_launch_description():return LaunchDescription([Node(package='turtlesim',executable='mimic',name='mimic',remappings=[('/input/pose', '/turtle2/pose'),('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'),])])

        此启动文件将启动模拟节点,它将向一个海龟发出命令以跟随另一个。该节点旨在接收关于主题 /input/pose 的目标姿势。在我们的例子中,我们想要从 /turtle2/pose 主题重新映射目标姿势。最后,我们将 /output/cmd_vel 主题重新映射到 /turtlesim2/turtle1/cmd_vel。这样,我们的 turtlesim2 模拟世界中的 turtle1 将跟随我们初始 turtlesim 世界中的 turtle2。

七、配置文件

        现在让我们创建一个名为 turtlesim_rviz.launch.py​​ 的文件。

import osfrom ament_index_python.packages import get_package_share_directoryfrom launch import LaunchDescription
from launch_ros.actions import Nodedef generate_launch_description():rviz_config = os.path.join(get_package_share_directory('turtle_tf2_py'),'rviz','turtle_rviz.rviz')return LaunchDescription([Node(package='rviz2',executable='rviz2',name='rviz2',arguments=['-d', rviz_config])])

        此启动文件将使用 turtle_tf2_py 包中定义的配置文件启动 RViz。此 RViz 配置将设置世界坐标系,启用 TF 可视化,并以自上而下的视图启动 RViz。

八、环境变量

        现在让我们在我们的包中创建最后一个名为 fixed_broadcaster.launch.py​​ 的启动文件。

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import EnvironmentVariable, LaunchConfiguration
from launch_ros.actions import Nodedef generate_launch_description():return LaunchDescription([DeclareLaunchArgument('node_prefix',default_value=[EnvironmentVariable('USER'), '_'],description='prefix for node name'),Node(package='turtle_tf2_py',executable='fixed_frame_tf2_broadcaster',name=[LaunchConfiguration('node_prefix'), 'fixed_broadcaster'],),])

        此启动文件显示了在启动文件中调用环境变量的方式。环境变量可用于定义或推送命名空间,以区分不同计算机或机器人上的节点。

九、运行launch文件

9.1 修改setup.py 

        打开 setup.py 并添加以下行,以便安装 launch/ 文件夹中的启动文件和 config/ 中的配置文件。 data_files 字段现在应如下所示:

data_files=[...(os.path.join('share', package_name, 'launch'),glob(os.path.join('launch', '*.launch.py'))),(os.path.join('share', package_name, 'config'),glob(os.path.join('config', '*.yaml'))),],

9.2 编译并执行

        要最终查看代码的结果,请使用以下命令构建包并启动顶级启动文件:

    ros2 launch launch_tutorial launch_turtlesim.launch.py

        您现在将看到两个 turtlesim 模拟已启动。第一个有两只乌龟,第二个有一只。在第一个模拟中,turtle2 生成在世界的左下角。它的目标是到达相对于 turtle1 框架在 x 轴上五米远的 carrot1 框架。

        第二个中的 turtlesim2/turtle1 旨在模仿 turtle2 的行为。

        如果您想控制 turtle1,请运行 teleop 节点。

    ros2 run turtlesim turtle_teleop_key

        结果,您将看到类似的图片:

        除此之外,RViz 应该已经启动。它将显示相对于原点位于左下角的世界坐标系的所有海龟坐标系。

小结

        在本教程中,您了解了使用 ROS 2 启动文件管理大型项目的各种技巧和实践。

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...