ROS2-RViz-MoveIt-Gazebo-URDF学习笔记(五)
gazebo sim与gazebo classic的区别package.xml在**gazebo classic**场景中在**gazebo sim**场景
ros2_control在**gazebo classic**场景中在**gazebo sim**场景中
launch.py在**gazebo sim**场景中启动 gazebo方法1启动 gazebo方法2
gazebo_bridgerobot_state_publisherrviz_nodecontroller_spawner加载机器人模型到Gazebomove_group定义参数注册事件处理器LaunchDescription的启动顺序
gazebo sim与gazebo classic的区别
注意:两者是存在冲突的,所以要避免两者同时存在,且一些配置文件也要注意区分哪些适配于gazebo sim或gazebo classic。
文件里写“gazebo”的一般是classic版本,“gz”的一般是sim版本。
package.xml
在gazebo classic场景中
在gazebo classic场景中,package.xml会存在如下配置:


在gazebo sim场景

上述是官网的表述,即在Gazebo sim中使用“ros_gz”替换”gazebo_ros_pkgs“,且为了减少不必要的依赖,可以细化到使用“ros_gz”的子包,如“ros_gz_sim”、“ros_gz_bridge”等。
以下是在gazebo sim场景中,package.xml的配置:

# 更新依赖文件后,需要重新安装一下依赖
rosdep install --from-paths . -i -y
ros2_control
在Moveit2自动生成的**.urdf.xacro文件中会import机器人的描述文件(urdf)、ros2_control配置文件(**.ros2_control.xacro)等。
需要调整**.ros2_control.xacro。
在gazebo classic场景中
在gazebo classic场景中,**.ros2_control.xacro会存在如下配置:


或者

注意:上述2个<hardware>的<plugin>是不一样的。
| 对比维度 | |
|
|---|---|---|
| 适用场景 | 与 Gazebo(Classic 或 Ignition)联合仿真时使用,需要物理引擎计算关节力/速度/位置。 | 不需要真实物理仿真,可在无 Gazebo 的纯 ROS 2 环境中“虚拟”跑控制器,用于快速调试或演示。 |
| 是否依赖 Gazebo | 是,必须配合 插件及 文件一起使用。 |
否,完全不依赖 Gazebo,只需 即可启动。 |
| 数据流向 | 从 Gazebo 的物理引擎读取关节状态 → 写入控制器;控制指令 → 写回 Gazebo 物理引擎。 | 控制器发出的命令被 GenericSystem 直接“回环”到状态接口,无真实动力学计算。 |
| 实时性/精度 | 受仿真步长、物理引擎影响,可模拟摩擦、惯性、碰撞等。 | 理想化回环,通常瞬时响应,无动力学延迟,仅验证控制器逻辑。 |
| 典型启动方式 | 加载世界 → → 控制器启动。 |
直接 即可;无需仿真环境。 |
| 参数差异 | 需在 URDF/SDF 内同时写 和 两段配置。 |
仅保留 ,无 段。 |
| 用途举例 | 1. 差速小车/机械臂在 Gazebo 中做力控、运动学验证; 2. 视觉-动力学耦合仿真。 |
1. 快速测试控制器参数、话题接口; 2. CI 流水线自动测试控制器逻辑; 3. 无硬件时的演示与教学。 |
在gazebo sim场景中
GazeboSystem 是 Classic 时代的遗留实现;
GazeboSimSystem 是面向新版 Gazebo 的官方继任者,两者接口层级等价,但底层连接的是完全不同的仿真引擎,launch 与插件库必须配套使用,不可互换
| 对比维度 | gazebo_ros2_control/GazeboSystem | gz_ros2_control/GazeboSimSystem |
|---|---|---|
| 适配的 Gazebo 版本 | 仅 Gazebo Classic(11 及更早) | 仅 Gazebo Sim(原 Ignition,Edifice/Harmonic 等) |
| 包名 / 仓库 | gazebo_ros2_control | gz_ros2_control |
| 硬件插件 tag 写法 | |
|
| SDF/URDF 中配套 Gazebo 插件(必须同时写) | |
|
| 接口默认支持 | State、Effort、Velocity、Position | 同上,但实现类变为 |
| 命令行为差异(以 Position 为例) | 内部用 PID 把位置误差换算成速度,再喂给 Classic 的关节电机 | 同样算法,但底层由 Gazebo Sim 的 DART/Physics 引擎执行 |
| 自定义系统插件基类 | 继承 |
继承 |
| 启动流程 | |
|
| 是否可混用 | ❌ 不能混用:Classic 世界只能加载 libgazebo_ros2_control.so;Sim 世界只能加载 libgz_ros2_control-system.so | ❌ 同上 |
| 维护状态 | 随 Classic 进入“仅修严重 bug”阶段 | 官方主推,持续更新新特性(低通、非线性弹簧示例等) |
在gazebo sim场景中,**.ros2_control.xacro会存在如下配置:

或者


launch.py
官网给的两个示例:
在gazebo classic场景中:
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration
def generate_launch_description():
launch_file_dir = os.path.join(get_package_share_directory('turtlebot3_gazebo'), 'launch')
pkg_gazebo_ros = get_package_share_directory('gazebo_ros')
use_sim_time = LaunchConfiguration('use_sim_time', default='true')
x_pose = LaunchConfiguration('x_pose', default='0.0')
y_pose = LaunchConfiguration('y_pose', default='0.0')
world = os.path.join(
get_package_share_directory('turtlebot3_gazebo'),
'worlds',
'empty_world.world'
)
gzserver_cmd = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(pkg_gazebo_ros, 'launch', 'gzserver.launch.py')
),
launch_arguments={'world': world}.items()
)
gzclient_cmd = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(pkg_gazebo_ros, 'launch', 'gzclient.launch.py')
)
)
robot_state_publisher_cmd = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(launch_file_dir, 'robot_state_publisher.launch.py')
),
launch_arguments={'use_sim_time': use_sim_time}.items()
)
spawn_turtlebot_cmd = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(launch_file_dir, 'spawn_turtlebot3.launch.py')
),
launch_arguments={
'x_pose': x_pose,
'y_pose': y_pose
}.items()
)
ld = LaunchDescription()
# Add the commands to the launch description
ld.add_action(gzserver_cmd)
ld.add_action(gzclient_cmd)
ld.add_action(robot_state_publisher_cmd)
ld.add_action(spawn_turtlebot_cmd)
return ld
在gazebo sim场景中
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
# 改动1:新增AppendEnvironmentVariable
from launch.actions import AppendEnvironmentVariable
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration
def generate_launch_description():
launch_file_dir = os.path.join(get_package_share_directory('turtlebot3_gazebo'), 'launch')
# 改动2:由gazebo_ros改为ros_gz_sim
ros_gz_sim = get_package_share_directory('ros_gz_sim')
use_sim_time = LaunchConfiguration('use_sim_time', default='true')
x_pose = LaunchConfiguration('x_pose', default='0.0')
y_pose = LaunchConfiguration('y_pose', default='0.0')
world = os.path.join(
get_package_share_directory('turtlebot3_gazebo'),
'worlds',
'empty_world.world'
)
# 改动3:设置环境变量GZ_SIM_RESOURCE_PATH
# 需要设置环境变量GZ_SIM_RESOURCE_PATH,以告诉Gazebo在哪里可以找到模型
# 在gazebo_ros_pkgs中,因为在package.xml中使用了<export>标签,为Gazebo创建一个类似的环境变量(GAZEBO_MODEL_PATH),
# 可参考本文章的第1个图,在其下面就是使用了<gazebo_ros gazebo_model_path="${prefix}/models"/>
set_env_vars_resources = AppendEnvironmentVariable(
'GZ_SIM_RESOURCE_PATH',
os.path.join(get_package_share_directory('turtlebot3_gazebo'),
'models'))
# 改动4:由gzserver.launch.py改为使用gz_sim.launch.py启动文件
# -s 参数使 Gazebo 仅运行服务器端而不启动 GUI 客户端界面。
# -r 参数指示 Gazebo 在启动后立即开始模拟运行。
gzserver_cmd = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(ros_gz_sim, 'launch', 'gz_sim.launch.py')
),
launch_arguments={'gz_args': ['-r -s -v4 ', world], 'on_exit_shutdown': 'true'}.items()
)
# 改动5:由gzclient.launch.py改为使用gz_sim.launch.py启动文件
# -g 以仅运行图形用户界面(GUI)客户端。
gzclient_cmd = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(ros_gz_sim, 'launch', 'gz_sim.launch.py')
),
launch_arguments={'gz_args': '-g -v4 '}.items()
)
robot_state_publisher_cmd = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(launch_file_dir, 'robot_state_publisher.launch.py')
),
launch_arguments={'use_sim_time': use_sim_time}.items()
)
spawn_turtlebot_cmd = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(launch_file_dir, 'spawn_turtlebot3.launch.py')
),
launch_arguments={
'x_pose': x_pose,
'y_pose': y_pose
}.items()
)
ld = LaunchDescription()
# Add the commands to the launch description
# 改动5:新增ld.add_action(set_env_vars_resources)
ld.add_action(set_env_vars_resources)
ld.add_action(gzserver_cmd)
ld.add_action(gzclient_cmd)
ld.add_action(robot_state_publisher_cmd)
ld.add_action(spawn_turtlebot_cmd)
return ld
solidworks导出urdf时自动生成的gazebo.launch.py:
import os
from ament_index_python.packages import get_package_share_directory
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch import LaunchDescription
from launch.substitutions import LaunchConfiguration
from launch.actions import IncludeLaunchDescription, LogInfo, TimerAction
from launch_ros.actions import Node
def generate_launch_description():
"""
生成 ROS 2 启动描述对象,用于启动机器人状态发布节点、Gazebo 仿真环境并在其中生成机器人实体。
Returns:
launch.LaunchDescription: 包含所有启动动作和节点的启动描述对象。
"""
# 获取默认路径
# 定义机器人在 Gazebo 模型中的名称
robot_name_in_model = "robodescriptioncpp"
# 获取 robodescriptioncpp 包的共享目录路径
urdf_tutorial_path = get_package_share_directory('robodescriptioncpp')
# 拼接 URDF 文件的完整路径,该文件描述了机器人的模型
default_model_path = os.path.join(
urdf_tutorial_path, 'urdf', 'robodescriptioncpp.urdf')
# 读取 URDF 文件内容
# 以只读模式打开 URDF 文件,并将文件内容读取到 robot_description 变量中
with open(default_model_path, 'r') as urdf_file:
robot_description = urdf_file.read()
# 创建机器人状态发布节点
# 该节点负责发布机器人的关节状态和 TF(坐标变换)信息
robot_state_publisher_node = Node(
package='robot_state_publisher', # 节点所属的包名
executable='robot_state_publisher', # 可执行文件名称
name='robot_state_publisher',
output='screen',
parameters=[{'robot_description': robot_description}] # 传递机器人的 URDF 描述参数
)
# 包含另一个用于启动 Gazebo 的启动文件
launch_gazebo = IncludeLaunchDescription(
PythonLaunchDescriptionSource([os.path.join(get_package_share_directory(
'gazebo_ros'), 'launch', 'gazebo.launch.py')]), # 指定 Gazebo 启动文件的路径
)
# 请求 Gazebo 生成机器人实体
spawn_entity_node = Node(
package='gazebo_ros', # 节点所属的包名
executable='spawn_entity.py', # 可执行文件名称
arguments=['-topic', 'robot_description', # 指定机器人描述信息的话题
'-entity', 'my_bot'], # 指定机器人在 Gazebo 中的实体名称 robot_name_in_model
# 等待 Gazebo 启动完成后再执行
output='screen',
on_exit=[LogInfo(msg="Robot spawning completed.")]
)
# 返回包含所有节点和启动动作的启动描述对象
return LaunchDescription([
robot_state_publisher_node,
launch_gazebo,
# 添加延迟,确保 Gazebo 启动后再生成实体
TimerAction(
period=5.0,
actions=[spawn_entity_node]
)
])
在gazebo sim场景中
启动 gazebo方法1
# 启动 gazebo
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import AppendEnvironmentVariable
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
def generate_launch_description():
ros_gz_sim = get_package_share_directory('ros_gz_sim')
world = os.path.join(
get_package_share_directory('turtlebot3_gazebo'),
'worlds',
'empty_world.world'
)
set_env_vars_resources = AppendEnvironmentVariable(
'GZ_SIM_RESOURCE_PATH',
os.path.join(get_package_share_directory('turtlebot3_gazebo'),
'models'))
gzserver_cmd = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(ros_gz_sim, 'launch', 'gz_sim.launch.py')
),
launch_arguments={'gz_args': ['-r -s -v4 ', world], 'on_exit_shutdown': 'true'}.items()
)
gzclient_cmd = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(ros_gz_sim, 'launch', 'gz_sim.launch.py')
),
launch_arguments={'gz_args': '-g -v4 '}.items()
)
ld = LaunchDescription()
ld.add_action(set_env_vars_resources)
ld.add_action(gzserver_cmd)
ld.add_action(gzclient_cmd)
return ld
启动 gazebo方法2
from launch.actions import IncludeLaunchDescription
from launch.conditions import IfCondition, UnlessCondition
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration
from launch_ros.substitutions import FindPackageShare
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
# Initialize Arguments
gui = LaunchConfiguration("gui")
# 最简单,使用FindPackageShare
gazebo = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
[FindPackageShare("ros_gz_sim"), "/launch/gz_sim.launch.py"]
),
launch_arguments=[("gz_args", " -r -v 3 empty.sdf")],
)
# 最简单,使用get_package_share_directory
gazebo = IncludeLaunchDescription(
PythonLaunchDescriptionSource([
get_package_share_directory('ros_gz_sim') + '/launch/gz_sim.launch.py',
]),
launch_arguments=[("gz_args", " -r -v 3 empty.sdf")],
)
# 指定物理引擎
gazebo = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
[FindPackageShare("ros_gz_sim"), "/launch/gz_sim.launch.py"]
),
launch_arguments=[("gz_args", " -r -v 3 empty.sdf --physics-engine gz-physics-bullet-featherstone-plugin")],
)
# 增加条件判断IfCondition,真值时才执行。
gazebo = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
[FindPackageShare("ros_gz_sim"), "/launch/gz_sim.launch.py"]
),
launch_arguments=[("gz_args", " -r -v 3 empty.sdf")],
condition=IfCondition(gui),
)
# 无图形化显示
gazebo_headless = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
[FindPackageShare("ros_gz_sim"), "/launch/gz_sim.launch.py"]
),
launch_arguments=[("gz_args", ["--headless-rendering -s -r -v 3 empty.sdf"])],
condition=UnlessCondition(gui),
)
return LaunchDescription([
gazebo,
gazebo_headless,
])
| 项目 | FindPackageShare | get_package_share_directory |
|---|---|---|
| 所在模块 | |
|
| 返回类型 | (launch 表达式) |
(普通字符串) |
| 求值时机 | launch 文件 执行阶段 由 launch 引擎解析 | 调用函数 立即 返回路径 |
| 是否依赖 ament index | 是(解析时) | 是(调用时) |
gazebo_bridge
from launch_ros.actions import Node
from launch import LaunchDescription
def generate_launch_description():
gazebo_bridge = Node(
package="ros_gz_bridge",
executable="parameter_bridge",
arguments=["/clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock"],
output="screen",
)
return LaunchDescription([
gazebo_bridge,
])
robot_state_publisher
from launch import LaunchDescription
from launch.substitutions import Command, FindExecutable, LaunchConfiguration, PathJoinSubstitution
from moveit_configs_utils import MoveItConfigsBuilder
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare
import os
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
# 最简单
robot_description_content = Command(
[
PathJoinSubstitution([FindExecutable(name='xacro')]),
' ',
PathJoinSubstitution(
[FindPackageShare('gz_ros2_control_demos'),
'urdf', 'test_cart_position.xacro.urdf']
),
]
)
robot_description = {'robot_description': robot_description_content}
node_robot_state_publisher = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
output="screen",
parameters=[robot_description],
)
# 高级做法
# robot_description()里的就是moveit自动生成的xacro文件
# robot_description_semantic()里的就是moveit自动生成的srdf文件
moveit_config = ( MoveItConfigsBuilder("robot_name", package_name="moveit_config")
.robot_description('config/robot_name.gazebo.urdf.xacro')
.robot_description_semantic('config/robot_name.srdf').to_moveit_configs()
)
node_robot_state_publisher = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
name="robot_state_publisher",
# "both"表示同时将输出显示到控制台和日志文件中
output="both",
parameters=[moveit_config.robot_description,
{'use_sim_time': True}, # 必须使用仿真时间
{"publish_frequency": 30.0},
],
)
# 其他方法
TURTLEBOT3_MODEL = os.environ['TURTLEBOT3_MODEL']
use_sim_time = LaunchConfiguration('use_sim_time', default='false')
urdf_file_name = 'turtlebot3_' + TURTLEBOT3_MODEL + '.urdf'
print('urdf_file_name : {}'.format(urdf_file_name))
urdf_path = os.path.join(
get_package_share_directory('turtlebot3_gazebo'),
'urdf',
urdf_file_name)
with open(urdf_path, 'r') as infp:
robot_desc = infp.read()
node_robot_state_publisher = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
name='robot_state_publisher',
output='screen',
parameters=[{
'use_sim_time': use_sim_time,
'robot_description': robot_desc
}],
)
return LaunchDescription([
node_robot_state_publisher,
])
rviz_node
from launch import LaunchDescription
from launch.substitutions import PathJoinSubstitution
from moveit_configs_utils import MoveItConfigsBuilder
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
# 方法1
rviz_config_file = PathJoinSubstitution(
[FindPackageShare("ros2_control_demo_description"), "rrbot/rviz", "rrbot.rviz"]
)
rviz_node = Node(
package="rviz2",
executable="rviz2",
name="rviz2",
output="log",
arguments=["-d", rviz_config_file],
)
# 方法2
packagepath = get_package_share_directory('moveit_config')
moveit_config = ( MoveItConfigsBuilder("robot_name", package_name="moveit_config")
.robot_description('config/robot_name.gazebo.urdf.xacro')
.robot_description_semantic('config/robot_name.srdf').to_moveit_configs()
)
rviz_node = Node(
package="rviz2",
executable="rviz2",
output="log",
arguments=["-d", packagepath+'/config/moveit.rviz'],
parameters=[
moveit_config.robot_description,
moveit_config.robot_description_semantic,
moveit_config.robot_description_kinematics,
moveit_config.planning_pipelines,
moveit_config.joint_limits,
{'use_sim_time': True},
],
)
return LaunchDescription([
rviz_node,
])
controller_spawner
yaml配置文件:
# ros2_controllers.yaml
controller_manager:
ros__parameters:
update_rate: 50 # Hz
arm:
type: joint_trajectory_controller/JointTrajectoryController
hand:
type: position_controllers/GripperActionController
joint_state_broadcaster:
type: joint_state_broadcaster/JointStateBroadcaster
arm:
ros__parameters:
joints:
- joint1
- joint2
- joint3
- joint4
- joint5
- joint6
command_interfaces:
- position
state_interfaces:
- position
- velocity
allow_nonzero_velocity_at_trajectory_end: true
hand:
ros__parameters:
joint: joint_gripper_and_left
launch.py文件:
from launch import LaunchDescription
from launch.substitutions import PathJoinSubstitution
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
# 方法1:合并写一个控制器启动器:启动关节状态发布器,arm组控制器,夹爪控制器
controller_spawner_node = Node(
package="controller_manager",
executable="spawner",
arguments=[
"joint_state_broadcaster", "arm", "hand"
],
)
# 方法2:分开写多个控制器启动器
joint_state_broadcaster_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["joint_state_broadcaster", "--controller-manager", "/controller_manager"],
)
arm_controller_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["arm", "--controller-manager", "/controller_manager"],
)
hand_controller_spawner = Node(
package="controller_manager",
executable="spawner",
arguments=["hand", "--controller-manager", "/controller_manager"],
)
# 方法3
joint_state_broadcaster_spawner = Node(
package='controller_manager',
executable='spawner',
arguments=['joint_state_broadcaster'],
)
robot_controllers = PathJoinSubstitution(
[
FindPackageShare('gz_ros2_control_demos'),
'config',
'ros2_controllers.yaml',
]
)
robot_controller_spawner = Node(
package='controller_manager',
executable='spawner',
arguments=[
'arm',
'hand',
'--param-file',
robot_controllers,
],
)
return LaunchDescription([
controller_spawner_node,
joint_state_broadcaster_spawner,
arm_controller_spawner,
hand_controller_spawner,
robot_controller_spawner,
])
注意:上述能自动找到ros2_controllers.yaml是因为在之前的urdf/xacro文件中有配置信息:
<gazebo>
<plugin filename="gz_ros2_control-system" name="gz_ros2_control::GazeboSimROS2ControlPlugin">
<parameters>$(find gz_ros2_control_demos)/config/ros2_controllers.yaml</parameters>
</plugin>
</gazebo>
关键作用:
gz_ros2_control-system:这是Gazebo的ROS2控制插件自动启动控制器管理器:当Gazebo启动时,插件会自动创建控制器管理器加载控制器配置:自动读取ros2_controllers.yaml配置文件
在Gazebo仿真时,上述gz_ros2_control-system会自动启动ros2_control节点。
若是真实硬件,还需增加ros2_control节点开启配置:
from launch import LaunchDescription
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
packagepath = get_package_share_directory('moveit_config')
ros2_control_node = Node(
package="controller_manager",
executable="ros2_control_node",
parameters=[packagepath+'/config/ros2_controllers.yaml',
{'use_sim_time': True}],
output="both",
)
return LaunchDescription([
ros2_control_node,
])
加载机器人模型到Gazebo
from launch import LaunchDescription
from launch_ros.actions import Node
from moveit_configs_utils import MoveItConfigsBuilder
def generate_launch_description():
# 方法1
moveit_config = ( MoveItConfigsBuilder("robot_name", package_name="moveit_config")
.robot_description('config/robot_name.gazebo.urdf.xacro')
.robot_description_semantic('config/robot_name.srdf').to_moveit_configs()
)
robot_to_gazebo_node = Node(
package='ros_gz_sim',
executable='create',
arguments=['-string', moveit_config.robot_description['robot_description'], '-x', '0.0', '-y', '0.0', '-z', '0.0', '-name', 'robot_name']
)
# 方法2
# 这里的robot_description是ros话题
gz_spawn_entity = Node(
package='ros_gz_sim',
executable='create',
output='screen',
arguments=['-topic', '/robot_description',
'-name', 'cart', '-allow_renaming', 'true'],
)
return LaunchDescription([
robot_to_gazebo_node,
gz_spawn_entity,
])
| 维度 | robot_to_gazebo_node | gz_spawn_entity |
|---|---|---|
| 输入源 | 后直接跟 URDF 字符串(内存变量) |
去 订阅 ROS 参数服务器上的 话题 |
| 参数写法 | |
|
| 谁先准备好 URDF | launch 文件里 立即展开 xacro 并塞进节点参数 | 节点启动时 自己去读 参数;因此要求前面必须有 或 把参数写进 ROS |
| 适用场景 | 想 一次性把 URDF 硬编码 进 spawn 节点,不依赖参数服务器 | 标准做法:参数服务器已经存在 ,多个节点共用,随时可重读 |
| 优缺点 | 不依赖参数顺序,launch 里能直接看见模型;但命令行可能超长,调试不便 | 命令行短、与 rviz/moveit 共用同一参数,推荐;但要保证 spawn 之前参数已就位 |
move_group
move_group节点是MoveIt系统的核心,它提供以下功能:
运动规划服务 – 接收目标位姿,规划无碰撞的运动轨迹逆运动学求解 – 计算达到目标位姿所需的关节角度碰撞检测 – 确保规划的路径不会与环境发生碰撞轨迹执行 – 将规划好的轨迹发送给控制器执行状态监控 – 实时监控机器人的运动状态
这个节点启动后,其他节点就可以通过ROS服务或动作接口来请求运动规划,是整个机器人智能运动的基础。
from launch import LaunchDescription
from launch_ros.actions import Node
from moveit_configs_utils import MoveItConfigsBuilder
def generate_launch_description():
moveit_config = ( MoveItConfigsBuilder("robot_name", package_name="moveit_config")
.robot_description('config/robot_name.gazebo.urdf.xacro')
.robot_description_semantic('config/robot_name.srdf').to_moveit_configs()
)
# 启动move_group node/action server
move_group_node = Node(
package="moveit_ros_move_group",
executable="move_group",
output="screen",
parameters=[moveit_config.to_dict(),
{'use_sim_time': True}],
)
return LaunchDescription([
move_group_node,
])
注意:若没有使用MoveIt,直接通过ROS话题直接发送位置目标,则不需要上述配置。
定义参数
# 语法结构
DeclareLaunchArgument(
name='argument_name', # 参数名称
default_value='default_value', # 默认值
description='Description of what this argument does' # 参数描述
)
使用示例
DeclareLaunchArgument(
'use_sim_time',
default_value=use_sim_time,
description='If true, use simulated clock'),
参数名称,在命令行中使用:
ros2 launch package file.launch.py use_sim_time:=false
参数的描述信息:
ros2 launch package file.launch.py --show-args
注册事件处理器
RegisterEventHandler 允许你在启动过程中注册事件处理器,当特定事件发生时执行相应的动作。这是实现启动顺序控制和依赖管理的关键机制。
事件类型
ROS 2 Launch 支持多种事件处理器:
OnProcessExit – 进程退出事件OnProcessStart – 进程启动事件OnExecutionComplete – 执行完成事件OnIncludeLaunchDescription – 包含启动描述事件
使用示例:
# 1. gz_spawn_entity → 生成机器人实体(并行启动)
# 2. ↓ 当gz_spawn_entity完成后
# 3. joint_state_broadcaster_spawner → 启动关节状态广播器
RegisterEventHandler(
event_handler=OnProcessExit(
target_action=gz_spawn_entity, # 监听目标:Gazebo实体生成
on_exit=[joint_state_broadcaster_spawner], # 响应动作:启动关节状态广播器
)
)
LaunchDescription的启动顺序
┌─────────────────────────────────────────────────────────────────────┐
│ 启动阶段分析 │
├─────────────────────────────────────────────────────────────────────┤
│ 1. 并行启动(无依赖)
│ ├─ IncludeLaunchDescription → 启动Gazebo环境
│ ├─ RegisterEventHandler(1) → 注册实体生成完成事件处理器
│ ├─ RegisterEventHandler(2) → 注册关节状态广播器完成事件处理器
│ ├─ RegisterEventHandler(3) → 注册具体控制器完成事件处理器
│ ├─ gazebo_bridge → 时钟桥接器(立即启动)
│ ├─ node_robot_state_publisher → 机器人状态发布器(立即启动)
│ ├─ gz_spawn_entity → 实体生成器(立即启动)
│ └─ DeclareLaunchArgument → 参数声明(配置)
│
│ 2. 事件驱动启动(有依赖)
│ ↓ 当 gz_spawn_entity 完成后
│ joint_state_broadcaster_spawner → 关节状态广播器
│
│ 3. 事件驱动启动(有依赖)
│ ↓ 当 joint_state_broadcaster_spawner 完成后
│ robot_controller_spawner → 启动具体控制器
│
│ 3. 最终启动
│ ↓ 当 robot_controller_spawner 完成后
│ ├─ move_group_node → 启动MoveIt 的运动规划节点
│ └─ rviz_node → 启动rviz节点
└─────────────────────────────────────────────────────────────────────┘
return LaunchDescription([
# Launch gazebo environment
gazebo,
RegisterEventHandler(
event_handler=OnProcessExit(
target_action=gz_spawn_entity,
on_exit=[joint_state_broadcaster_spawner],
)
),
RegisterEventHandler(
event_handler=OnProcessExit(
target_action=joint_state_broadcaster_spawner,
on_exit=[robot_controller_spawner],
)
),
RegisterEventHandler(
event_handler=OnProcessExit(
target_action=robot_controller_spawner,
on_exit=[rviz_node, move_group_node], # 同时启动
)
),
gazebo_bridge,
node_robot_state_publisher,
gz_spawn_entity,
# Launch Arguments
DeclareLaunchArgument(
'use_sim_time',
default_value=use_sim_time,
description='If true, use simulated clock'),
])


