Tutorial 5: Simulation

Objective: This tutorial session is devoted to learn the basic issues related to simulation using Gazebo within a ROS environment. The Gazebo version used will be Ignition Fortress, which is the one recommended for ROS 2 Humble (see Summary of Compatible ROS and Gazebo Combinations).

Contents

After this session you will be able to:

  • Understand the Gazebo simulation infrastructure: the files to model the world, the simulation server and the client, the GUI and the plugins.

  • Understand how Gazebo is integrated within ROS by means of the ros_gz metapackage.

  • Use Gazebo from ROS by configuring launch files and tunning the urdf robot models.

  • Understand the mechanisms for the simulation of sensors, and use a simulated camera and a fiducial marker detector to determine the pose of objects.

References:

This tutorial is partially based on several of the tutorials from gazebosim.org.

Complementary resources: Gazebosim in Github

Provided packages:

This tutorial will use the following package:

Package ignition_gazebo_sensors_tutorial
Package ignition_ros2_srv_bridge
Package aruco_ros
Package rrbot_description
Package rrbot_gazebo

Prepare the colcon workspace to work with it:

  1. If you have not yet done so, follow the steps in Setup to:

  • clone in your ~/data/git-projects folder the meta-repository containing all the required packages,

  • create the colcon workspace folders (use colcon_ws5 for the current tutorial).

  1. Follow the procedure shown in Procedure to prepare the exercises to import in the intro2ros/2025/teamXX/EXERCISE5 subgroup of your GitLab account the packages:

  1. In your git-projects folder clone the ignition_gazebo_sensors_tutorial and ignition_ros2_srv_bridge projects of your GitLab account inside an exercise5 subfolder (change XX by your team number). You can use https:

git clone https://gitlab.com/intro2ros/2025/teamXX/exercise5/ignition_gazebo_sensors_tutorial.git exercise5/ignition_gazebo_sensors_tutorial
git clone https://gitlab.com/intro2ros/2025/teamXX/exercise5/ignition_ros2_srv_bridge.git exercise5/ignition_ros2_srv_bridge

or ssh:

git clone git@gitlab.com:intro2ros/2025/teamXX/exercise5/ignition_gazebo_sensors_tutorial.git exercise5/ignition_gazebo_sensors_tutorial
git clone git@gitlab.com:intro2ros/2025/teamXX/exercise5/ignition_ros2_srv_bridge.git exercise5/ignition_ros2_srv_bridge
  1. In your colcon_ws5/src folder create a symbolic link to the package to be used:

ln -s ~/data/git-projects/exercise5/ignition_gazebo_sensors_tutorial
ln -s ~/data/git-projects/exercise5/ignition_ros2_srv_bridge
ln -s ~/data/git-projects/all_introduction_to_ros2_packages/aruco_ros
ln -s ~/data/git-projects/all_introduction_to_ros2_packages/rrbot_description
ln -s ~/data/git-projects/all_introduction_to_ros2_packages/rrbot_gazebo
  1. If you are working at the ETSEIB computer rooms, follow the steps in Configuring Gazebo section to configure Gazebo.

Gazebo basics

Gazebo is a simulation tool for robotic systems that has a robust physics’ engine, high-quality graphics, and convenient programmatic and graphical interfaces. It is a standalone application that is also integrated as a ROS package.

Gazebo versions

There are two major versions of Gazebo: Gazebo Classic, and Gazebo (formerly Ignition).

  • Gazebo Classic releases use numbers to track versions. Gazebo 11 is the last major release and has already reached its end-of-life on 2025-01-29.

  • Gazebo uses lettered release names (e.g. Fortress, Garden, Harmonic, etc); all releases can be found in this table, being the Fortress release the one recommended to work with ROS 2 Humble.

In this tutorial the Gazebo version we will use is Ignition Fortress.

If you have the need to update packages that still use Gazebo Classic you can follow these guidelines: Migrating ROS 2 packages that use Gazebo Classic.

Gazebo files

To run a Gazebo simulation you need:

  • A world file: A file with extension .sdf that contains all the elements in a simulation, including robots, lights, sensors, and static objects, formatted using the Simulation Description Format (SDF).

    <?xml version="1.0" ?>
    <sdf version="1.8">
     <world name="world_demo">
       <physics name="1ms" type="ignored"> <max_step_size>0.001</max_step_size> <real_time_factor>1.0</real_time_factor></physics>
       <plugin filename="ignition-gazebo-physics-system" name="gz::sim::systems::Physics"> </plugin>
       <plugin filename="ignition-gazebo-user-commands-system" name="gz::sim::systems::UserCommands"></plugin>
       <plugin filename="ignition-gazebo-scene-broadcaster-system" name="gz::sim::systems::SceneBroadcaster"></plugin>
       ...
       <light type="directional" name="sun"> ...</light>
       <model name="ground_plane"> ... </model>
       <model name="my model"> ... </model>
       ...
       ...
     </world>
    </sdf>
    

    The components in a world file are specified using different tags:

    • physics: This tag specifies the type and properties of the dynamic engine.

    • plugin: This tag specifies dynamically loaded chunk of code for e.g. simulating the dynamics of the world (Physics plugin), managing models with user commands (UserCommands plugin) or showing the world scene (SceneBroadcaster plugin).

    • gui: This tag allows to include plugins to cutomize the GUI.

    • light: This tag specifies the light source in the world.

    • model: This tag is used to populate the world with models.

    • include: This tag is used to include files describing models (see below).

    The components of a model are:

    • Links: A link contains the physical properties of one body of the model. It contains collision elements to encapsulate the geometry used for collision checking, (optional) visual elements used to visualize parts of a link, an inertial element to describe the dynamic properties of the link such as mass and rotational inertia matrix, (optional) sensor elements to collect data from the world for use in plugins, (optional) light elements to describe light sources attached to the link.

    • Joints (optional): A joint connects two links. A parent and child relationship is established along with other parameters such as axis of rotation, and joint limits.

    • Plugins (optional): A plugin is a shared library created by a third party to control a model.

    <model name="horseB1">
     <pose>0 0 0.5 0 0 0</pose>
      <link name="link">
        <collision name="collision">
         <geometry>
           <box>
             <size>1 1 1</size>
           </box>
         </geometry>
        </collision>
        <visual name="visual">
         <geometry>
           <uri>model://horseB1/meshes/horseB1.dae</uri>
         </geometry>
        </visual>
      </link>
    </model>
    
  • Model files: To better structure the information, models are usually described in SDF files, one per file, i.e. they contain a single <model> … </model>. Model files can be retrieved from the Gazebo Fuel website, where there are hundreds of models already available, or locally stored. In this later case, we need to set IGN_GAZEBO_RESOURCE_PATH environment variable to the parent folder of the model files, e.g.:

    export IGN_GAZEBO_RESOURCE_PATH="/absolute/path/to/models"
    
    <?xml version="1.0" ?>
    <sdf version="1.5">
      <world name="default">
        <include>
          <uri>
            model://Coke
          </uri>
        </include>
        <include>
          <uri> https://fuel.gazebosim.org/1.0/OpenRobotics/models/Coke </uri>
        </include>
       </world>
     </sdf>
    

In the SDF worlds tutorial you can find more information on how to build your world using SDF, and how to add models to it.

Exercise

Take a look at the camera_world.sdf file in the worlds folder of the ignition_gazebo_sensors_tutorial package, and at the pawnB1.sdf file in the models/pawnB1 folder.

Gazebo executables

The following command starts Gazebo:

ign gazebo the_world_to_be_opened

This command actually starts two processes:

  • ign gazebo server: Is the core of gazebo, it runs the physics update-loop and sensor data generation, and can be run independently of a graphical interface.

  • ign gazebo gui: Is the user interface; multiple instances can be executed.

The communication is handled by the Gazebo Transport library. See the Gazebo Sim Architecture tutorial for details.

Caution

Always quit Gazebo using the “Quit” option from the menu. If you just close the GUI window, the ign gazebo server process may remain running and cause problems when starting Gazebo again

The GUI

Gazebo uses some environment variables to locate files:

  • IGN_GAZEBO_RESOURCE_PATH: colon-separated set of directories where Gazebo will search for resources such as world and model files.

  • IGN_GAZEBO_SYSTEM_PLUGIN_PATH: colon-separated set of directories where Gazebo will search for the plugin shared libraries at runtime (the installed plugins are usually found at /usr/lib/x86_64-linux-gnu/ign-gazebo-6/plugins).

Exercise

Set in your ~/.bashrc:

  • the variable IGN_GAZEBO_RESOURCE_PATH with the worlds folder of the installed Gazebo version, and the worlds and models folders of the ignition_gazebo_sensors_tutorial package (revise and change the path if needed)

  • the variable IGN_GAZEBO_SYSTEM_PLUGIN_PATH to /usr/lib/x86_64-linux-gnu/ign-gazebo-6/plugins.

export IGN_GAZEBO_RESOURCE_PATH=/usr/share/ignition/ignition-gazebo6/worlds:~/data/colcon_ws5/src/ignition_gazebo_sensors_tutorial/models:~/data/colcon_ws5/src/ignition_gazebo_sensors_tutorial/worlds:${IGN_GAZEBO_RESOURCE_PATH}

export IGN_GAZEBO_SYSTEM_PLUGIN_PATH=/usr/lib/x86_64-linux-gnu/ign-gazebo-6/plugins:${IGN_GAZEBO_SYSTEM_PLUGIN_PATH}

Source the ~/.bashrc to make the change effective, and open the camera_world.sdf file from the ignition_gazebo_sensors_tutorial package and the shapes.sdf world from the default set of installed worlds (recall to close the GUI with the Quit option before opening it again):

source ~./bashrc
ign gazebo camera_world.sdf
ign gazebo shapes.sdf

Information on the GUI options and mouse control can be found in the Understanding the GUI tutorial.

Gazebo plugins

A plugin is a chunk of code that is compiled as a shared library and inserted into the simulation. Plugins have direct access to all the functionality of Gazebo through the standard C++ classes.

The Gazebo Fortress architecture is based on the Entity Component System (ECS) software architectural pattern, which allows plugins, referred to as “systems”, to interact with various simulation components in a very flexible manner, i.e. they can access and modify multiple simulation aspects (physics, visuals, sensors, etc.) in a unified way.

Some plugins that where required in previous versions of Gazebo (i.e. Gazebo classic), mainly related to sensors, are no longer needed because of the integration of their functionalities into the core architecture of Gazebo Fortress. This integration streamlines simulation setup and reduces the need for external plugins. For instance, the model of a camera just needs to define the features of the image sensor, that is, no explicit plugin is required to publish the images, which are automatically published on the topic /camera_name/image.

<model name="camera1">
  <link name="camera_link">
    <inertial>
         ···
    </inertial>
    <collision name="collision">
         ···
    </collision>
    <visual name="visual">
         ···
    </visual>
    <sensor type="camera" name="camera1_sensor">
      <gz_frame_id>camera_link_optical</gz_frame_id>
      <camera>
        <horizontal_fov>1.047</horizontal_fov>
        <image>
          <width>1920</width>
          <height>1080</height>
        </image>
        <clip>
          <near>0.1</near>
          <far>100</far>
        </clip>
      </camera>
      <always_on>1</always_on>
      <update_rate>30</update_rate>
      <visualize>true</visualize>
    </sensor>
  </link>
</model>

If, however, you want to track the camera’s pose, you can add the PosePublisher system plugin at the model level like this:

<model name="camera1">
  <link name="camera_link">
    <inertial>
         ···
    </inertial>
    <collision name="collision">
         ···
    </collision>
    <visual name="visual">
         ···
    </visual>
    <sensor type="camera" name="camera1_sensor">
         ···
    </sensor>
    <plugin filename="libignition-gazebo-pose-publisher-system.so" name="ignition::gazebo::systems::PosePublisher">
        <publish_sensor_pose>true</publish_sensor_pose>
    </plugin>
  </link>
</model>

This system plugin will publish the transform of the camera in the form of gz::msgs::Pose message.

The different types of plugins are based on the simulation phase:

  • Update: Plugins that are run every simulation tick, e.g. the TouchPlugin checks if a model has touched some specific target for a given time continuously and exclusively. See the TouchPlugin.cc file that has the method TouchPlugin::Update where this check is actually done.

  • PreUpdate: Plugins that are run before the simulation step, e.g. the JointController calculates and applies the necessary forces or torques to achieve desired joint positions or velocities. See the JointController.cc file that has the method JointController::PreUpdate where these computations are actually done.

  • PostUpdate: Plugins that are run after the simulation step, e.g. the PosePublisher updates the poses after the simulation step, of duration one tick or more. See the PosePublisher.cc file that has the method PosePublisher::PostUpdate where the publish is actually done.

  • GUI: Plugins that are used by Gazebo GUI clients to provide interactive elements.

All system plugins can be found here, and their implementation in this repository.

The plugins detailed in the following subsections are usually included in the world files, like in the camera_world.sdf in the worlds folder of the ignition_gazebo_sensors_tutorial package. Each provides runtime capabilities via Ignition Transport interfaces (topics and services).

The Scene Broadcaster System plugin

Purpose: Broadcasts the scene graph to connected GUI clients (e.g., gz-gui), allowing remote visualization and interaction.

<plugin filename="ignition-gazebo-scene-broadcaster-system" name="gz::sim::systems::SceneBroadcaster"/>

Interfaces:

  • Topic /world/<world_name>/scene/info: Scene graph with entity data.

The Sensors System plugin

Purpose: Simulates sensor components like cameras, lidars, and IMUs.

<plugin filename="ignition-gazebo-sensors-system" name="gz::sim::systems::Sensors">
  <render_engine>ogre2</render_engine>
</plugin>

Interfaces (examples, vary by sensor):

  • Topic /world/<world>/model/<model>/link/<link>/sensor/<sensor>/image: Camera images.

  • Topic /world/<world>/model/<model>/link/<link>/sensor/<sensor>/scan: Lidar scans.

  • Topic /world/<world>/model/<model>/link/<link>/sensor/<sensor>/imu: IMU readings.

The UserCommands System plugin

Purpose: Enables runtime manipulation of entities such as spawning, deleting, and moving models.

<plugin filename="ignition-gazebo-user-commands-system" name="gz::sim::systems::UserCommands"/>

Interfaces:

  • Service /world/<world_name>/create: Create a new entity from SDF.

  • Service /world/<world_name>/remove: Remove an existing entity.

  • Service /world/<world_name>/set_pose: Set the pose of an entity.

Exercise

Open Gazebo with the camera_world:

ros2 launch ignition_gazebo_sensors_tutorial camera_ignition_gazebo.launch.py

The topic /world/camera_world/pose/info is provided by the SceneBroadcaster system plugin.

Get the information of this topic (and of the Pose messages) and echo it.

ign topic -i -t /world/camera_world/pose/info
ign msg -i ignition.msgs.Pose_V
ign msg -i ignition.msgs.Pose
ign topic -e -t /world/camera_world/pose/info

Apply the service /world/camera_world/set_pose to change the position of the pawnB1 object:

ign service -s /world/camera_world/set_pose \
  --reqtype ignition.msgs.Pose \
  --reptype ignition.msgs.Boolean \
  --req 'name: "kingW", position: { x: 0.2, y: 0.2, z: 0.01 }, orientation: { x: 0.0, y: 0.0, z: 0.0, w: 1.0 }' \
  --timeout 2000

Integration between ROS and Gazebo

The integration between ROS and Gazebo is managed by a set of packages organized in the ros_gz repository (the humble branch is for the integration between ROS 2 Humble and Gazebo Fortress).

The following subsections will explain this information:

  • The contents of ros_gz repository and how to configure the package.xml and CMakeLists.txt files to work with it.

  • The ros_gz_bridge package: its purpose and parameters.

  • How to configure the launch file to work with ros_gz.

  • How to spawn models in Gazebo.

The ros_gz repository

The ros_gz repository holds the following packages:

  • ros_gz_sim: This package contains launch files and utilities to start Ignition Gazebo and to spawn models and worlds into the simulator.

  • ros_gz_bridge: This package provides a bidirectional network bridge which enables the exchange of messages between ROS and Gazebo. It works at a generic serialization level, i.e. it is not optimized for performance.

  • ros_gz_image: This package provides a unidirectional bridge for images from Gazebo to ROS. The bridge subscribes to Gazebo image messages (ignition::msgs::Image) and republishes them to ROS (sensor_msgs/msg/Image). It is specifically optimized for image data, e.g. using zero-copy transport or shared memory, where available.

  • ros_gz_point_cloud: This package provides a unidirectional bridge for point clouds from Gazebo to ROS. The bridge subscribes to Gazebo impoint cloud messages (ignition::msgs::PointCloudPacked) and republishes them to ROS (sensor_msgs/msg/PointCloud2).

  • ros_gz_sim_demos: This package contains demos showing how to use Gazebo with ROS.

Only the specific ros_gz subpackages you need should be included in the package.xml file and in the CMakeLists.txt file; this will help reduce unnecessary dependencies. For instance:

<buildtool_depend>ament_cmake</buildtool_depend>

<depend>rclcpp</depend>
<depend>geometry_msgs</depend>
<depend>ros_gz_bridge</depend> <!--Creates communication bridges between ROS 2 topics and Gazebo topics.-->
<depend>ros_gz_image</depend> <!--Bridge specialized in transmitting images. Used to get visual data (e.g. from simulated cameras) from Gazebo and send it to ROS 2 -->
<depend>ros_gz_sim</depend> <!--This package contains launch files and utilities to start Ignition Gazebo and to load models and worlds into the simulator -->
<depend>sensor_msgs</depend>
<depend>tf2</depend>
<!--depend>rrbot_control<depend-->

<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
find_package(rclcpp REQUIRED)
find_package(ament_cmake REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(ros_gz_bridge REQUIRED)
find_package(ros_gz_image REQUIRED)
find_package(ros_gz_sim REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(tf2 REQUIRED)

set(dependencies
  rclcpp
  realsense_gazebo_plugin
  geometry_msgs
  nav_msgs
  ros_gz_bridge
  ros_gz_image
  ros_gz_sim
  sensor_msgs
  tf2
)

install(DIRECTORY launch DESTINATION share/${PROJECT_NAME})
install(DIRECTORY worlds DESTINATION share/${PROJECT_NAME})
install(DIRECTORY models DESTINATION share/${PROJECT_NAME})
install(DIRECTORY rviz DESTINATION share/${PROJECT_NAME})

Exercise

Take a look at the package.xml file and the CMakeLists.txt file of the ignition_gazebo_sensors_tutorial package.

The ros_gz_bridge package

The ros_gz_bridge package provides a bidirectional network bridge which enables the exchange of messages between ROS and Gazebo. It supports a large but limited set of message types (check this README to verify the supported types).

Important

This package bridges topics but no services, except for the service /world/<my_world_name>/control (provided by the core simulation system itself) which is bridged to ros_gz_interfaces/srv/ControlWorld service, and that allows you to control the simulation world, such as pausing, unpausing, or resetting it (see Example 4).

The package ros_gz_interfaces, explained below, defines the services to spawn, delete or modify the pose of objects, that can be used to offers these services, as done in the package ignition_ros2_srv_bridge for the SpawnEntity service.

We can initialize a bidirectional bridge, allowing ROS to act as the publisher and Ignition as the subscriber, or vice versa. Let IGN_TOPIC be the Ignition topic name and IGN_MSG the Ignition message type; and let ROS_TOPIC be the ROS topic name and ROS_MSG the ROS message type. Then, the syntax of the arguments of the parameter_bridge executable of the package is:

  • /IGN_TOPIC@ROS_MSG[IGN_MSG, for bridging from Gazebo to ROS. For instance, the images published by a camera in Gazebo:

    ros2 run ros_gz_bridge parameter_bridge /camera/image@sensor_msgs/msg/Image[ignition.msgs.Image
    
  • /ROS_TOPIC@ROS_MSG]IGN_MSG, for bridging from ROS to Gazebo. For instance, the commanded velocities published form a ROS node:

    ros2 run ros_gz_bridge parameter_bridge /cmd_vel@geometry_msgs/msg/Twist]ignition.msgs.Twist
    

If there are several topics to be bridged, just separate them with spaces.

This can be more easily configured using a launch file (shown below), that allows also to easily rempap the ROS topic names as needed.

Configuring launch files

The following snippet shows how to launch the parameter_bridge executable from the ros_gz_bridge package and remapping the ROS topics:

start_gazebo_ros_bridge_cmd = Node(
    package='ros_gz_bridge',
    executable='parameter_bridge',
    name='gz_bridge',
    output='screen',
    arguments=[
        '/clock@rosgraph_msgs/msg/Clock[ignition.msgs.Clock',
        '/world/camera_world/model/camera1/link/camera_link/sensor/camera1_sensor/camera_info@sensor_msgs/msg/CameraInfo[ignition.msgs.CameraInfo',
        '/world/camera_world/model/camera1/link/camera_link/sensor/camera1_sensor/image@sensor_msgs/msg/Image[ignition.msgs.Image'
    ],
    remappings=[
        ('/world/camera_world/model/camera1/link/camera_link/sensor/camera1_sensor/image', '/camera/image'),
        ('/world/camera_world/model/camera1/link/camera_link/sensor/camera1_sensor/camera_info', '/camera/camera_info')
    ]
)

Recall that the Gazebo topic names can be listed as follows:

ign topic -l

And to launch the Gazebo node, the following should be added (change package and file names as required):

#Environment variables
pkg_path = FindPackageShare("ignition_gazebo_sensors_tutorial")

# Force OpenGL to run in software mode (necessary in headless environments or containers).
set_opengl = SetEnvironmentVariable(name='LIBGL_ALWAYS_SOFTWARE', value='True')

# Set Ignition resource paths
ign_resource_path = SetEnvironmentVariable(
    name='IGN_GAZEBO_RESOURCE_PATH',
    value=PathJoinSubstitution([pkg_path, "models"])
)

# The world to simulate: change this to your world file
simulation_world_file_path = PathJoinSubstitution(
    [pkg_path,
    "worlds/camera_world.sdf"]
)

# Start the Gazebo node
simulation_cmd = ExecuteProcess(
    cmd=['ign', 'gazebo', '-r', simulation_world_file_path],
    output='screen'
)

Exercise

Take a look at the camera_ignition_gazebo.launch.py file in the launch folder of the ignition_gazebo_sensors_tutorial package to see how Gazebo and the ros_gz_bridge nodes are launched.

Then, build and launch the file camera_ignition_gazebo_rviz.launch.py that includes the file camera_ignition_gazebo.launch.py and also opens rviz:

colcon build
source install/setup.bash
ros2 launch ignition_gazebo_sensors_tutorial camera_ignition_gazebo_rviz.launch.py

The ros_gz_interfaces package

The package ros_gz_interfaces offers message and service data structures for interacting with Gazebo from ROS 2:

The package ignition_ros2_srv_bridge has been implemented to offer the services:

  • to spawn a new object into the Gazebo world, defined in a given .sdf file.

  • to delete an object of the Gazebo world, given its name.

The SpawnEntity.srv service interface is:

ros_gz_interfaces/EntityFactory entity_factory  # Message to create a new entity
---
bool success                                     # Return true if spawned successfully.

And the EntityFactory message used as request is:

string name                             # New name for the entity, overrides the name on the SDF
bool allow_renaming false               # Whether the server is allowed to rename the entity in case of
                                        # overlap with existing entities.

# Only one method is supported at a time (sdf,sdf_filename,clone_name)
string sdf                              # SDF description in string format
string sdf_filename                     # Full path to SDF file.
string clone_name                       # Name of entity to clone

geometry_msgs/Pose pose                 # Pose where the entity will be spawned in the world.
string relative_to "world"              # Pose is defined relative to the frame of this entity.

The DeleteEntity.srv service interface is:

ros_gz_interfaces/Entity entity  # Gazebo Sim entity to be deleted.
---
bool success                      # Return true if deletion is successful.

And the Entity message used as request is:

# Entity type: constant definition
uint8 NONE      = 0
uint8 LIGHT     = 1
uint8 MODEL     = 2
uint8 LINK      = 3
uint8 VISUAL    = 4
uint8 COLLISION = 5
uint8 SENSOR    = 6
uint8 JOINT     = 7

uint64 id      # Entity unique identifier across all types. Defaults to 0
string name    # Entity name, which is not guaranteed to be unique.
uint8 type     # Entity type.

Exercise

Launch the camera_ignition_gazebo.launch.py and call the service to spawn a new object to the world, and then delete it.

ros2 launch ignition_gazebo_sensors_tutorial camera_ignition_gazebo.launch.py
ros2 service call /world/ign_gazebo_world/create ros_gz_interfaces/srv/SpawnEntity "{entity_factory: {name: 'kingW', sdf_filename: './install/ignition_gazebo_sensors_tutorial/share/ignition_gazebo_sensors_tutorial/models/kingW/kingW.sdf', allow_renaming: true,pose: {position: {x: 0, y: 0, z: 0.2}} }}"
ros2 service call /world/ign_gazebo_world/delete ros_gz_interfaces/srv/DeleteEntity "{entity: {name: 'kingW_0', type: '2' }}"

This package could be extended to include the services to change the pose of an object.

Spawning robot models

In Gazebo Fortress (Ignition Gazebo), robot models are spawned using the ros_gz_sim package, which provides a tool called create that spawns entities directly from URDF strings via ROS 2 launch files.

Steps

  1. Process a .xacro file to generate a URDF description.

  2. Create the robot_description string.

  3. Use the ros_gz_sim/create node to insert the robot into the Gazebo simulation.

  4. Configure the entity name and pose (position and orientation).

As en example, the spawn_rrbot.launch.py launch file of the rrbpot_gazebo packages illustrates the spawning of the rrbot robot:

from launch import LaunchDescription
from launch.substitutions import Command, FindExecutable, LaunchConfiguration, PathJoinSubstitution
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare

def generate_launch_description():

    # Robot spawn position and orientation
    position = [0.0, 0.0, 0.02]  # X, Y, Z
    orientation = [0.0, 0.0, 0.0]  # Roll, Pitch, Yaw

    entity_name = "rrbot"

    # Parameters for the robot description
    description_package = LaunchConfiguration('description_package', default='rrbot_description')
    description_file = LaunchConfiguration('description_file', default='rrbot.xacro')

    robot_description_content = Command([
        PathJoinSubstitution([FindExecutable(name="xacro")]),
        " ",
        PathJoinSubstitution([
            FindPackageShare(description_package),
            "urdf",
            description_file
        ]),
        " ",
        "name:=",
        entity_name,
    ])

    # Spawn the robot using ros_gz_sim
    spawn_robot = Node(
        package='ros_gz_sim',
        executable='create',
        name='spawn_robot',
        output='screen',
        arguments=[
            '-entity', entity_name,
            '-x', str(position[0]),
            '-y', str(position[1]),
            '-z', str(position[2]),
            '-R', str(orientation[0]),
            '-P', str(orientation[1]),
            '-Y', str(orientation[2]),
            '-string', robot_description_content
        ]
    )

    return LaunchDescription([
        spawn_robot,
    ])

That is, the robot is spawned in the simulation using the ros_gz_sim/create executable with the following key arguments:

  • -string: to directly pass the URDF string without writing to a file or topic,

  • -x, -y, -z: to set the robot’s initial position.

  • -R, -P, -Y: to set the robot’s initial orientation (roll, pitch, yaw), in radians.

Modeling robots for Gazebo

Gazebo ROS package structure

The following file hierarchy is the standard for organizing the robot information for applications using ROS with Gazebo:

../_images/package_structure.png

Exercise

Browse the rrbot_description and the rrbot_gazebo folders describing the rrbot.

Tunning URDF models

To use a URDF file in Gazebo, the following is required:

  • An <inertia> element within each <link> element must be properly specified and configured.

  • A <link name=”world”/> link has to be added if the robot should be rigidly attached to the world/base_link:

    <!-- Used for fixing robot to Gazebo 'base_link' -->
    <link name="world"/>
    
    <joint name="fixed" type="fixed">
      <parent link="world"/>
      <child link="link1"/>
    </joint>
    
  • Some <gazebo> elements, detailed below, are an extension to the URDF and have to be added for simulation purposes in Gazebo. There are three different types of <gazebo> elements, according to the reference property included:

    • For the <robot> tag (<gazebo>, i.e. with no reference): Parameters that will be directly inserted into the SDF <model>, like plugins:

    <robot>
      <!-- joint_states plugin -->
      <gazebo>
        <plugin
          name="ignition::gazebo::systems::JointStatePublisher"
          filename="libignition-gazebo6-joint-state-publisher-system.so">
        </plugin>
      </gazebo>
      ...
    </robot>
    
    • For <link> tags (<gazebo reference=’link_name’>): They are basically parameters related to the dynamic simulation, like the friction coefficients or the contact stiffness and damping for rigid body contacts. Also, parameters like plugins, that are directly inserted into the SDF <model>:

      <!-- Link2 -->
      <gazebo reference="link2">
        <mu1>0.2</mu1>
        <mu2>0.2</mu2>
        <material>Gazebo/Black</material>
      </gazebo>
      
    • For <joint> tags (<gazebo reference=’joint_name’>): They are basically parameters related to the dynamic simulation, like the damping and friction properties of the joints. Also, parameters like plugins, that are directly inserted into the SDF <model>:

      <!-- Joint1 -->
      <gazebo reference="joint1">
        <dynamics>
          <damping>0.5</damping>
          <friction>0.1</friction>
        </dynamics>
      </gazebo>
      

    Sometimes, as done with the rrbot robot, all the <gazebo> elements are grouped in a single file that is then included in the URDF file, e.g. in the rrbot.urdf file we can find this line:

    <!-- Import all Gazebo-customization elements, including Gazebo colors -->
    <xacro:include filename="$(find rrbot_description)/urdf/rrbot.gazebo" />
    

Gazebo will then convert the URDF to SDF automatically.

Exercise

Take a look at the file rrbot.gazebo in the urdf folder of the rrbot_gazebo package.

The rrbot demo

In the rrbot demo (in packages rrbot_description and rrbot_gazebo) the two-degrees-of-freedom robot rrbot (with a camera at its end-effector) is spawned in a world where there is a block obstacle.

The following information can be obtained from the simulation (through Ignition topics):

  • World state: The world state publishing is built into Gazebo Fortress, i.e. no need to add any plugin.

  • JointStates: The joint values are published because the JointStatePublisher plugin has been added to the robot (file rrbot.gazebo in the urdf folder of the rrbot_gazebo package).

  • The camera images: A topic with the images is published automatically by having described a sensor camera at the end-effector ().

These Ignition topics are bridged to ROS 2 topics as specified by the arguments of the ros_gz_bridge node in the launch file .

The demo runs a Gazebo and rviz (rviz subscribes to the /joint_states topic that is published by the plugin and bridged by the ros_gz_bridge node).

Exercise

Check the rrbot_description and rrbot_gazebo packages and run the demo.

ros2 launch rrbot_gazebo rrbot_gazebo.launch.py

The use_sim_time parameter

The use_sim_time parameter should typically be set to true if you are using a simulator/simulation environment, and your ROS nodes interface (ie: get their data, and send their data) with that simulator/simulation environment.

You can set this parameter simultaneously for all the nodes in the launch file:

return LaunchDescription([
      SetParameter(name='use_sim_time', value=True),
      # 'use_sim_time' will be set on all nodes following the line above
      node_1,
      ...,
      node_n,
  ])

ros_gz_sim_demos

The ros_gz_sim_demos repository offers several interesting demos, like:

Other demos involve sensors like IMUs, Lidar, RGBD cameras or Magnetometers.

Using images and the ArUco library for pose estimation

As an application example, the estimation of the poses of objects will be tackled using the ArUco library, an open-source library for fast camera pose estimation using squared markers, and the aruco_ros package that wraps it. There are two alternatives nodes:

  • The aruco_ros nodes of type single look for a given marker ID and if found publishes several messages with its information (e.g. a geometry_msgs/PoseStamped with the pose and a visualization_msgs/Marker with the rviz marker), and a tf added to the tf_tree.

  • The aruco_ros nodes of type marker_publisher publishes a message of type aruco_msgs/MarkerArray with the information of the IDs and poses of all the markers found.

The aruco_ros nodes of type single

If there are few markers to be detected, which may also be different size, a node of type single should be run for each marker ID to be detected, as illustrated below.

The following example launches gazebo, rviz and an aruco_ros node. The gazebo world is composed of a camera and an object with an ArUco marker (the kingW model with marker ID 316):

ros2 launch ignition_gazebo_sensors_tutorial camera_ignition_gazebo_rviz_aruco.launch.py
../_images/camera_aruco_cube_gazebo.png ../_images/camera_aruco_cube_rviz.png

Exercise

Move the pose of the cubes using the gazebo GUI and check how the new poses are tracked in rviz.

Exercise

Take a look at the files of the ignition_gazebo_sensors_tutorial package to check that:

  • File launch/camera_ignition_gazebo.launch.py runs gazebo and loads the camera_world.

  • File worlds/camera_world.sdf describes a world with a camera (the camera-plugin model) and an object (the kingW chess piece model).

  • File models/camera-plugin/model.sdf describes the camera sensor, where the name and frame are specified.

  • File launch/camera_image_pipeline.launch.py runs the node single of the aruco_ros package with the values of the arguments passed (or given as default).

  • File launch/camera_ignition_gazebo_rviz_aruco.launch includes the other launch files, broadcasts the frames of the camera and runs the rviz node.

EXERCISE 5

The RealSense camera

The Intel® RealSense™ Depth Camera D435 is a RGBD camera that generates the depth data with stereo vision technology that is optionally assisted by an infrared projector. The imaging system has the ability to synchronize with color stream. Detailed technical information can be found in the datasheet.

Proposed exercise

This aim of this exercise is to create a simulated world that reproduces the real scenario.

Exercise

Modify the ignition_gazebo_sensors_tutorial package:

  • Add two more pieces: the bishopB1 and the pawnB1 (the models are provided in the models folder).

  • Change the model of the camera to the realsense (the model is provided in the models folder).

  • Use the color topic name and the color camera info topic name in the remapping required when launching the aruco_single.

  • Use camera_color_optical_frame as default_value when declaring the launch argument camera_frame.

Info: The IDs of the bishopB1 and the pawnB1 are 213 and 201, respectively, and accordingly their tf names aruco_213 and aruco_201.

../_images/realsense_aruco_cube_gazebo.png ../_images/realsense_aruco_cube_rviz.png

Exercise

Implement a node that reads a yaml file as a parameter, which includes the chess pieces names and their location, and calls the /world/ign_gazebo_world/create service of the ignition_ros2_srv_bridge to spawn them in the Gazebo world. The node should be called chess_gazebo_setup and the yaml file chess_configuration_setup.yaml.

chess_gazebo_setup:
    ros__parameters:
        bishopB1: "B4"
        pawnB1: "C5"

Exercise

Implement a node that listens to the tf_tree and configures an internal data structure with the chess pieces and their location, and writes a chess configuration yaml file. The node should be called listener_node.