ROS2-RViz-MoveIt-Gazebo-URDF学习笔记(五)

内容分享13小时前发布
0 0 0

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会存在如下配置:
ROS2-RViz-MoveIt-Gazebo-URDF学习笔记(五)
ROS2-RViz-MoveIt-Gazebo-URDF学习笔记(五)

gazebo sim场景

ROS2-RViz-MoveIt-Gazebo-URDF学习笔记(五)

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

以下是在gazebo sim场景中,package.xml的配置:
ROS2-RViz-MoveIt-Gazebo-URDF学习笔记(五)


# 更新依赖文件后,需要重新安装一下依赖
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会存在如下配置:

ROS2-RViz-MoveIt-Gazebo-URDF学习笔记(五)

ROS2-RViz-MoveIt-Gazebo-URDF学习笔记(五)
或者

ROS2-RViz-MoveIt-Gazebo-URDF学习笔记(五)

注意:上述2个<hardware>的<plugin>是不一样的。

对比维度
<plugin>gazebo_ros2_control/GazeboSystem</plugin>

<plugin>mock_components/GenericSystem</plugin>
适用场景 与 Gazebo(Classic 或 Ignition)联合仿真时使用,需要物理引擎计算关节力/速度/位置。 不需要真实物理仿真,可在无 Gazebo 的纯 ROS 2 环境中“虚拟”跑控制器,用于快速调试或演示。
是否依赖 Gazebo 是,必须配合
libgazebo_ros2_control.so
插件及
.world/.sdf
文件一起使用。
否,完全不依赖 Gazebo,只需
ros2_control_node
即可启动。
数据流向 从 Gazebo 的物理引擎读取关节状态 → 写入控制器;控制指令 → 写回 Gazebo 物理引擎。 控制器发出的命令被 GenericSystem 直接“回环”到状态接口,无真实动力学计算。
实时性/精度 受仿真步长、物理引擎影响,可模拟摩擦、惯性、碰撞等。 理想化回环,通常瞬时响应,无动力学延迟,仅验证控制器逻辑。
典型启动方式
ros2 launch gazebo_ros gazebo.launch.py
加载世界 →
spawn_entity
→ 控制器启动。
直接
ros2 run controller_manager ros2_control_node --params-file xxx.yaml
即可;无需仿真环境。
参数差异 需在 URDF/SDF 内同时写
<ros2_control><hardware>…

<gazebo><plugin filename="libgazebo_ros2_control.so">…
两段配置。
仅保留
<ros2_control><hardware><plugin>mock_components/GenericSystem</plugin></hardware>…
,无
<gazebo>
段。
用途举例 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 写法
<plugin>gazebo_ros2_control/GazeboSystem</plugin>

<plugin>gz_ros2_control/GazeboSimSystem</plugin>
SDF/URDF 中配套 Gazebo 插件(必须同时写)
<plugin filename="libgazebo_ros2_control.so" name="gazebo_ros2_control">…</plugin>

<plugin filename="libgz_ros2_control-system.so" name="gz_ros2_control::GazeboSimROS2ControlPlugin">…</plugin>
接口默认支持 State、Effort、Velocity、Position 同上,但实现类变为
GazeboSimSystemInterface
命令行为差异(以 Position 为例) 内部用 PID 把位置误差换算成速度,再喂给 Classic 的关节电机 同样算法,但底层由 Gazebo Sim 的 DART/Physics 引擎执行
自定义系统插件基类 继承
gazebo_ros2_control::GazeboSystemInterface
继承
gz_ros2_control::GazeboSimSystemInterface
启动流程
gazebo <world> + spawn_entity

gz sim <world> + gz create
是否可混用 ❌ 不能混用:Classic 世界只能加载 libgazebo_ros2_control.so;Sim 世界只能加载 libgz_ros2_control-system.so ❌ 同上
维护状态 随 Classic 进入“仅修严重 bug”阶段 官方主推,持续更新新特性(低通、非线性弹簧示例等)

gazebo sim场景中,**.ros2_control.xacro会存在如下配置:
ROS2-RViz-MoveIt-Gazebo-URDF学习笔记(五)
或者
ROS2-RViz-MoveIt-Gazebo-URDF学习笔记(五)
ROS2-RViz-MoveIt-Gazebo-URDF学习笔记(五)

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_ros.substitutions

ament_index_python.packages
返回类型
Substitution
(launch 表达式)

str
(普通字符串)
求值时机 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
输入源
-string
后直接跟 URDF 字符串(内存变量)

-topic
订阅 ROS 参数服务器上的
/robot_description
话题
参数写法
arguments=['-string', moveit_config.robot_description['robot_description'], ...]

arguments=['-topic', 'robot_description', ...]
谁先准备好 URDF launch 文件里 立即展开 xacro 并塞进节点参数 节点启动时 自己去读
/robot_description
参数
;因此要求前面必须有
robot_state_publisher

ros2_control_node
把参数写进 ROS
适用场景 一次性把 URDF 硬编码 进 spawn 节点,不依赖参数服务器 标准做法:参数服务器已经存在
/robot_description
,多个节点共用,随时可重读
优缺点 不依赖参数顺序,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'),
    ])

© 版权声明

相关文章

暂无评论

none
暂无评论...