UP | HOME

Simulation

Gazebo and ROS

These notes discuss practical usage of (Modern) Gazebo in ROS 2.

Installation

  1. Install gazebo and its ROS components with sudo apt install ros-kilted-ros-gz-sim
  2. Tutorials for Gazebo Ionic (Ionic is the version name)
    • Each version of ROS 2 has a corresponding recommended Gazebo version: ROS Kilted goes with Gazebo Ionic
    • As of ROS 2 Jazzy, specific Gazebo vendor packages are provided with ROS and can be installed with sudo apt install ros-<my_ros_distro>-ros-gz-sim
  3. Tutorial on Integrating Gazebo with ROS 2
    • The python launchfile examples in the tutorial above uses several poor practices that you should not adopt:
      1. Using get_package_share_directory: instead use the FindPackageShare substitution
        • There are other scattered instances of using a non-declarative style in Python launchfiles.
      2. SetEnvironmentVariable: with the package layout we use, setting the environment variable is not necessary because it is set when the install/setup.bash is sourced.
      3. IncludeLaunchDescripition(PythonLaunchDescriptionSource) The PythonLaunchDescriptionSource is unnecessary and therefore should not be used; IncludeLaunchDescription can directly handle launchfiles of any time.

Running

From the Command Line

  1. Run gazebo with ros2 launch ros_gz_sim gz_sim.launch.py
    • This launchfile provides a method to pass options to gazebo by specifying gz_args:=<args to gazebo>.
    • The arguments are the same as those taken by gz sim
    • The gz command can also launch gazebo directly (this is the non-ros method of starting gazebo).
      • The launchfile sets up the environment for Gazebo to better integrate with ROS so it is generally recommended
  2. ros_gz provides the primary method for interacting with Gazebo via ROS 2
  3. The gz command is the primary way of controlling Gazebo independently of ROS
    • Using the gz command is a valuable debugging tool, because it can help narrow down if the problem is with Gazebo or with ROS

From a Launch File

  1. The server can be started with the <gz_server> action in an XML launchfile
  2. The GzServer action is used in a Python launchfile
    • The python GzServer class (the python Action) provides the code that parses the <gz_server> xml tag, so these two actions are equivalent
  3. Prior to the introduction of the <gz_server> tag, the gz_sim.launch.py launchfile needed to be included in your launchfile.
    • This old method is no longer recommended.

Package Layout

  1. An example of this package layout can be found at https://github.com/m-elwin/me495_gazebo.
  2. SDF files for worlds go in a worlds/ directory and are installed to share/<pkg_name>/worlds
    • Use worlds to provide any custom world simulation assets created in your package
  3. SDF files for models go in a models/ directory and are installed to share/<pkg_name>/models
    • Each model has all of its assets under a subdirectory of models (e.g., models/mymodel)
    • Each model has a model.sdf file that specifies the model name.
    • To find the model, the name of the subdirectory must match the model name.
    • Store any customized models created with your package
  4. The GZ_SIM_<SOMETHING>_PATH environment variables tells gazebo where to find SDF files and other resources

    • A ROS package that provides gazebo resources needs to set these variables appropriately.
    • Create a .dsv file in the env-hooks directory of your package with the following content (be sure to update the <pkg_name> to be the name of the package):

      prepend-non-duplicate;GZ_SIM_RESOURCE_PATH;share/<pkg_name>/models
      prepend-non-duplicate;GZ_SIM_RESOURCE_PATH;share/<pkg_name>/worlds
      prepend-non-duplicate;GZ_SIM_PLUGIN_PATH;share/<pkg_name>/plugins
      prepend-non-duplicate;GZ_SYSTEM_PLUGIN_PATH;share/<pkg_name>/plugins
      
    • Follow the colcon.pkg instructions to install the hook and have it update the environment whenever you source install/setup.bash
      • When the hook is installed properly, after source install/setup.bash, echo $GZ_SIM_RESOURCE_PATH should contain the paths to the gazebo resources
      • The PLUGIN paths are there for reference: plugins must be written in C++ and are beyond the scope of these notes.
  5. In ROS 1, these paths were handled in the package.xml with special Gazebo tags. It seems like similar functionality is being introduced into ROS 2, though we are not currently adopting this practice: see Transitional Documentation for more information.

SDF Files

Format

  • Gazebo uses SDF files to represent robots, worlds, and everything that appears in the simulation.
    • An SDF is an XML-based file format that is "human readable"
  • SDF Tutorials provides help with writing SDF files, including building worlds
  • Most Gazebo models also include additional assets (3D meshes, textures, etc) that the SF file refers to.
  • There are two types of entity that can be described by SDF: models and worlds
    • Models are individual objects in an environment
    • Worlds describe a whole entire simulation scene and typically contain or refer to many models

Model Management

  • Gazebo allows anybody to share simulation assets with the world via Gazebo Fuel
    • The website contains functionality to preview, download, and link-to assets for simulation
    • You can obtain a URI to refer to the model in your SDF file: this will cause Gazebo to download the model when required
    • It is also possible to dowload a zip file containing all assets and the SDF file describing a world/model
  • Assets that are referred to by URI will be downloaded and cached by Gazebo
    • Thus loading a world for the first time can take a while due to the data being downloaded but it will load much quicker a second time.
  • There are trade-offs between providing modles/worlds directly versus referring to them by URL
    • By referring to the item by URL, the user only downloads the data when needed
    • However, the server must be available when the user runs the simulation for the first time or else the asset cannot be used.
    • Storing models/assets in the git repository ensures they are always available, but can enlarge the size of the repository and complicate version control

Creating Worlds

  • In the Gazebo GUI, you can add the Resource Spawner to add models directly from Gazebo Fuel or your local computer, enabling you to create SDF world files interactively
  • When saving the world file, Gazebo will include a URI that contains the absolute local path to each model in the world
    • This means that, even if you spawned a resource from Fuel, the saved URI will refer to it's cached location
    • Thus, by default, worlds saved in the Gazebo GUI cannot be used across computers
  • After saving a world file, you must manually edit the URIs to make them portable
    • Search for the <uri> tag
    • Replace the <uri> as follows:
      1. If you are distributing the model with your repository (e.g., in the models directory), it can be replaced with model:///model_name, because the template package we use installs those models and puts the install directory on the GZ_SIM_RESOURCE_PATH.
        • Models can be downloaded from Fuel and added to your repository
      2. If you wish for the end user to download the model from Fuel the first time the simulation is run, replace the URI with the URL from the Fuel

Loading URDF/SDF files

URDF to SDF

  • ROS can be used to load both URDF and SDF models into the simulator.
    • URDF files are automatically converted to SDF prior to being loaded.
    • For a URDF model to be loaded successfully, all links must have visual, collision, and inertia specified.
  • Additional sdf tags can be added to a URDF by using the <gazebo> tag, which provide additional flexibility when converting a URDF to an SDF

    <robot xmlns:xacro="http://www.ros.org/wiki/xacro>
    <gazebo>
    <!-- Resulting SDF file will have all the SDF tags here and will be associated with the full robot model at the top-level-->
    </gazebo>
    <gazebo reference="link_name">
    <!-- resulting SDF file will have these SDF tags under the link named "link_name" -->
    </gazebo>
    <gazebo reference="joint_name">
    <!-- resulting SDF file will have these SDF tags under the joint named "joint_name" -->
    </gazebo>
    </robot>
    
  • A typical project structure is for a robot to provide:
    1. A robot.urdf.xacro file that contains robot definition without any gazebo tags.
    2. A robot.gazebo.xacro file that includes only the tags necessary for gazebo
    3. robot.urdf.xacro takes an argument (e.g., gazebo) that conditionally includes robot.gazebo.xacro.
      • If the argument is true, the robot.gazebo.xacro file is included
      • If the argument is false, the robot.gazebo.xacro file is NOT included, and the resulting URDF file is free of any Gazebo dependencies

The SDF Gazebo extension to URDF provides more details.

SDF to URDF

  1. It is also possible to maintain a robot as an SDF file and convert it to a URDF file automatically using the sdformat_urdf package
  2. The SDF format is much broader than the URDF format, so maintaining an SDF file that can be reliably converted into a URDF file imposes additional restrictions on the SDF file
  3. Currently, most ROS packages that support Gazebo provide URDF files that can be converted into SDF files.

Debugging

It can be helpful to inspect urdf files at different stages of conversion, especially if there are errors when trying to load your model.

  1. Make sure the files are installed via setup.py
  2. Source the workspace
  3. Run xacro ./install/path_to_xacro_file > robot.urdf to produce the URDF file
  4. Run gz sdf -p robot.urdf > robot.sdf to produce the SDF file

Spawning Robots

There are several ways to spawn a URDF model in gazebo, provided by the ros_gz_sim package

From Command Line

  • The ros2 run ros_gz_sim create node is the most straightforward method to spawning a model from the command-line (use --help to see all the options
    • It gives full control of the model
    • SDF and URDF files are supported (with URDF files being automatically converted)
    • For a xacro file, you should load the model either using a topic (i.e., if the robot_state_publisher) is already running or from a model_string by using command substitution: $(xacro /path/to/myrobot.urdf.xacro)
  • From the command line, ros2 launch ros_gz_sim gz_spawn_model.launch.py can be used.
    • This launch file is recommended in the tutorial, but seems to do a bunch of unnecessary/legacy environmental setup

From a Launch File

  1. The <gz_spawn_model> tag is used to spawn a robot from an XML launch file
  2. There is also GzSpawnModel for python launchfiles
    • If you inspect the above file, you can see that the Launch Action (coded in Python) is responsible for parsing the XML action, so the options and capabilities should be the same.
  3. The <gz_spawn_model> action was introduced relatively recently: prior to it's existence, you had to include the gz_spawn_model.launch.py launchfile in your launchfile or run the create node directly.
    • Going forward the <gz_spawn_model> tag is the preferred method.
  4. As of 10/2025, the gz_spawn_model has several optional arguments that (perhaps due to a bug) need to be present in the xml even if they are set to the empty string ""
    • The x, y, and z coordinates must be explicitly set to a floating-point value

Bridging

  • The ROS GZ Bridge is a node that is used to connect ROS topics and services to Gazebo topics and services.
  • Once the bridge is running you can publish/subscribe to the topics like any other ROS topic
  • You should generally run only one ros_gz_bridge and specify all topics/services that must be bridged
  • The Gazebo ROS 2 Bridge Documentation provides more information.

From the Command Line

  • ros2 run ros_gz_bridge parameter_bridge topic@ros_type@gz_type
    • Provide the topic name, the ROS type, and the corresponding gazebo type
    • Use --help for options/syntax
    • The second @ in the above syntax starts a bi-directional bridge (e.g. topics from gazebo are published to ROS and topics from ROS are published to Gazebo)
      • Replacing the second @ with a [ creates a bridge from Gazebo to ROS and using a ] creates a bridge from ROS to Gazebo
      • Sometimes uni-directional bridges are necessary. For example, we may want to receive tf frames from Gazebo without having all of ROS's Tf frames existing in the simulator.
      • In general, use a uni-directional bridge whenever you do not explicitly need bi-directional communication; to avoid subtle unforeseen problems
    • Be careful about topic types: for example the ROS type of the /tf topic is TFMessage not TransformStamped
    • Mappings between ROS and gz topic types are found in the API documentation
    • The parameter_bridge node also accepts configuration in YAML Format

From a Launch File

  1. The <ros_gz_bridge> tag is used to spawn a robot from an XML launch file
    • Using this tag is the preferred method. It also provides options to use_composition, enabling it to be automatically run in the same container as gazebo, which reduces communication overhead.
  2. There is also RosGzBridge for python launchfiles
    • If you inspect the above file, you can see that the Launch Action (coded in Python) is responsible for parsing the XML action, so the options and capabilities should be the same.
  3. The <ros_gz_bridge> action was introduced relatively recently: prior to it's existence, you had to start the bridge node manually and provide all parameters as command line arguments

Simulation Time

  1. Time in simulation can run at a different speed than time in the real world
  2. ROS 2 Nodes can use time from a simulator by setting the use_sim_time parameter to true
  3. When use_sim_time is true the node will get it's time from the /clock topic rather than the system clock.
    • For example, if the simulation is running at 50% real time, then a 100Hz timer in a ROS 2 node using simulation time will have that timer go off at 100Hz in simulation time, which corresponds to 50Hz in realtime.
  4. To get Gazebo to generate clock topic, it must be bridged to ros using the parameter_bridge: by passing /clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock
  5. For many simulation applications, nodes do not need to use simulation time. However, if code is checking timestamps (such as the Nav2 stack), then ensuring that all nodes are using simulated time becomes important.
  6. Information about Timing issues with ROS 2 control

Plugins

  1. Gazebo uses plugins to enhance/modify functionality
  2. Some plugins can be associated with specific models and used to control them
  3. Useful Plugins can be found in the API documentation, especially the Systems.
    • The documentation includes what tags can be used in the SDF to include the plugin
    • Assume the C++ namespace of the plugin is given as xx::yy::zz::systems::PluginName etc. Then in the SDF file the plugin is included as
    • Use <plugin name="xx::yy::zz::systems::PluginName" filename="xx-yy-zz-plugin-name-system">
      • For example, to use the gz::sim::systems::DiffDrive plugin:
        • Set name to gz::sim::systems::DiffDrive
        • set filename to gz-sim-diff-drive-system
        • Options for this plugin go inside the <plugin></plugin> tag (see API Documentation)
  4. The JointStatePublisher plugin publishes joint states

Common Problems

General Issues

  1. Gazebo uses a client-server architecture. Often the server does not exit when the client does and is running in the background
    • If a server is already running in the background run gz sim -g to launch the client.
  2. Make sure there aren't extraneous Gazebo processes running by looking through your process list for processes containing "gz" and killing them.

Software in Transition

As of 10/2025: There are three simultaneous transitions happening that have resulted in rapid churn in how ROS 2 and Gazebo works, which can make navigating online sources difficult:

  1. Transition from ROS to ROS 2
    • Although the transition is complete there are still many references to "using Gazebo with ROS" which refer to ROS 1 and "Gazebo Classic" (the simulator formerly known as Gazebo)
  2. Transition from Gazebo Classic (i.e., the original Gazebo Simulator ) to Gazebo (e.g., the new Gazebo Simulator)
    • Many google searches for "Gazebo" in fact return results for "Gazebo Classic": these simulators are completely different
    • Modern gazebo documentation and resources are hosted on https://gazebosim.org. Gazebo Classic is hosted on https://classic.gazebosim.org
  3. The renaming of Ignition Gazebo (former name of the modern Gazebo simulator) to Gazebo and the renaming of Gazebo (the original Gazebo simulator) to Gazebo Classic
    • There are still many references to ignition and commands to ign
    • Here is a guide in case you only see the old documentation: Ignition to Gazebo.
  4. ROS 2 now distributes it's own "vendored" version of Gazebo with each ROS 2 release (before, gazebo was distributed with Ubuntu)
    • These packages appear to be installed under /opt/ros/<rosdistro>/opt: maybe it's to avoid name conflicts but it ends up polluting the path quite a bit.
  5. A new method of Gazebo ROS 2 Integration, which looks promising but does not seem to be fully implemented or feature complete.
    • It allows starting the gazebo_server using a <gz_server> tag in a Launchfile (excellent!)
    • The server can run in a container as a composable node (great!)
    • The ROS 2 Gazebo Bridge can also run in the composable Node and has it's own xml launchfile tag (wonderful!)
    • The create_own_container attribute does not seem to be implemented in the currently released Jazzy version of ros_gz_sim package (though it is in the code…??)
    • There does not seem to be a straightforward way (or at least I have not found one) to start the Gazebo Gui client for use with the <gz_server>
      • gz sim -g sometimes connects to the server and sometimes does not: simply starting and restarting it a few times seems to work but it is unreliable on my system (might need to just do a bit more debugging here).
        • It would be, in my opinion, a useful contribution to add the capability to run the Gazebo Gui as a component node and launch it with a <gz_client> tag from a launchfile.
    • The migration to the ROS 1 way of handling Gazebo Paths (custom tags in package.xml) versus the ROS 2 way (using colcon env-hooks) may be better long-term, but it's implementation relies on using certain launchfiles which we cannot fully use currently.
    • The demonstrations for ros_gz_sim do not seem updated for the new method.
    • Overall, I would like to adopt the new methods, but their still seems to be a mix of documentation promoting the new methods, examples with the old methods. Hopefully by next year this will be a bit more mature.
  6. There is also talk of maintaining robots as SDF files rather than URDF files. I have not yet seen this in practice, and am hesitant to adopt this practice given that SDF files are more general than URDF files (so URDF can always be converted to SDF but not vice-versa).

Documentation

  1. Gazebo Tutorials
  2. SDF Format
  3. ROS 2 Gazebo Interface (See the README.md files in each package in the repository)
  4. Fuel Model Library

Author: Matthew Elwin.