Simulating with Gazebo
What is Gazebo?
Having a good simulation environment is a valuable tool in robotics, as testing on real hardware can often be expensive and time consuming. The actions of some robots can be hazardous, and deploying code (especially in early development) carries risks. Working with real hardware can also introduce issues that impede core algorithm development. For this reason, time spent building an accurate simulation environment is usually well worth it.
Gazebo is a free, open-source robot simulation environment. The project is run by Open Robotics, the same group looking after ROS, however the projects are managed separately and Gazebo is not a "part of" ROS. This distinction can sometimes be a little confusing, and although Gazebo and ROS do integrate very well, they approach some things a bit differently.
Gazebo has recently (at time of writing) been rewritten and replaced by a new simulator called Ignition Gazebo (sometimes referred to as just Ignition). This is similar to the change from ROS 1 to ROS 2, but ROS 2 is still compatible with the old Gazebo (version 11). Unfortunately, key plugins are still not ready for Ignition, so these tutorials will keep using Gazebo for now.
With Gazebo, we can create a virtual "world", and load simulated versions of our robots into it. Simulated sensors can detect the environment, and publish the data to the same ROS topics that real sensors would, allowing easy testing of algorithms. Then, forces can be applied to the simulated actuators on the robot, taking into account things like friction.
Gazebo Installation and Overview
With ROS already installed, the easiest way to install Gazebo with the correct dependencies and plugins to get it working with ROS is with the following command:
sudo apt install ros-foxy-gazebo-ros-pkgs
Now that we have Gazebo installed, we'll start by exploring some aspects of Gazebo without ROS. We can start it by running gazebo
at the command line, and we can also supply the path to a world file as the first argument. Run the command below to launch Gazebo with the "seesaw" world (which came bundled when we installed Gazebo).
gazebo /usr/share/gazebo-11/worlds/seesaw.world
Note, Gazebo can sometimes take a while to start (especially the first time) if it wants to download some stuff in the background.
Once Gazebo starts up we'll see the following screen. Take a minute to familiarise yourself with the interface (for the 3D view, left-click to pan, middle-click to orbit, right-click or scroll to zoom).
The simulated environment inside Gazebo is called the world, and this world consists of a bunch of models. If we open the "Models" drop-down in the top-left, we can see that the see-saw world contains 5 models: the ground plane, fulcrum, plank, and two cubes (it also has one Light called "sun"). Let's explore how these models interact in the physics engine.
By selecting "Translation Mode" (pair of crossed arrows, top toolbar), we can left-click models to display an axis marker, then click and drag an axis to slide it.
Have a play around and see what you can make the seesaw do. "Reset World" (Ctrl-R, or Edit→Reset World) is helpful to clear what you've done (however certain changes will persist across resets - for these you'll need to restart Gazebo).
Some things to try:
- Moving the blocks while keeping the seesaw balanced
- Pausing the simulation (bottom toolbar), moving the blocks to mid-air, then playing
- Rotating the blocks (Rotation mode, top toolbar)
- Applying a force (right-click on a block while in selection mode, "Apply Force/Torque")
Applying -2,000,000N in Z on one of the blocks should produce a rather satisfying catapult!
Gazebo and ROS
Before we try to run Gazebo with ROS integration, let's take a minute to understand how the different pieces fit together.
Fitting Gazebo into our structure
In the previous tutorials we were generating joint state values using joint_state_publisher_gui
, which were read (along with a URDF) by robot_state_publisher
, which broadcasted the transforms and echoed a copy of the URDF to the robot_description
topic. We used RViz to visualise all this.
As we introduce Gazebo, we want it to represent the "real world", a physical robot that we would interact with in various ways. For example, it would have the capacity move based on some control input, read data from sensors, and provide state feedback from the actuators. The diagram below shows this new structure - for now we can treat Gazebo as a big "blob", and the details (plugins, spawner, etc.) will become clearer soon.
URDF/SDF/World Files
In the last tutorial we learnt how to create a URDF file for our robot, and integrate it as shown in the diagram above. Gazebo, however, does not use URDF files natively, instead it uses a different (but similar) format called SDF. While URDF only describes a robot, SDF can describe a robot, or the world, or any model in the world.
The good news is that we don't have to double-up and write both an SDF and URDF file for our robot, just for the sake of simulation. Gazebo has the ability to automatically convert a URDF to SDF for us (we can use the command gz sdf -p
to experiment with this). Although this conversion is automatic, the resulting file still usually won't have everything Gazebo requires to run properly. Through the rest of this tutorial we'll cover a few things we need to add or check in our URDF file to make sure it will work correctly with Gazebo, and you can also check out the official tutorial here.
Apart from our robot, SDF files are still useful. We can use them to define models (any object that can be spawned, whether it's a cube, a car, a house, or our robot), or worlds (an environment that contains a collection of models). This is great because we can mix-and-match our robot with a variety of models and environments for testing.
Gazebo Tags
To add extra information to our URDF that is Gazebo-specific, we can add gazebo
tags. Gazebo will use the content of these tags to generate its SDF files, but other things reading the URDF can just ignore them. These gazebo tags can reference a component of the URDF by name (i.e. a robot
, link
, or joint
) using the syntax <gazebo reference="some_name">
, or for things that are not linked to a component, but are relevant to the whole file we just use <gazebo>
.
Plugins
Any time Gazebo needs to interact with ROS (or any other external software for that matter), it needs to use plugins. These are extra code libraries that we can install, and then reference in our URDF, which Gazebo will execute as it needs to. Want to control your Gazebo robot using ROS topics? Use a plugin. Want the Gazebo joint states to be published to a topic? A plugin. Want to access the data from a simulated sensor? Plugin. There are plenty of examples of these plugins in action in the example code at the end.
Sim Time
One trick to be aware of is that ROS treats time a little differently when simulating, and all nodes will need the /use_sim_time
parameter set to true
. You might notice some nodes (e.g. robot_state_publisher
) will seem to work without it, but even then it is best to set it as there can be subtle differences in behaviour. Here is an explanation of the concept (ROS 1, but the idea is the same), which is basically that Gazebo wants to "control time" so that we can run simulations faster or slower and ensure things are synchronised. Time in ROS is normally measured in "Unix time" (seconds since 1/1/1970), but while simulating it is measured in seconds since the start of the simulation.
In ROS 1 it was sufficient to set /use_sim_time
globally but in ROS 2 we need to do it on a per-node basis. Using launch files can greatly assist in this as we can turn it on/off in one place (at launch) and have it pass the argument on to all the nodes appropriately.
Extra notes
There are a few other things that are good to remember as we work with Gazebo and prepare our URDF file for simulation:
- Names - We touched on this briefly in the last tutorial, but to keep things simple we just need to make sure every joint and link has a (unique) name, and Gazebo shouldn't have trouble with that.
- Merging links - Gazebo will take any URDF links that are connected by
fixed
joints and combine them into a single SDF link. This isn't a problem, but can sometimes be confusing. - Inertial properties - Gazebo requires that every link has an
inertial
tag associated with it. Because it combines fixed-joined links, this means that we don't need every link in our URDF to have an inertial tag, just that in any group of fixed-joined links we need at least one to have an inertial tag (if multiple do, they will get merged by Gazebo). - World links - If our robot is fixed to the ground, the first link should be called
world
, and Gazebo will know that regardless of the rest of the physics, it should keep that link fixed still. For a mobile robot our first link should NOT be called world (and why would it be?). - Materials/Colours - We'll look more closely at this below, but for technical reasons Gazebo is unable to use the materials specified in our URDF. Instead, we need to add a
gazebo
tag for the link and specify an appropriate material in there.
Running Gazebo with ROS
Launching Gazebo
Earlier, we ran Gazebo directly with the gazebo
command, however this is running Gazebo separated from ROS. Running it with ROS support can be trickier, so we are provided with a ROS launch file that takes care of things for us. The command below will launch Gazebo, loading up a particular world environment (more on this in a moment) if we choose to specify it.
ros2 launch gazebo_ros gazebo.launch.py world:=src/robot_testing/worlds/world6.world
Spawning our robot
Once we have fixed up our URDF file, we want to run robot_state_publisher
to publish it to the robot_description
topic (as we've seen in the last few tutorials). Once this is up, we can run the command below which will spawn the robot into the gazebo
ros2 run gazebo_ros spawn_entity.py -topic robot_description -entity some_name
This will spawn the robot described by the URDF on robot_description
and give it the name some_name
within Gazebo. We can also spawn robots directly from files or model databases, but in most scenarios we will have a description topic running already so we use that.