

上一篇文章详细介绍了simulation.launch.py的内容,接下来我们进一步了解launch_setup函数中的代码:
dual_ur5e_gripper_control_launch = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
[FindPackageShare("ur5e_gripper_moveit_config"), "/launch", "/ur5e_gripper_sim_control.launch.py"]
),
launch_arguments={
"launch_rviz": "true",
}.items(),
)python可以看到这个代码调用了ur5e_gripper_moveit_config包下的launch目录下的ur5e_gripper_sim_control.launch.py文件,并传入参数launch_rviz,这个代码的作用是加载URDF模型并启动Gazebo仿真环境。
注意,这里使用了FindPackageShare。在ROS 2中,FindPackageShare 函数会
- 首先查找已安装的包(在install目录中)
- 然后才会查找系统安装的包(如/opt/ros/humble/share/)
- 不会直接查找src目录中的源文件
接下来,我们查看ur5e_gripper_sim_control.launch.py的源代码(路径为/home/whisper/ros2_ws/install/ur5e_gripper_moveit_config/share/ur5e_gripper_moveit_config/launch/ur5e_gripper_sim_control.launch.py,但是代码内容与src目录下是一样的):
第一步,先看generate_launch_description函数#
def generate_launch_description():
"""
生成launch描述,定义launch文件的参数和入口点
该函数声明所有launch参数并返回完整的LaunchDescription对象,
包含参数声明和实际的launch设置函数。
返回:
LaunchDescription: 包含所有launch参数和设置函数的描述对象
"""
declared_arguments = []
## 通用参数
declared_arguments.append(
DeclareLaunchArgument(
"description_package",
default_value="ur5e_gripper_moveit_config",
description="包含机器人URDF/XACRO文件的描述包。通常不设置该参数,\
它允许使用自定义描述。",
)
)
declared_arguments.append(
DeclareLaunchArgument(
"description_file",
default_value="ur5e_gripper.urdf.xacro",
description="包含机器人的URDF/XACRO描述文件。",
)
)
declared_arguments.append(
DeclareLaunchArgument(
"prefix",
default_value='""',
description="关节名称前缀,对多机器人设置很有用。\
如果更改了此参数,则控制器配置中的关节名称也必须更新。",
)
)
declared_arguments.append(
DeclareLaunchArgument(
"start_joint_controller",
default_value="true",
description="为机器人控制启用无头模式",
)
)
declared_arguments.append(
DeclareLaunchArgument(
"initial_joint_controller",
default_value="ur5e_arm_controller",
description="要启动的机器人控制器。",
)
)
declared_arguments.append(
DeclareLaunchArgument("launch_rviz", default_value="False", description="是否启动RViz?")
)
print(f"\033[95mdeclared_arguments:{declared_arguments}\033[0m")
return LaunchDescription(declared_arguments + [OpaqueFunction(function=launch_setup)])
python- 函数首先创建一个空列表declared_arguments,用于存储所有要声明的launch参数。这些参数可以在运行launch文件时通过命令行进行配置。
- 我打印了declared_arguments内容如下:
可以理解为generate_launch_description函数为launch_setup函数指定了需要加载的参数文件
第二步,看launch_setup函数#
代码如下:
def launch_setup(context, *args, **kwargs):
"""
设置并启动UR5e机器人与夹爪的Gazebo仿真环境
该函数配置机器人描述、控制器、可视化工具和Gazebo仿真环境,
并按正确的依赖顺序启动所有必需的节点。
参数:
context: Launch上下文
*args: 额外的位置参数
**kwargs: 额外的关键字参数
返回:
list: 需要启动的节点列表
"""
## 通用参数
description_package = LaunchConfiguration("description_package")
description_file = LaunchConfiguration("description_file")
launch_rviz = LaunchConfiguration("launch_rviz")
gazebo_world_file = os.path.join(
FindPackageShare(package='ur5e_gripper_moveit_config').find('ur5e_gripper_moveit_config'),
'gazebo','sim_env.world'
)
## 打印LaunchConfiguration的实际值
print(f"\033[95mdescription_package: {description_package.perform(context)}\033[0m")
print(f"\033[95mdescription_file: {description_file.perform(context)}\033[0m")
print(f"\033[95mlaunch_rviz: {launch_rviz.perform(context)}\033[0m")
print(f"\033[95mgazebo_world_file: {gazebo_world_file}\033[0m")
## RViz配置文件路径
rviz_config_file = PathJoinSubstitution(
[FindPackageShare(description_package), "rviz", "view_robot.rviz"]
)
## 机器人描述内容(通过xacro处理URDF文件生成)
robot_description_content = Command(
[
PathJoinSubstitution([FindExecutable(name="xacro")]),
" ",
PathJoinSubstitution(
[FindPackageShare(description_package), "urdf", description_file]
),
]
)
robot_description = {"robot_description": robot_description_content}
## 机器人状态发布节点
robot_state_publisher_node = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
output="both",
parameters=[{"use_sim_time": True}, robot_description],
)
## RViz可视化节点(根据launch_rviz参数决定是否启动)
rviz_node = Node(
package="rviz2",
executable="rviz2",
name="rviz2",
output="log",
arguments=["-d", rviz_config_file],
condition=IfCondition(launch_rviz),
)
## 关节状态广播器
joint_state_broadcaster_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["joint_state_broadcaster", "--controller-manager", "/controller_manager"],
)
## 在关节状态广播器启动后再启动RViz
delay_rviz_after_joint_state_broadcaster_spawner = RegisterEventHandler(
event_handler=OnProcessExit(
target_action=joint_state_broadcaster_spawner,
on_exit=[rviz_node],
)
)
## UR5e机械臂控制器
ur5e_arm_controller_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["ur5e_arm_controller", "-c", "/controller_manager"],
)
## Robotiq夹爪控制器
robotiq_gripper_controller_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["gripper_controller", "-c", "/controller_manager"],
)
## Gazebo仿真环境
gazebo = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
[FindPackageShare("gazebo_ros"), "/launch", "/gazebo.launch.py"]
),
launch_arguments={
"world": gazebo_world_file,
}.items(),
)
## 在Gazebo中生成机器人实体
gazebo_spawn_robot = Node(
package="gazebo_ros",
executable="spawn_entity.py",
name="spawn_ur",
arguments=["-entity", "ur5e_gripper", "-topic", "robot_description"],
output="screen",
)
## 需要启动的节点列表
nodes_to_start = [
robot_state_publisher_node,
joint_state_broadcaster_spawner,
delay_rviz_after_joint_state_broadcaster_spawner,
ur5e_arm_controller_spawner,
robotiq_gripper_controller_spawner,
gazebo,
gazebo_spawn_robot,
]
return nodes_to_startpython接下来我们在launch_setup函数中对参数值进行解析并打印,内容如下:
补充:
- DeclareLaunchArgument:用于声明一个可以在启动时传递的参数(命令行参数)。
- LaunchConfiguration:用于获取和使用在启动时通过 DeclareLaunchArgument 声明并传递的参数的值。
- 在ROS 2的launch系统中,LaunchConfiguration是一个惰性求值的对象,它只在launch过程中才会被解析为实际值。因此,要获取其实际值,必须在launch上下文环境中使用.perform(context)方法。
launch_setup代码解析一:#
## RViz配置文件路径
rviz_config_file = PathJoinSubstitution(
[FindPackageShare(description_package), "rviz", "view_robot.rviz"]
)python这段代码设置了rviz的路径,使用了PathJoinSubstitution组件,与下面的构建路径的方式不同。
register_depth_launch = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
[FindPackageShare("vision"), "/launch", "/register_depth.launch.py"]
),
)python区别如下:
PythonLaunchDescriptionSource是用来加载launch文件的,而PathJoinSubstitution是用来加载其他配置文件的。
launch_setup代码解析二:#
## 机器人描述内容(通过xacro处理URDF文件生成)
robot_description_content = Command(
[
PathJoinSubstitution([FindExecutable(name="xacro")]),
" ",
PathJoinSubstitution(
[FindPackageShare(description_package), "urdf", description_file]
),
]
)
robot_description = {"robot_description": robot_description_content}
print(f"\033[95mrobot_description: {robot_description}\033[0m")python这段代码用于生成机器人的描述内容,通过调用xacro工具处理URDF文件来生成完整的机器人描述。
-
Command
- 这是一个用于执行系统命令的工具类
- 它允许在launch过程中动态执行命令并获取输出结果
- 命令将在launch时执行,而不是在launch文件解析时执行
-
PathJoinSubstitution([FindExecutable(name=“xacro”)]):
- FindExecutable(name=“xacro”)用于查找xacro可执行文件的完整路径
- PathJoinSubstitution在这里实际上只是将结果转换为字符串
-
空格字符串 ” ”:
- 这是在命令中添加空格分隔符
-
第二个PathJoinSubstitution:
- 构建URDF文件的路径
- FindPackageShare(description_package)查找包含机器人描述的包
- “urdf”是子目录名
- description_file是URDF/XACRO文件名(默认为”ur5e_gripper.urdf.xacro”)
整体功能是构建并执行以下形式的命令:
xacro /home/whisper/ros2_ws/install/ur5e_gripper_moveit_config/share/ur5e_gripper_moveit_config/urdf/ur5e_gripper.urdf.xacropython执行这个命令会输出处理后的完整URDF描述,其中所有xacro宏和参数都已展开。
最终,robot_description字典将被用作robot_state_publisher节点的参数:
robot_state_publisher_node = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
output="both",
parameters=[{"use_sim_time": True}, robot_description], ## 在这里使用
)python这是一个Node对象的定义,用于在launch文件中启动一个ROS 2节点。每个节点都需要指定一个包(package)和可执行文件(executable)来运行。Node的理解参考博客 ↗
各参数说明
-
package=“robot_state_publisher”:
- 指定节点所属的ROS 2包名
- robot_state_publisher是一个专门用于发布机器人状态的系统包
-
executable=“robot_state_publisher”:
- 指定要运行的可执行文件名
- 这是robot_state_publisher包中的主可执行文件
-
output=“both”:
- 指定节点输出(日志)的处理方式
- “both”表示同时输出到屏幕和日志文件
- 其他选项包括”screen”(仅屏幕)、“log”(仅日志文件)等
-
parameters=[{“use_sim_time”: True}, robot_description]:
- 指定传递给节点的参数列表
- 这是一个包含两个元素的列表:
- {“use_sim_time”: True}: 设置参数,告诉节点使用仿真时间而不是系统时间
- 这在Gazebo仿真环境中非常重要,因为仿真时间可能与实际系统时间不同
- 当设置为True时,节点将订阅/clock话题获取仿真时间
- robot_description: 包含机器人描述的参数字典
- 这包含了通过xacro命令生成的完整URDF描述
- robot_state_publisher使用这个描述来了解机器人的结构和关节关系
- {“use_sim_time”: True}: 设置参数,告诉节点使用仿真时间而不是系统时间
launch_setup代码解析三:#
## RViz可视化节点(根据launch_rviz参数决定是否启动)
rviz_node = Node(
package="rviz2",
executable="rviz2",
name="rviz2",
output="log",
arguments=["-d", rviz_config_file],
condition=IfCondition(launch_rviz),
)python-
package=“rviz2”: 指定ROS 2包名,这里是RViz可视化工具包
-
executable=“rviz2”: 指定要运行的可执行文件名
-
name=“rviz2”: 指定节点名称
-
output=“log”: 输出设置为日志模式,将输出记录到日志文件而不是显示在终端
-
arguments=[“-d”, rviz_config_file]: 启动参数
- -d参数指定RViz配置文件
- rviz_config_file是之前定义的配置文件路径
-
condition=IfCondition(launch_rviz): 启动条件
- 只有当launch_rviz参数为true时才会启动这个节点
- 这使得RViz的启动变得可选和灵活
launch_setup代码解析四:#
## 关节状态广播器
joint_state_broadcaster_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["joint_state_broadcaster", "--controller-manager", "/controller_manager"],
)python这是一个使用控制器管理器的spawner工具来启动关节状态广播器的节点。
-
package=“controller_manager”: 指定ROS 2包名,这里是控制器管理器包
-
executable=“spawner”: 指定要运行的可执行文件名,spawner是用于加载和启动控制器的工具
-
arguments: 传递给spawner的参数列表
- joint_state_broadcaster: 要启动的控制器名称,用于广播关节状态
- —controller-manager: 指定控制器管理器的参数
- /controller_manager: 控制器管理器的服务名称
这个节点的作用是启动关节状态广播器控制器,它会发布机器人关节的状态信息(如位置、速度等)到/joint_states话题,供其他节点(如robot_state_publisher)使用。这是机器人仿真和控制中必需的组件。
launch_setup代码解析五:#
## UR5e机械臂控制器
ur5e_arm_controller_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["ur5e_arm_controller", "-c", "/controller_manager"],
)python这是一个使用控制器管理器的spawner工具来启动UR5e机械臂控制器的节点。
-
package=“controller_manager”: 指定ROS 2包名,这里是控制器管理器包
-
executable=“spawner”: 指定要运行的可执行文件名,spawner是用于加载和启动控制器的工具
-
arguments: 传递给spawner的参数列表
- ur5e_arm_controller: 要启动的控制器名称,这是专门为UR5e机械臂配置的控制器
- -c: 控制器管理器的简写参数选项
- /controller_manager: 控制器管理器的服务名称
这个节点的作用是启动UR5e机械臂控制器,它负责控制机械臂的关节运动。与之前启动的关节状态广播器不同,这个控制器是用来发送控制指令给机械臂的,使机械臂能够按照指定的轨迹运动。这是控制真实或仿真机械臂所必需的组件。
launch_setup代码解析六:#
## Gazebo仿真环境
gazebo = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
[FindPackageShare("gazebo_ros"), "/launch", "/gazebo.launch.py"]
),
launch_arguments={
"world": gazebo_world_file,
}.items(),
)python-
PythonLaunchDescriptionSource: 指定要包含的launch文件来源
- FindPackageShare(“gazebo_ros”): 查找gazebo_ros包的共享目录
- 路径组合为: gazebo_ros包路径/launch/gazebo.launch.py
- 这是Gazebo ROS包提供的标准launch文件
-
launch_arguments: 传递给被包含launch文件的参数
- “world”: gazebo_world_file: 指定Gazebo要加载的世界文件路径
- gazebo_world_file是之前定义的世界文件路径变量
注意,这里使用的gazebo.launch.py是官方包提供的
launch_setup代码解析七:#
## 在Gazebo中生成机器人实体
gazebo_spawn_robot = Node(
package="gazebo_ros",
executable="spawn_entity.py",
name="spawn_ur",
arguments=["-entity", "ur5e_gripper", "-topic", "robot_description"],
output="screen",
)python这是一个用于在Gazebo仿真环境中生成机器人模型实体的节点,通过调用Gazebo ROS包提供的spawn_entity.py脚本来实现。
-
package=“gazebo_ros”: 指定ROS 2包名,这里是Gazebo ROS接口包
-
executable=“spawn_entity.py”: 指定要运行的可执行文件名,这是Gazebo ROS包提供的用于在仿真中生成实体的Python脚本
-
name=“spawn_ur”: 指定节点名称
-
output=“screen”: 输出设置为屏幕模式,将输出显示在终端上
-
arguments: 传递给spawn_entity.py脚本的参数列表
- -entity: 指定要创建的实体名称
- ur5e_gripper: 实体名称,这里命名为ur5e_gripper
- -topic: 指定从中获取机器人描述的话题
- robot_description: 话题名称,即之前定义的机器人描述参数
工作原理 这个节点通过以下步骤工作:
- 从robot_description话题获取机器人的URDF描述
- 在Gazebo仿真环境中创建名为ur5e_gripper的实体
- 将获取到的URDF描述应用到新创建的实体上
总结#
这个launch文件的主要功能是启动UR5e机械臂与Robotiq夹爪的完整Gazebo仿真环境,包括机器人模型加载、控制器配置、可视化界面和仿真世界设置。文件通过声明多个可配置参数(如描述包、描述文件、是否启动RViz等)来提供灵活性,并按正确顺序启动各个必要组件:首先启动Gazebo仿真环境和机器人状态发布器,然后加载关节状态广播器、机械臂控制器和夹爪控制器,最后在仿真环境中生成机器人实体。整个启动过程还考虑了依赖关系,例如确保在关节状态广播器启动后再启动RViz,以保证各组件能够正确协同工作。