使用 ROS 2 Launch 启动 Gazebo
可以通过多种方式从 ROS 2 launch 系统启动 Gazebo。
使用 ros_gz_sim 包自带的 launch 文件
Section titled “使用 ros_gz_sim 包自带的 launch 文件”ros_gz_sim 包提供了两个 launch 文件:gz_server.launch.py 和 gz_sim.launch.py,分别用于启动 Gazebo server,或启动完整的 Gazebo(server 和 GUI)。
ros2 launch ros_gz_sim gz_sim.launch.py gz_args:=empty.sdf或者只启动 server:
ros2 launch ros_gz_sim gz_server.launch.py world_sdf_file:=empty.sdf请查看各 launch 文件的参数块(这里 和 这里),了解每个 launch 文件所接受的参数。
使用自定义 launch 文件
Section titled “使用自定义 launch 文件”也可以从自定义 launch 文件启动 Gazebo。为此提供了自定义的 <gz_server/> 标签,可在 XML launch 文件中使用。此时,参数通过标签内的属性传入。下面是启动 Gazebo server 的示例:
<launch> <arg name="world_sdf_file" default="empty.sdf" /> <arg name="world_sdf_string" default="" /> <arg name="container_name" default="ros_gz_container" /> <arg name="create_own_container" default="False" /> <arg name="use_composition" default="False" /> <gz_server world_sdf_file="$(var world_sdf_file)" world_sdf_string="$(var world_sdf_string)" container_name="$(var container_name)" create_own_container="$(var create_own_container)" use_composition="$(var use_composition)"> </gz_server></launch>此时,<gz_server> 的参数从命令行读取。这只是其中一种方式——也可以硬编码部分值,或者根本不使用全部参数。
Python
Section titled “Python”与 XML launch 文件相比,Python launch 文件提供了更底层的定制和逻辑控制能力。例如,可以设置环境变量,加入 Python 特有的函数和逻辑。下面的示例中,可将示例包、world 和桥接 topic 替换为自己的内容。它更像是一个脚手架,而非可以直接独立运行的文件。
from ament_index_python.packages import get_package_share_directoryfrom launch import LaunchDescriptionfrom launch_ros.actions import Nodefrom launch.actions import SetEnvironmentVariable, IncludeLaunchDescriptionfrom launch.launch_description_sources import PythonLaunchDescriptionSourcefrom launch.substitutions import PathJoinSubstitutionfrom launch_ros.substitutions import FindPackageShare
def generate_launch_description(): ros_gz_sim_pkg_path = get_package_share_directory('ros_gz_sim') example_pkg_path = FindPackageShare('example_package') # Replace with your own package name gz_launch_path = PathJoinSubstitution([ros_gz_sim_pkg_path, 'launch', 'gz_sim.launch.py'])
return LaunchDescription([ SetEnvironmentVariable( 'GZ_SIM_RESOURCE_PATH', PathJoinSubstitution([example_pkg_path, 'models']) ), SetEnvironmentVariable( 'GZ_SIM_PLUGIN_PATH', PathJoinSubstitution([example_pkg_path, 'plugins']) ), IncludeLaunchDescription( PythonLaunchDescriptionSource(gz_launch_path), launch_arguments={ 'gz_args': PathJoinSubstitution([example_pkg_path, 'worlds/example_world.sdf']), # Replace with your own world file 'on_exit_shutdown': 'True' }.items(), ),
# Bridging and remapping Gazebo topics to ROS 2 (replace with your own topics) Node( package='ros_gz_bridge', executable='parameter_bridge', arguments=['/example_imu_topic@sensor_msgs/msg/Imu@gz.msgs.IMU',], remappings=[('/example_imu_topic', '/remapped_imu_topic'),], output='screen' ), ])下面是另一个示例,使用 Python 中的高层 action 来启动 gzserver:
from launch import LaunchDescriptionfrom launch.actions import DeclareLaunchArgumentfrom launch.substitutions import LaunchConfigurationfrom ros_gz_sim.actions import GzServer
def generate_launch_description():
declare_world_sdf_file_cmd = DeclareLaunchArgument( 'world_sdf_file', default_value='', description='Path to the SDF world file')
# Create the launch description and populate ld = LaunchDescription([ GzServer( world_sdf_file=LaunchConfiguration('world_sdf_file') ), ])
# Declare the launch options ld.add_action(declare_world_sdf_file_cmd)
return ld与 ros_gz_bridge 一起启动
Section titled “与 ros_gz_bridge 一起启动”XML 示例 launch 文件可在 此处 查看。 Python 示例 launch 文件可在 此处 查看。
直接从终端使用这些 launch 文件的示例命令:
ros2 launch ros_gz_sim ros_gz_sim.launch.py world_sdf_file:=empty.sdf bridge_name:=ros_gz_bridge config_file:=<path_to_your_YAML_file> use_composition:=True create_own_container:=True在上面的 launch 文件中,可能会注意到 ros_gz_bridge 的 create_own_container 参数被硬编码为 False。这样做是为了避免创建两个重复的 container(一个用于 gz_server,另一个用于 ros_gz_bridge),而是让 ros_gz_bridge 复用 gz_server 创建的 container。更多信息可参阅 此 PR。
关于 ros_gz_bridge 的更多信息,请参阅 ROS 2 集成。
关于组合的更多信息,请参阅 组合。
关于 ROS 2 Control 的进一步注意事项
Section titled “关于 ROS 2 Control 的进一步注意事项”如果打算将 ros2_control 与 Gazebo 配合使用,请查看 gz_ros2_control 仓库中的 示例 launch 文件。
为了让 controller_manager 正常工作,必须发布 /clock topic:
gz_bridge = Node( package="ros_gz_bridge", executable="parameter_bridge", arguments=['/clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock'], parameters=[{ "qos_overrides./tf_static.publisher.durability": "transient_local" }], output="screen",)如果没有发布 /clock topic,controller_manager 会发出类似下面的警告或错误:
[gazebo-1] [WARN] [1744219953.983130822] [controller_manager]: No clock received, using time argument instead! Check your node's clock configuration (use_sim_time parameter) and if a valid clock source is available.时序问题(Timing Issues)
Section titled “时序问题(Timing Issues)”默认情况下,由 gz_ros2_control 启动的 controller_manager 的 use_sim_time 为 true。如果由于某种原因将其设置为 false,它会回退到系统时钟。
这会导致如下日志:
[gazebo-1] [INFO] [1744209678.974210234] [gz_ros_control]: Loading controller_manager[gazebo-1] [INFO] [1744209679.000651931] [controller_manager]: Using Steady (Monotonic) clock for triggering controller manager cycles.最终导致致命错误:
[gazebo-1] terminate called after throwing an instance of 'std::runtime_error'[gazebo-1] what(): can't compare times with different time sources在使用仿真时间时,请确保 use_sim_time 正确设置为 true,以避免此类不匹配。