构建机器人


预计完成时间:10 分钟

2.7   使用 ROS2 Launch 文件生成机器人

这是把模型生成到 Gazebo Sim 中最推荐的方法。通过这种可控的生成方式,你可以在需要时自动把模型插入 Gazebo Sim。

launch 文件夹中创建一个新文件 empty_world.launch.py,并写入相应内容。

  在 Web Shell 3 中执行

In [ ]:
touch /workspace/src/robot_description/launch/empty_world.launch.py

现在,把下面的代码复制到你刚刚创建的 launch 文件中。

  empty_world.launch.py

In [ ]:
import os
from ament_index_python.packages import (get_package_prefix, get_package_share_directory)
from launch import LaunchDescription
from launch.actions import (DeclareLaunchArgument, IncludeLaunchDescription)
from launch.substitutions import (PathJoinSubstitution, LaunchConfiguration)
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import SetParameter

# ROS2 Launch System will look for this function definition #
def generate_launch_description():

    # Get Package Description and Directory #
    package_description = "robot_description"
    package_directory = get_package_share_directory(package_description)

    # Set the Path to Robot Mesh Models for Loading in Gazebo Sim #
    # NOTE: Do this BEFORE launching Gazebo Sim #
    install_dir_path = (get_package_prefix(package_description) + "/share")
    robot_meshes_path = os.path.join(package_directory, "meshes")
    gazebo_resource_paths = [install_dir_path, robot_meshes_path]
    if "IGN_GAZEBO_RESOURCE_PATH" in os.environ:
        for resource_path in gazebo_resource_paths:
            if resource_path not in os.environ["IGN_GAZEBO_RESOURCE_PATH"]:
                os.environ["IGN_GAZEBO_RESOURCE_PATH"] += (':' + resource_path)
    else:
        os.environ["IGN_GAZEBO_RESOURCE_PATH"] = (':'.join(gazebo_resource_paths))

    # Load Empty World SDF from Gazebo Sim Package #
    world_file = "empty.sdf"
    world_config = LaunchConfiguration("world")
    declare_world_arg = DeclareLaunchArgument("world",
                                              default_value=["-r ", world_file],
                                              description="SDF World File")
    
    # Declare GazeboSim Launch #
    gzsim_pkg = get_package_share_directory("ros_gz_sim")
    gz_sim = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            PathJoinSubstitution([gzsim_pkg, "launch", "gz_sim.launch.py"])),
            launch_arguments={"gz_args": world_config}.items(),
    )

    # Create and Return the Launch Description Object #
    return LaunchDescription(
        [
            declare_world_arg,
            # Sets use_sim_time for all nodes started below (doesn't work for nodes started from ignition gazebo) #
            SetParameter(name="use_sim_time", value=True),
            gz_sim,
        ]
    )
- 重要说明 -

注意,在这个 launch 文件中我们使用的是环境变量 IGN_GAZEBO_RESOURCE_PATH,而不是 GZ_SIM_RESOURCE_PATH。为什么会这样?

原因在于,我们现在不再通过 gz sim <world_file> 这个命令直接启动 Gazebo Sim,而是通过 ROS 2 功能包 ros_gz_sim 提供的 gz_sim.launch.py 文件来启动它,如下所示:

In [ ]:
gzsim_pkg = get_package_share_directory("ros_gz_sim")
    gz_sim = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            PathJoinSubstitution([gzsim_pkg, "launch", "gz_sim.launch.py"])),
            launch_arguments={"gz_args": world_config}.items(),
    )

换句话说,Gazebo Sim 现在是通过 ROS 2 启动的,而不是直接运行的。那么,这为什么重要呢?

这很重要,因为本课程使用的 ROS 2 发行版是 ROS 2 Humble,它与 Gazebo Sim Garden(也就是你前面一直在运行的版本)并不兼容。对于 ROS 2 Humble,受支持的版本是 Gazebo Sim Fortress,而它仍然沿用了 Ignition Gazebo 的一些约定。(如果这里有点混乱,可以回看 1.1 单元。)

因此,当你把 Gazebo Sim 和 ROS 2 Humble 配合使用时,仍然需要遵循一些 Ignition 风格的约定。没关系,后面凡是遇到这种情况,我们都会特别指出。

- 重要说明结束 -

启动仿真:

  在 Web Shell 1 中执行

In [ ]:
cd /workspace
In [ ]:
colcon build --packages-select robot_description && source install/setup.bash
In [ ]:
ros2 launch robot_description empty_world.launch.py

你现在启动的是一个空世界。建议把生成机器人的逻辑放到单独的文件中,这样在开发过程中就可以不重启整个仿真,而是直接删除并重新生成机器人。

新建一个 spawn.launch.py 文件,并填入下面的内容:

  spawn.launch.py

In [ ]:
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import (Command, LaunchConfiguration)
from launch_ros.actions import (Node, SetParameter)

# ROS2 Launch System will look for this function definition #
def generate_launch_description():

    # Get Package Description and Directory #
    package_description = "robot_description"
    package_directory = get_package_share_directory(package_description)

    # Load URDF File #
    urdf_file = 'robot.urdf'
    robot_desc_path = os.path.join(package_directory, "urdf", urdf_file)
    print("URDF Loaded !")

    # Robot State Publisher (RSP) #
    robot_state_publisher_node = Node(
        package='robot_state_publisher',
        executable='robot_state_publisher',
        name='robot_state_publisher_node',
        output="screen",
        emulate_tty=True,
        parameters=[{'use_sim_time': True, 
                     'robot_description': Command(['xacro ', robot_desc_path])}]
    )

    # Spawn the Robot #
    declare_spawn_x = DeclareLaunchArgument("x", default_value="0.0",
                                            description="Model Spawn X Axis Value")
    declare_spawn_y = DeclareLaunchArgument("y", default_value="0.0",
                                            description="Model Spawn Y Axis Value")
    declare_spawn_z = DeclareLaunchArgument("z", default_value="0.5",
                                            description="Model Spawn Z Axis Value")
    gz_spawn_entity = Node(
        package="ros_gz_sim",
        executable="create",
        name="my_robot_spawn",
        arguments=[
            "-name", "my_robot",
            "-allow_renaming", "true",
            "-topic", "robot_description",
            "-x", LaunchConfiguration("x"),
            "-y", LaunchConfiguration("y"),
            "-z", LaunchConfiguration("z"),
        ],
        output="screen",
    )

    # Create and Return the Launch Description Object #
    return LaunchDescription(
        [
            # Sets use_sim_time for all nodes started below (doesn't work for nodes started from ignition gazebo) #
            SetParameter(name="use_sim_time", value=True),
            robot_state_publisher_node,
            declare_spawn_x,
            declare_spawn_y,
            declare_spawn_z,
            gz_spawn_entity,
        ]
    )

这个 CLI 允许你传入三个参数:X、Y 和 Z。运行这个 ros_gz_sim/create 节点后,它会把 URDF 模型转换并插入当前正在运行的 Gazebo 仿真中。

生成机器人:

  在 Web Shell 2 中执行

In [ ]:
cd /workspace
In [ ]:
colcon build --packages-select robot_description && source install/setup.bash
In [ ]:
ros2 launch robot_description spawn.launch.py

结果应当类似下图:

Gazebo Sim 中生成的机器人 Gazebo Sim 中生成的机器人 - 局部放大
"Robot Spawn View" "Robot Spawn View CloseUp"

查看 Entity Tree 中的 my_robot 实体。URDF 文件中定义的所有连杆和关节都会显示出来,只有固定关节不会单独列出。机器人模型的详细信息会显示在 Component Inspector 中。对于 SDF 来说,这一步通常不是必须的,因为它会把相关连杆合并显示。

"Robot Entity Tree"

- 练习 2.4.2 -

为 launch 文件新增一个参数,用来设置插入模型的名称。它需要把固定写死的 my_robot 名称替换为 CLI 中传入的名称。

完成后,用不同的名称把你创建的机器人生成两次,并检查 Entity Tree,确认功能是否正常。

预期结果是:执行下面的命令后,得到如下效果:

ros2 launch robot_description spawn.launch.py x:=5 y:=5 model_name:=my_robot_2

"Expected Result Ex24"

- 练习 2.4.2 结束 -
- 练习 2.4.2 参考答案 -

在自己动手尝试之前,不要先看答案。完成练习之后再查看参考答案,会更方便你对比自己的实现。

如果你在完成练习时遇到问题,可以到论坛寻求支持。

spawn.launch.py

- 练习 2.4.2 参考答案结束 -