How to Make a Mobile Robot in Gazebo (ROS2 Foxy)

In this tutorial, we will learn how to make a model of a mobile robot in Gazebo from scratch. Our simulated robot will be a wheeled mobile robot. It will have two big wheels on each side and a caster wheel in the middle. Here is what you will build. In this case, I have the robot going in reverse (i.e. the big wheels are on the front of the vehicle):



Setup the Model Directory

Here are the official instructions, but we’ll walk through all the steps below. It is important to go slow and build your robotic models in small steps. No need to hurry. 

Create a folder for the model.

mkdir -p ~/.gazebo/models/my_robot

Create a model config file. This file will contain a description of the model.

gedit ~/.gazebo/models/my_robot/model.config

Add the following lines to the file, and then Save. You can see this file contains fields for the name of the robot, the version, the author (that’s you), your e-mail address, and a description of the robot.

<?xml version="1.0"?>
  <name>My Robot</name>
  <sdf version='1.4'>model.sdf</sdf>

   <name>My Name</name>

    My awesome robot.

Close the config file.

Now, let’s create an SDF (Simulation Description Format) file. This file will contain the tags that are needed to create an instance of the my_robot model. 

gedit ~/.gazebo/models/my_robot/model.sdf

Copy and paste the following lines inside the sdf file.

<?xml version='1.0'?>
<sdf version='1.4'>
  <model name="my_robot">

Save the file, but don’t close it yet.

Create the Structure of the Model

Now we need to create the structure of the robot. We will start out by adding basic shapes. While we are creating the robot, we want Gazebo’s physics engine to ignore the robot. Otherwise the robot will move around the environment as we add more stuff on it.

To get the physics engine to ignore the robot, add this line underneath the <model name=”my_robot”> tag.


This is what you’re sdf file should look like at this stage:


Now underneath the <static>true</static> line, add these lines:

          <link name='chassis'>
            <pose>0 0 .1 0 0 0</pose>

            <collision name='collision'>
                  <size>.4 .2 .1</size>

            <visual name='visual'>
                  <size>.4 .2 .1</size>

Here is how your sdf file should look now. 


Click Save and close the file.

What you have done is create a box. The name of this structure (i.e. link) is ‘chassis’. A chassis for a mobile robot is the frame (i.e. skeleton) of the vehicle.

The pose (i.e. position and orientation) of the geometric center of this box will be a position of (x = 0 meters, y = 0 meters, z = 0.1 meters) and an orientation of (roll = 0 radians, pitch = 0 radians, yaw = 0 radians). The pose of the chassis (as well as the post of any link in an sdf file) is defined relative to the model coordinate frame (which is ‘my_robot’).


  • Roll (rotation about the x-axis)
  • Pitch (rotation about the y-axis)
  • Yaw (rotation about the z-axis)  

Inside the collision element of the sdf file, you specify the shape (i.e. geometry) that Gazebo’s collision detection engine will use. In this case, we want the collision detection engine to represent our vehicle as a box that is 0.4 meters in length, 0.2 meters in width, and 0.1 meters in height. 

Inside the visual tag, we place the shape that Gazebo’s rendering engine will use to display the robot. 

In most cases, the visual tag will be the same as the collision tag, but in some cases it is not.

For example, if your robot car has some complex geometry that looks like the toy Ferrari car below, you might model the collision physics of that body as a box but would use a custom mesh to make the robot model look more realistic, like the figure below.


Now let’s run Gazebo so that we can see our model. Type the following command:


On the left-hand side, click the “Insert” tab.

On the left panel, click “My Robot”. You should see a white box. You can place it wherever you want.


Go back to the terminal window, and type CTRL + C to close Gazebo.

Let’s add a caster wheel to the robot. The caster wheel will be modeled as a sphere with a radius of 0.05 meters and no friction.

Relative to the geometric center of the white box (i.e. the robot chassis), the center of the spherical caster wheel will be located at x = -0.15 meters, y = 0 meters, and z = -0.05 meters.

Note that when we create a box shape, the positive x-axis points towards the front of the vehicle (i.e. the direction of travel). The x-axis is that red line below.

The positive y-axis points out the left side of the chassis. It is the green line below. The positive z-axis points straight upwards towards the sky.

gedit ~/.gazebo/models/my_robot/model.sdf

Add these lines after the first </visual> tag but before the </link> tag.

          <collision name='caster_collision'>
            <pose>-0.15 0 -0.05 0 0 0</pose>


          <visual name='caster_visual'>
            <pose>-0.15 0 -0.05 0 0 0</pose>

Click Save and close the file.

Now relaunch gazebo, and insert the model.


Insert -> My Robot

Go back to the terminal window, and close gazebo by typing CTRL + C.

You can now see our robot has a brand new caster wheel located towards the rear of the vehicle.


Now let’s add the left wheel. We will model this as a cylinder with a radius of 0.1 meters and a length of 0.05 meters.

gedit ~/.gazebo/models/my_robot/model.sdf

Right after the </link> tag (but before the </model> tag, add the following lines. You can see that the wheel is placed at the front of the vehicle (x=0.1m), the left side of the vehicle (y=0.13m), and 0.1m above the surface (z=0.1m). It is a cylinder that is rotated 90 degrees (i.e. 1.5707 radians) around the model’s y axis and 90 degrees around the model’s z axis.

     <link name="left_wheel">
        <pose>0.1 0.13 0.1 0 1.5707 1.5707</pose>
        <collision name="collision">
        <visual name="visual">

Launch Gazebo, and see how it looks. You can click the box at the top of the panel to see the robot from different perspectives.


Now, let’s add a right wheel. Insert these lines into your sdf file right before the </model> tag. 

    <link name="right_wheel">
        <pose>0.1 -0.13 0.1 0 1.5707 1.5707</pose>
        <collision name="collision">
        <visual name="visual">

Save it, close it, and open Gazebo to see your robot. Your robot has a body, a spherical caster wheel, and two big wheels on either side. 


Now let’s change the robot from static to dynamic. We need to add two revolute joints, one for the left wheel and one for the right wheel. Revolute joints (also known as “hinge joints”) are your wheels motors. These motors generate rotational motion to make the wheels move.

In the sdf file, first change this:


To this


Then add these lines right before the closing </model> tag.

      <joint type="revolute" name="left_wheel_hinge">
        <pose>0 0 -0.03 0 0 0</pose>
          <xyz>0 1 0</xyz>

      <joint type="revolute" name="right_wheel_hinge">
        <pose>0 0 0.03 0 0 0</pose>
          <xyz>0 1 0</xyz>

<child> defines the name of this joint’s child link.

<parent> defines the parent link.

<pose> describes the offset from the the origin of the child link in the frame of the child link.

<axis> defines the joint’s axis specified in the parent model frame. This axis is the axis of rotation. In this case, the two joints rotate about the y axis.

<xyz> defines the x, y, and z components of the normalized axis vector. 

You can learn more about sdf tags at the official website.

Save the sdf file, open Gazebo, and insert your model.

Click on your model to select it.

You will see six tiny dots on the right side of the screen. Click on those and drag your mouse to the left. It is a bit tricky to click on these as Gazebo can be quite sensitive. Just keep trying to drag those dots to the left using your mouse.

Under the “World -> Models” tab on the left, select the my_robot model. You should see two joints appear on the Joints tab on the right.


Under the Force tab, increase the force applied to each joint to 0.1 N-m. You should see your robot move around in the environment.

Congratulations! You have built a mobile robot in Gazebo.

PID Control Made Simple

In this tutorial, I’ll explain how PID (Proportional-Integral-Derivative) Control works using an analogy.

Imagine we are the captain of a ship. We want the boat to maintain a heading of due north automatically, without any human intervention.


We go out and hire a smart robotics software engineer to write a control algorithm for the ship. 

The variable that we want to control is the ship’s heading, and we want to make sure the heading maintains a “set point” of due north (e.g. 0 degrees).

Proportional Control

  • If the ship is heading slightly off course to the left, we want the ship to steer slightly to the right.
  • If the ship is heading slightly off course to the right, we want the ship to steer slightly to the left.
  • If the ship is heading strongly off course to the left, we want the ship to steer strongly to the right.
  • If the ship is heading strongly off course to the right, we want the ship to steer strongly to the left.

In short, the steering magnitude is directly proportional to the difference between the desired heading and the current heading. The further you are off course, the harder the ship steers.

Mathematically, proportional control is: 

Control Output  = Kp * (Desired – Actual)


  • Control Output is what you want to control (e.g. steering intensity, speed, etc.)
  • Kp is some non-negative constant
  • Desired = Set Point (e.g. the heading you want to achieve…i.e. 0 degrees north)
  • Actual = Process Variable (e.g. the heading you measured)
  • Error = Desired – Actual

The Proportional term is all about adjusting the output based on the present error.

Integral Control

Now let’s suppose that there is a crosswind. Even though the ship is adjusting the steering in proportion to the heading error, there are still differences between the actual and desired heading that are accumulating over time as a result of this ongoing crosswind. 

The ship continues to drift off course even though proportional control is being applied.

The integral term looks for the residual error that is generated even after proportional control is applied. In this example, the residual error is caused by the wind, and it keeps adding up over time. The integral term seeks to get rid of this residual error by incorporating the historical cumulative value of the error. As the error decreases, the integral term will decrease.

Integral control looks for factors that keep pushing the ship off course and adjusts accordingly.

Mathematically, we now have to add the Integral term.

Control Output  = Kp * Error + Ki * Sum of Errors Over Time

The Integral term is all about adjusting the output based on the past error.

Derivative Control

Let’s suppose that the ship is steering hard to the left in order to correct the heading. You don’t want the ship to steer so hard that momentum causes the ship to overshoot your desired heading. 

What you want to do is have the ship slow down the steering as it approaches the heading. 

The derivative term of PID control does this. It is used to slow down the heading correction as the measured heading approaches the desired heading. This derivative term helps the ship avoid overshooting.

Mathematically, we now have to add the Derivative term to get the final PID Control equation:

Control Output  = Kp * Error + Ki * Sum of Errors Over Time + Kd * Rate of Change of the Error with Respect to Time

How all of the PID control constants combine to generate the desired control response

The Derivative term is all about adjusting the output based on the future (predicted) error. If the error is decreasing too quickly with respect to time, this term will reduce the control output.

How To Set up a ROS2 Project for Python – Foxy Fitzroy

In this tutorial, we will learn how to set up a ROS2 project from scratch.


Source Your ROS2 Installation

We first need to create a new workspace. A workspace is a folder that will contain all your ROS packages.

Here is the official tutorial, but I’ll walk through all the steps below.

Open a new terminal window.

To find out which version of ROS2 you have installed, type the following command:

printenv ROS_DISTRO

I’m using the foxy distribution of ROS2.

Now, let’s source the ROS2 environment. Type the following command:

source /opt/ros/foxy/setup.bash

Create a Workspace

Let’s create a workspace directory. “-p” is short for “–parents”. This flag makes sure the mkdir (“make directory”) command makes the parent /dev_ws/ folder as well as the /src/ folder within it (Feel free to check out my Linux commands tutorial).

mkdir -p ~/dev_ws/src
cd ~/dev_ws/src

The syntax is:

mkdir -p ~/<yourworkspace>_ws/src

cd ~/<yourworkspace>_ws/src

Create a Package

Now let’s create a ROS package.

We will use the following syntax.

ros2 pkg create –build-type ament_python <package_name>

Type this command:

ros2 pkg create --build-type ament_python my_package

Your package named my_package has now been created.

Build Your Package

Return to the root of your workspace:

cd ~/dev_ws

Build all packages in the workspace.

colcon build

If you only want to build my_package, you can type

colcon build --packages-select my_package

Source the Setup File

Open a new terminal window.

Source your ROS2 installation.

source /opt/ros/foxy/setup.bash

Move to your workspace folder.

cd ~/dev_ws

Add the files you just created to the path.

source install/setup.bash

If you don’t have gedit installed, install it using this command:

sudo apt-get install gedit

Open your .bashrc file.

gedit ~/.bashrc

Add this line of code to the very bottom of the file.

source /opt/ros/foxy/setup.bash

You now don’t have to source your ROS2 installation every time you open a new terminal window.

While you’re at it add this line of code to the very bottom of the .bashrc file.

source ~/dev_ws/install/setup.bash

The syntax is

source ~/<your_workspace>/install/setup.bash

Save the file, and close it.

Your system now knows where to find your ROS2 packages.

Write Node(s)

Move to the dev_ws/src/my_package/my_package folder.

cd dev_ws/src/my_package/my_package

Write a Python program (i.e. node), and add it to this folder you’re currently in. This post has some sample code you can use. Copy the Publisher Node code, and paste it into a file named


Save the file and close it.

Now change the permissions on the file.

chmod +x

Make sure that the names of your python nodes are not the same as the name of the package. Otherwise, you will have ImportErrors.

You can add as many Python programs as you like. For example, you might have one node that defines a class, and then you have another node that imports that class and uses it to make calculations, which are then published to a ROS2 topic as some data type (e.g. Float64, geometry_msgs/Twist, etc.).

Add Dependencies

Navigate one level back.

cd ..

You are now in the dev_ws/src/my_package/ directory. 



You should see the, setup.cfg, and package.xml files.

Open package.xml with your text editor.

gedit package.xml

Fill in the <description>, <maintainer>, and <license> tags.

Add a new line after the ament_python build_type dependency and add the following dependencies which will correspond to other packages the packages in this workspace needs (this will be in your node’s import statements):


This let’s the system know that this package needs the rclpy and geometry_msgs packages when its code is executed. 

The code doesn’t need the geometry_msgs package, but I’m having you add it anyway just to show you that you can add all sorts of message types in this package.xml file.

Save the file.

Add an Entry Point

Now we need to add the entry points for the node(s) by opening the file.


Make sure this code is in there:

        'console_scripts': [
                'my_python_script = my_package.my_python_script:main',

The syntax is:

‘my_python_script = my_package.my_python_script:main’

my_python_script will be the name of the executable.

‘my_package.my_python_script:main’ tells the system to execute the main() function inside Therefore, this will be the entry point when you run your node.

The executable script will go to:


Save and close it.

Check for Missing Dependencies

Check for any missing dependencies before you build the package.

Move to your workspace folder.

cd ~/dev_ws

Run the following command:

rosdep install -i --from-path src --rosdistro <distro> -y

For example:

rosdep install -i --from-path src --rosdistro foxy -y

I get a message that says:

“#All required rosdeps installed successfully”

Build and Run

Now, build your package by first moving to the src folder.

cd src

Then build the package.

colcon build --packages-select my_package

You should get a message that says:

“Summary: 1 package finished [<time it took in seconds>]”

Open a new terminal tab.

cd ~/dev_ws

Run the node:

ros2 run my_package my_python_script

The syntax is:

ros2 run <package_name> <node name without the .py>

Open a new terminal tab, and move to the root directory of the workspace.

cd ~/dev_ws

List the active topics.

ros2 topic list -t

To listen to any topic, type:

ros2 topic echo /topic_name

Where you replace “/topic_name” with the name of the topic. 

When you’ve had enough, type Ctrl+C in each terminal tab to stop the nodes from spinning.

Zip the Workspace for Distribution

If you ever want to zip the whole workspace and send it to someone, open a new terminal window.

Move to the directory containing your workspace.

cd ~/dev_ws
cd ..
zip -r /dev_ws

The syntax is:

zip -r <> <foldername>