37 KiB
Introduction to Gazebo Sim with ROS2
卡片名称:Gazebo中使用URDF创建机器人
描述:学习如何使用 URDF 语言在 Gazebo 仿真中创建机器人,最终完成一个可在 Gazebo 中正确加载和验证的移动机器人模型。
目录
- 1 简介
- 2 Gazebo Sim、Ignition Gazebo 与 Gazebo Classic 对比
- 3 为什么使用 URDF 而不是 SDF?
- 4 创建移动机器人
- 5 连杆与关节
- 6 网格模型
- 7 使用 Gazebo Sim 服务生成机器人
- 8 使用 ROS2 Launch 文件生成机器人
- 9 总结
1 简介
预计完成时间:30 秒
在本单元中,你将先梳理 Gazebo Sim、Ignition Gazebo 与 Gazebo Classic 的关系,再学习如何使用 URDF 语言为 Gazebo 仿真创建机器人。后续内容会逐步覆盖连杆、关节、网格模型以及机器人生成方式,最终完成一个可在 Gazebo 中正确加载和验证的移动机器人模型。
2 Gazebo Sim、Ignition Gazebo 与 Gazebo Classic 对比
预计完成时间:5 分钟
什么是 Gazebo Sim?
Gazebo Sim 是一个 3D 动态模拟器,能够在复杂的室内和室外环境中准确高效地模拟机器人群体。虽然与游戏引擎类似,但 Gazebo Sim 提供了更高保真度的物理模拟、一套完整的传感器系统,以及面向用户和程序的接口。
开源机器人基金会(OSRF) 开发并维护 Gazebo Sim。
什么是 Ignition Gazebo?
Gazebo Sim 前身被称为 Ignition Gazebo。2022 年 4 月,Open Robotics 团队宣布弃用 Ignition Gazebo 这一名称,改用 Gazebo Sim。从 Garden 版本开始,Ignition Gazebo 已经完成重新品牌化,本课程也基于 Garden 版本构建,因此统一使用新的 Gazebo Sim 品牌和以 gz 开头的命令行工具。
例如,本课程里会使用 gz model --list 或 gz topic -t /chatter "Hello!" 这样的命令。如果你计划使用较早的 Fortress 版本,则需要把 gz 命令替换成 ign 命令。
参考资料: https://www.openrobotics.org/blog/2022/4/6/a-new-era-for-gazebo
Gazebo Sim 对机器人开发者有什么帮助?
Gazebo Sim 允许你在真实环境的动态模拟中测试机器人和算法。你可以选择多种物理引擎、高级 3D 图形、传感器及噪声模型、机器人插件以及现成的机器人模型库。此外,它运行在 TCP/IP 传输层之上,这使得网络集成变得简单,就像 ROS2 一样。
虽然 Gazebo Classic(版本 11)曾是 ROS 的默认模拟器,但它已经进入生命周期末期,且不适用于 Humble 之后的新版本 ROS2。新的 Gazebo Sim 会逐步取代 Gazebo Classic。
可视化和调试
运行模拟可以提供你所需的关于环境和机器人的所有信息。你甚至可以调试软件和模拟本身,定位问题发生的位置。如果你想测试某种特定情况,也可以强制设定某个场景,反复运行软件直到获得足够的信息。
"快速失败,更快学习"
你不需要在真实机器人上测试软件来发现问题和调整参数。在模拟机器人上失败更快、成本更低,也能比在真实机器人上试错更快地改进软件。
构建机器人太昂贵了!
每个人都想在真实机器人上测试算法,但现实是拥有一台真实机器人太昂贵了。不仅购买构建机器人所需的全部组件很贵,把所有部件组装并协调工作也非常耗时,同时你还需要处理机械、电子和软件等多个方面的问题。
社区支持
Gazebo Sim 是开源软件,由 OSRF 维护。它拥有一个充满活力的社区,鼓励用户使用、改进并彼此提供支持。
Gazebo Sim 与 Gazebo Classic 的比较
Gazebo Sim 为仿真带来了新的工作方式,提供了一整套开发库和云服务工具来简化模拟流程。
它可以帮助你利用高保真的传感器数据流,在接近真实的环境中快速迭代物理设计;你也可以在安全环境下测试控制策略,并把模拟充分用在持续集成测试中。
虽然 Gazebo Classic 的大多数功能已经在 Gazebo Sim 中实现,但仍有部分功能尚未完全补齐。例如,Gazebo Sim 中长期被提及的一个问题是没有完整的“缩放模式”,模型暂时无法像 Classic 那样直接缩放。
参考资料: https://gazebosim.org/docs/fortress/comparison
在场景渲染方面,两者差异比较明显。Gazebo Classic 的画面更暗,在相同光照条件下阴影不够清晰;相比之下,Gazebo Sim 能更好地渲染亮度、光照和阴影细节。
| Gazebo Classic | Gazebo Sim |
|---|---|
![]() |
![]() |
3 为什么使用 URDF 而不是 SDF?
预计完成时间:30 秒
Gazebo Sim 使用 SDF 格式来描述世界、模型、机器人以及构建完整仿真所需的其他组件。那么,为什么这里要用 URDF 来构建机器人呢?
ROS2 的许多工具都是围绕 URDF 构建的,例如 RVIZ2。当你在 Gazebo 仿真中生成一个用 URDF 定义的机器人时,Gazebo Sim 会在底层自动把 URDF 文件转换为 SDF。
在下一单元中,当你把机器人生成到 Gazebo 仿真里并让它与 ROS2 配合工作时,会进一步看到这一点。
4 创建移动机器人
预计完成时间:7 分钟
要创建一个新的机器人模型,你需要按照特定的目录结构来组织机器人的定义文件和文件夹。
除了机器人的定义文件,也就是 URDF 文件之外,你还需要使用 launch 文件来调试机器人的创建过程。
创建一个功能包,并在其中新建以下文件夹:
在 Web Shell 1 中执行
cd /workspace/src
ros2 pkg create robot_description --build-type ament_python --dependencies rclpy urdf xacro
mkdir -p robot_description/urdf
touch robot_description/urdf/robot.urdf
URDF 文件夹将用于存放机器人的描述文件。
将下面的内容粘贴到 robot.urdf 文件中:
robot.urdf
<?xml version="1.0"?>
<robot name="robot">
<!-- Link - chassis -->
<link name="link_chassis">
<inertial>
<mass value="10" />
<origin xyz="0 0 0.3" rpy="0 0 0" />
<inertia ixx="1.5417" ixy="0" ixz="0" iyy="3.467" iyz="0" izz="4.742" />
</inertial>
<collision>
<geometry>
<box size="2 1.3 0.4" />
</geometry>
</collision>
<visual>
<geometry>
<box size="2 1.3 0.4" />
</geometry>
<material name="Red">
<color rgba="1 0 0 1" />
</material>
</visual>
</link>
</robot>
使用下面的命令在该功能包中创建一个新的 launch 文件:
在 Web Shell 1 中执行
cd /workspace/src
mkdir -p robot_description/launch
touch robot_description/launch/rviz.launch.py
launch 文件夹将用于存放使用 RVIZ2 调试该机器人模型所需的 launch 文件。
复制并粘贴以下内容:
rviz.launch.py
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.substitutions import Command
from launch_ros.actions import Node
# 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])}]
)
# RViz2 Launch Configuration (RViz) #
rviz_node = Node(
package='rviz2',
executable='rviz2',
name='rviz2_node',
output='screen',
emulate_tty=True,
parameters=[{'use_sim_time': True}],
)
# Create and Return the Launch Description Object #
return LaunchDescription(
[
robot_state_publisher_node,
rviz_node,
]
)
修改 setup.py 文件,确保 ROS2 Launch 系统能够检测并执行你在上面步骤中创建的 launch 文件。
将下面的内容复制到 setup.py 文件中,直接替换原有内容。
setup.py
import os
from glob import glob
from setuptools import setup
package_name = 'robot_description'
setup(
name=package_name,
version='0.0.0',
packages=[package_name],
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
(os.path.join('share', package_name, 'urdf'), glob('urdf/*.urdf')),
(os.path.join('share', package_name, 'launch'), glob('launch/*.launch.*')),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='user',
maintainer_email='user@todo.todo',
description='TODO: Package description',
license='TODO: License declaration',
entry_points={
'console_scripts': [
],
},
)
新旧文件之间真正不同的只有下面这几行:
# imports
import os
from glob import glob
...
# paths to urdf and launch files that must be copied to 'share' folder
(os.path.join('share', package_name, 'urdf'), glob('urdf/*.urdf')),
(os.path.join('share', package_name, 'launch'), glob('launch/*.launch.*')),
在平台右上角点击“分割右侧”和“远程桌面”,并等待连接。
按照下面的命令来启动你刚刚创建的文件:
在 Web Shell 1 中执行
cd /workspace
colcon build --packages-select robot_description && source install/setup.bash
ros2 launch robot_description rviz.launch.py
启动该文件后,等待图形化工具打开。你应该会看到下图所示的结果。然后,在显示面板中添加 RobotModel,并将 /robot_description 设为描述主题。接着,把 RVIZ2 中的固定坐标系改为 link_chassis。这样就会把机器人的底盘连杆设为 RVIZ2 模型可视化的参考坐标系。
把这个配置保存在你的工作区中,这样下次启动 RVIZ2 时,机器人模型就会被正确显示。 按照下面的命令来创建rviz目录:
新建终端,在 Web Shell 2 中执行
cd /workspace/src
mkdir -p robot_description/rviz
在rviz2界面使用“ctrl+shift+s“,保存当前的 RVIZ2 配置到正确的工作目录下。
将当前的 RVIZ2 配置保存为 config.rviz,并放到你的 ROS2 robot_description 功能包中新建的 rviz 目录里。
把 rviz 文件夹添加到 setup.py 中,这样下次编译时就能检测到 config.rviz。
此时你的 setup.py 文件应该包含下面这三行:
(os.path.join('share', package_name, 'urdf'), glob('urdf/*.urdf')),
(os.path.join('share', package_name, 'rviz'), glob('rviz/*.rviz')),
(os.path.join('share', package_name, 'launch'), glob('launch/*.launch.*')),
最后,把 config.rviz 文件作为参数传给 launch 文件中的 RVIZ 节点。
# Load RViz Configuration File #
rviz_config_file = "config.rviz"
rviz_config_path = os.path.join(package_directory, "rviz", rviz_config_file)
print("RViz Config Loaded !")
# Rviz2 Launch Configuration (RViz)#
rviz_node = Node(
package='rviz2',
executable='rviz2',
name='rviz2_node',
output='screen',
emulate_tty=True,
parameters=[{'use_sim_time': True}],
arguments=['-d', rviz_config_path],
)
现在你的 launch 文件应该如下所示:
rviz.launch.py
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.substitutions import Command
from launch_ros.actions import Node
# 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])}]
)
# Load RViz Configuration File #
rviz_config_file = "config.rviz"
rviz_config_path = os.path.join(package_directory, "rviz", rviz_config_file)
print("RViz Config Loaded !")
# Rviz2 Launch Configuration (RViz)#
rviz_node = Node(
package='rviz2',
executable='rviz2',
name='rviz2_node',
output='screen',
emulate_tty=True,
parameters=[{'use_sim_time': True}],
arguments=['-d', rviz_config_path],
)
# Create and Return the Launch Description Object #
return LaunchDescription(
[
robot_state_publisher_node,
rviz_node,
]
)
5 连杆与关节
预计完成时间:15 分钟
连杆
你已经创建了第一个只有单一元素的机器人模型,也就是一个连杆。那么,一个连杆由哪些部分组成呢?
每个连杆都必须包含以下属性:
惯性属性
它包含 质量(kg)、原点(m) 和 惯性矩阵(kg·m²)。
<inertial>
<mass value="10" />
<origin
xyz="0 0 0.3"
rpy="0 0 0" />
<inertia
ixx="1.5417"
ixy="0"
ixz="0"
iyy="3.467"
iyz="0"
izz="4.742" />
</inertial>
碰撞与可视属性
在这个例子中,collision 和 visual 看起来是相同的,但它们的用途不同。模拟器使用 collision 来计算连杆之间的物理交互;visual 仅用于渲染显示,不会影响连杆与其他对象的交互方式。
如果你的可视网格非常复杂,但又不希望在物理交互上做不必要的高强度计算,或者想节省算力,就可以让 collision 和 visual 使用不同的几何定义。
<collision>
<geometry>
<box size="2 1.3 0.4" />
</geometry>
</collision>
<visual>
<geometry>
<box size="2 1.3 0.4" />
</geometry>
</visual>
关节
关节用于连接各个连杆,并在它们之间建立运动关系。在本单元中,你要构建的是移动机器人,因此接下来要给你创建的底盘加上一只车轮。
继续在 robot.urdf 文件中,在 'link' 结束标签之后添加以下代码:
复制并粘贴
<!-- Joint - chassis / left wheel -->
<joint name="joint_chassis_left_wheel" type="continuous">
<origin rpy="0 0 0" xyz="-0.5 0.65 0" />
<child link="link_left_wheel" />
<parent link="link_chassis" />
<axis rpy="0 0 0" xyz="0 1 0" />
<limit effort="10000" velocity="1000" />
<joint_properties damping="1.0" friction="1.0" />
</joint>
<!-- Link - left wheel -->
<link name="link_left_wheel">
<inertial>
<mass value="1" />
<origin xyz="0 0 0" rpy="0 0 0" />
<inertia ixx="0.002526666666667" ixy="0" ixz="0" iyy="0.002526666666667" iyz="0" izz="0.005"/>
</inertial>
<collision>
<origin rpy="0 0 1.5707" xyz="0 0.18 0" />
<geometry>
<cylinder length="0.12" radius="0.4"/>
</geometry>
</collision>
<visual>
<origin rpy="0 0 1.5707" xyz="0 0.18 0" />
<geometry>
<cylinder length="0.12" radius="0.4"/>
</geometry>
</visual>
<collision>
<origin rpy="0 0 1.5707" xyz="0 0.06 0" />
<geometry>
<cylinder length="0.12" radius="0.08"/>
</geometry>
</collision>
<visual>
<origin rpy="0 0 1.5707" xyz="0 0.06 0" />
<geometry>
<cylinder length="0.12" radius="0.08"/>
</geometry>
</visual>
</link>
车轮连杆定义
这里你有两组 collision 和 visual 标签,它们各自对应不同的部分。一个连杆中可以包含多组这样的标签,数量并不受限。它们都会按照关节的位置关系显示出来。
在这个例子里,你分别为车轮本体和它的轮轴创建了一组可视与碰撞定义。
重新启动 rviz.launch.py 文件。
在 Web Shell 1 中执行
cd /workspace
colcon build --packages-select robot_description && source install/setup.bash
ros2 launch robot_description rviz.launch.py
你应该得到如下结果:
但是等等……车轮去哪了?这里发生了什么?
这是因为你当前只运行了 Robot State Publisher 和 RViz。这个节点只会发布机器人固定关节的状态(这里是底盘),不会发布可动关节的状态(例如车轮)。如果想把这些可动关节显示出来,你还需要一个能够提供关节状态的节点,这就是 Joint State Publisher GUI 的作用。
要启动 Joint State Publisher GUI,请在一个新的 web shell 中运行以下命令:
在 Web Shell 2 中执行
ros2 run joint_state_publisher_gui joint_state_publisher_gui
提示: 如果无法运行请先执行:
sudo apt update
sudo apt install ros-humble-joint-state-publisher-gui
unset PYTHONPATH
source /opt/ros/humble/setup.bash
现在你会看到类似下面的界面:
注意,现在你已经可以通过滑块来控制车轮旋转了。
当然,这还不是车轮期望的安装位置或旋转方式。那么问题出在哪里呢?
关节定义
来看一下关节定义:
<joint name="joint_chassis_left_wheel" type="continuous">
<origin rpy="0 0 0" xyz="-0.5 0.88 0" />
<child link="link_left_wheel" />
<parent link="link_chassis" />
<axis rpy="0 0 0" xyz="0 1 0" />
<limit effort="10000" velocity="1000" />
<joint_properties damping="1.0" friction="1.0" />
</joint>
第一个属性 type 用来定义该连杆能够进行哪一种运动。常见类型包括 continuous、revolute、prismatic 和 fixed。除此之外还有其他类型,你可以在这里查看完整参考:http://wiki.ros.org/urdf/XML/joint。
origin 属性用于定义关节相对于底盘的位置。底盘连杆起始于 0 0 0,长度为 2 米,宽度为 1.3 米。底盘会向两侧延伸,因此它的侧边界位于 1.3/2,也就是 0.65 米 的位置。
接着,需要定义 child 和 parent 连杆。再比如 axis,它用来指定转动轴。你还可以通过 limit 设置最大力矩和速度,不过现在这部分并不是重点。当前阶段你主要是在设计机器人的几何结构,而与运动动力学相关的 joint properties 会在后续再讨论。
最后来看 axis 定义。这里决定该连杆绕哪一个坐标轴旋转。在这个例子中,你希望它绕 Y 轴旋转,这个设置是正确的。
修正连杆定义以匹配关节旋转
问题在于,你的这个 link 圆柱几何体(长度和半径)是按照 Z 轴方向定义的,因此需要绕 X 轴旋转一下才能修正。
回到左轮的连杆定义,把 collision 和 visual 标签中的 origin 属性改成绕 X 轴旋转 90 度:
<link name="link_left_wheel">
...
<collision>
<origin rpy="1.5707 0 0" xyz="0 0.18 0" />
<geometry>
<cylinder length="0.12" radius="0.4"/>
</geometry>
</collision>
<visual>
<origin rpy="1.5707 0 0" xyz="0 0.18 0" />
<geometry>
<cylinder length="0.12" radius="0.4"/>
</geometry>
</visual>
<collision>
<origin rpy="1.5707 0 0" xyz="0 0.06 0" />
<geometry>
<cylinder length="0.12" radius="0.08"/>
</geometry>
</collision>
<visual>
<origin rpy="1.5707 0 0" xyz="0 0.06 0" />
<geometry>
<cylinder length="0.12" radius="0.08"/>
</geometry>
</visual>
...
重新启动 rviz.launch,并同时运行 Joint State Publisher GUI。
在 Web Shell 1 中执行
cd /workspace
colcon build --packages-select robot_description && source install/setup.bash
ros2 launch robot_description rviz.launch.py
在 Web Shell 2 中执行
unset PYTHONPATH
source /opt/ros/humble/setup.bash
ros2 run joint_state_publisher_gui joint_state_publisher_gui
查看下图中的机器人尺寸,你会更容易理解为什么 XML 定义里使用的是这些数值:
练习 2.5.1
完善当前模型并把它补全!
在这个练习中,再补充以下几个部分,让机器人更完整:
- 添加右侧车轮
- 给两个车轮都添加新的 visual 元素,让旋转效果更明显(例如轮毂盖)
- 在机器人前方添加一个通过 fixed 关节连接的脚轮
- 修改并补充底盘和车轮的材质颜色,让它看起来尽量接近下图
请参考下图来创建脚轮的固定关节以及可视部件。
注意: 当前 ROS2 RVIZ2 中存在一个 Bug:URDF 中父连杆的颜色会传递到子连杆上,因此 RVIZ 中显示的颜色可能与 URDF 文件中的定义并不完全一致。更多信息请参考 RVIZ2 的颜色 Bug。
练习 2.5.1 结束
练习 2.5.1 参考答案
在你自己尝试完成练习之前,不要先看答案。完成之后再回头对照参考答案,会更有助于比较和检查自己的实现。
如果你在完成练习时遇到问题,可以到论坛寻求支持。
练习 2.5.1 参考答案结束
参考资料
你可以在下面查看连杆和关节的完整规范(链接指向 ROS1 文档):
在使用 XML 为 ROS2 定义机器人模型时,这些说明依然适用。
6 网格模型
预计完成时间:10 分钟
网格模型可以用来创建更复杂的三维几何形状。设想一下,你手上有一台真实机器人,整个项目是用 CAD 软件完成的,现在你想对它进行仿真,那么你通常会直接拥有模型文件。URDF 支持 .stl 和 .dae 文件,其中后者通常能提供更好的显示效果。
下面来看如何在你当前制作的机器人中使用网格模型。
进入底盘的 visual 部分,并将下面这段代码替换掉:
<geometry>
<box size="2 1.3 0.4" />
</geometry>
替换为以下内容:
复制并粘贴
<geometry>
<mesh filename="package://robot_description/meshes/chassis.stl" />
</geometry>
URDF 支持 package:// 这种写法,可以通过相对路径更方便地找到文件。这也意味着你的功能包中必须有一个 meshes 文件夹。先创建这个文件夹,然后上传下面的文件:
新建终端,在 Web Shell 3 中执行
mkdir -p /workspace/src/robot_description/meshes/
将这个 chassis.stl 文件放进你在 robot_description 功能包里创建的 meshes 文件夹。
把 meshes 文件夹添加到 setup.py 中,这样下次编译时就能检测到 *.stl 文件。
此时你的 setup.py 文件应该包含下面这四行:
(os.path.join('share', package_name, 'urdf'), glob('urdf/*.urdf')),
(os.path.join('share', package_name, 'rviz'), glob('rviz/*.rviz')),
(os.path.join('share', package_name, 'meshes'), glob('meshes/*.stl')),
(os.path.join('share', package_name, 'launch'), glob('launch/*.launch.*')),
重新启动 rviz.launch 和 Joint State Publisher GUI,你应该看到如下结果:
注意,这个 .stl 文件会沿用基础几何体所设置的材质颜色。
练习 2.6.1
为车轮也添加一个网格模型文件。
使用下面这个文件:
调整轮毂盖的可视效果,让机器人尽量接近下图所示的样子:
练习 2.6.1 结束
练习 2.6.1 参考答案
在自己尝试完成练习之前,不要先看答案。完成后再查看参考答案,会更方便你对比自己的实现。
如果你在完成练习时遇到问题,可以到论坛寻求支持。
练习 2.6.1 参考答案结束
7 使用 Gazebo Sim 服务生成机器人
预计完成时间:5 分钟
你已经把机器人搭建好了,它已经有了底盘和车轮,因此已经可以用于仿真了。下面来看看如何把它放进 Gazebo 仿真环境中。
你可以通过两种方式把机器人模型生成到 Gazebo Sim 中:
- 使用 Gazebo Sim 生成服务
- 使用 ROS 2 Launch 文件
使用 Gazebo Sim 生成服务
虽然这不是把模型生成到 Gazebo Sim 中的首选方法,但你仍然可以借此观察模型在 Gazebo Sim 中生成后的行为。
这种方式会使用 Gazebo Sim 的 create 服务,它采用的是 EntityFactory 消息类型。
注意:
开始之前,请关闭并终止所有 web shell 中正在运行的任务。对所有活跃的 web shell 按下 Ctrl + C 来停止当前进程。
修改“远程桌面”面板,切换到“仿真器”。
首先需要设置环境变量,为 Gazebo Sim 指定资源路径。
把模型的 URDF 文件路径以及相关网格文件路径都配置进去。
在 Web Shell 1 中执行
export GZ_SIM_RESOURCE_PATH=/workspace/src/robot_description/urdf:/workspace/src
现在以无头模式启动 Gazebo Sim 世界,这里我们使用空世界。
在 Web Shell 1 中执行
gz sim -s -r empty.sdf
在 Web Shell 2 中执行
bash /workspace/src/run_websocket.sh
在另一个终端中,使用下面的命令列出所有可用的服务:
在 Web Shell 3 中执行
gz service --list
执行完上面的命令后,你会在仿真器里看到加载的空世界,并在输出中看到一个名为 /world/empty/create 的服务。通用的服务名称格式为 /world/<name_of_world>/create。例如,如果你的 world 文件中的世界名称是 factory_floor,那么服务名就会是 /world/factory_floor/create。
继续在同一个终端中,查看该服务的信息,以确认请求和响应所使用的消息类型。
注意: 和 ROS2 命令不同,这个命令不支持 Tab 自动补全,所以需要手动输入完整命令。
在 Web Shell 3 中执行
gz service --info --service /world/empty/create
执行完该命令后,你应该会看到与下图类似的输出:
Service providers [Address, Request Message Type, Response Message Type]:
tcp://192.168.32.7:37923, gz.msgs.EntityFactory, gz.msgs.Boolean
然后在同一个终端中输入下面的命令,将你的 URDF 模型生成到 Gazebo Sim 中:
注意: 和 ROS2 命令不同,这个命令不支持 Tab 自动补全,所以需要手动输入完整命令。
在 Web Shell 3 中执行
cd /workspace/src/robot_description/urdf
gz service --service /world/empty/create \
--reqtype gz.msgs.EntityFactory \
--reptype gz.msgs.Boolean \
--timeout 1000 \
--req 'sdf_filename: "robot.urdf" name: "my_robot"'
注意:--req 参数中的内容不能包含空格。如果你写入了空格,就会收到下面这样的报错:
The following arguments were not expected: "my_robot" name: "robot.urdf",
如果你正确完成了前面的步骤,那么执行生成服务命令后应该会返回 data: true。
查看 Gazebo Sim 界面时,你会发现机器人模型已经生成在 x = 0.0, y = 0.0, and z = 0.0 的位置。
8 使用 ROS2 Launch 文件生成机器人
预计完成时间:10 分钟
这是把模型生成到 Gazebo Sim 中最推荐的方法。通过这种可控的生成方式,你可以在需要时自动把模型插入 Gazebo Sim。
在 launch 文件夹中创建一个新文件 empty_world.launch.py,并写入相应内容。
在 Web Shell 3 中执行
touch /workspace/src/robot_description/launch/empty_world.launch.py
现在,把下面的代码复制到你刚刚创建的 launch 文件中。
empty_world.launch.py
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 resources 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 "GZ_SIM_RESOURCE_PATH" in os.environ:
for resource_path in gazebo_resource_paths:
if resource_path not in os.environ["GZ_SIM_RESOURCE_PATH"]:
os.environ["GZ_SIM_RESOURCE_PATH"] += (':' + resource_path)
else:
os.environ["GZ_SIM_RESOURCE_PATH"] = (':'.join(gazebo_resource_paths))
# Load the empty world in headless server mode #
world_file = "empty.sdf"
world_config = LaunchConfiguration("world")
declare_world_arg = DeclareLaunchArgument(
"world",
default_value=["-s -r ", world_file],
description="Gazebo Sim world and startup flags",
)
# 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,
]
)
启动仿真:
在 Web Shell 1 中执行
cd /workspace
colcon build --packages-select robot_description && source install/setup.bash
ros2 launch robot_description empty_world.launch.py
在 Web Shell 2 中执行
bash /workspace/src/run_websocket.sh
等待仿真器界面刷新,你现在启动的是一个空世界。建议把生成机器人的逻辑放到单独的文件中,这样在开发过程中就可以不重启整个仿真,而是直接删除并重新生成机器人。
在launch目录下新建一个 spawn.launch.py 文件,并填入下面的内容:
在 Web Shell 3 中执行
touch src/robot_description/launch/spawn.launch.py
spawn.launch.py
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 3 中执行
cd /workspace
colcon build --packages-select robot_description && source install/setup.bash
ros2 launch robot_description spawn.launch.py
结果应当类似下图:
练习 2.8.1
为 launch 文件新增一个参数,用来设置插入模型的名称。它需要把固定写死的 my_robot 名称替换为 CLI 中传入的名称。
预期结果是:执行下面的命令后,得到如下效果:
ros2 launch robot_description spawn.launch.py
# 生成完后退出
ros2 launch robot_description spawn.launch.py x:=5 y:=5 model_name:=my_robot_2
练习 2.8.1 结束
练习 2.8.1 参考答案
在自己动手尝试之前,不要先看答案。完成练习之后再查看参考答案,会更方便你对比自己的实现。
如果你在完成练习时遇到问题,可以到论坛寻求支持。
练习 2.8.1 参考答案结束
9 总结
预计完成时间:30 秒
在本单元中,你学习了使用 URDF 构建机器人模型所需的基础元素,包括连杆、关节以及其他关键组件。你还动手创建了自己的机器人,并把它生成到 Gazebo 中,确保模型能够被正确加载,并准备好在仿真环境中进行交互。















