Skip to main content

Robot Simulation with Gazebo

Gazebo is the de facto standard simulator for ROS 2 robotics. It provides physics simulation, realistic sensor simulation, and complex multi-robot environments. Developing and testing robot behaviors in simulation before hardware deployment saves time, money, and reduces risk.

Understanding URDF and Gazebo​

A robot's structure is defined in URDF (Unified Robot Description Format) files. Gazebo loads URDF files and adds physics properties. The workflow is: design robot → create URDF → add Gazebo plugins → test in simulation.

Creating a Simple Robot URDF​

This URDF defines a simple mobile robot with a base, two wheels, and a caster:

<?xml version="1.0" ?>
<robot name="simple_robot" xmlns:xacro="http://www.ros.org/wiki/xacro">

<link name="base_link">
<visual>
<geometry>
<box size="0.4 0.3 0.2"/>
</geometry>
<material name="blue">
<color rgba="0 0 1 1"/>
</material>
</visual>
<collision>
<geometry>
<box size="0.4 0.3 0.2"/>
</geometry>
</collision>
<inertial>
<mass value="5.0"/>
<inertia ixx="0.1" ixy="0" ixz="0" iyy="0.1" iyz="0" izz="0.05"/>
</inertial>
</link>

<!-- Left wheel -->
<link name="left_wheel">
<visual>
<geometry>
<cylinder length="0.08" radius="0.1"/>
</geometry>
</visual>
<collision>
<geometry>
<cylinder length="0.08" radius="0.1"/>
</geometry>
</collision>
<inertial>
<mass value="1.0"/>
<inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.01"/>
</inertial>
</link>

<joint name="left_wheel_joint" type="revolute">
<axis xyz="0 1 0"/>
<parent link="base_link"/>
<child link="left_wheel"/>
<origin rpy="0 0 0" xyz="-0.15 0.15 0"/>
<limit effort="10" velocity="5"/>
</joint>

<!-- Right wheel -->
<link name="right_wheel">
<visual>
<geometry>
<cylinder length="0.08" radius="0.1"/>
</geometry>
</visual>
<collision>
<geometry>
<cylinder length="0.08" radius="0.1"/>
</geometry>
</collision>
<inertial>
<mass value="1.0"/>
<inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.01"/>
</inertial>
</link>

<joint name="right_wheel_joint" type="revolute">
<axis xyz="0 1 0"/>
<parent link="base_link"/>
<child link="right_wheel"/>
<origin rpy="0 0 0" xyz="-0.15 -0.15 0"/>
<limit effort="10" velocity="5"/>
</joint>

</robot>

Gazebo Launch File​

Launch the robot in Gazebo with proper configuration:

# robot_sim/launch/gazebo.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import ExecuteProcess
from launch.substitutions import FindPackageShare
import os

def generate_launch_description():
pkg_share = FindPackageShare('robot_sim').find('robot_sim')
urdf_path = os.path.join(pkg_share, 'urdf', 'simple_robot.urdf')

with open(urdf_path, 'r') as f:
robot_desc = f.read()

gazebo = ExecuteProcess(
cmd=['gazebo', '--verbose', '-s', 'libgazebo_ros_init.so',
'-s', 'libgazebo_ros_factory.so'],
output='screen'
)

spawn_robot = Node(
package='gazebo_ros',
executable='spawn_entity.py',
arguments=['-topic', 'robot_description', '-entity', 'simple_robot'],
output='screen'
)

robot_state_publisher = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{'robot_description': robot_desc}],
output='screen'
)

return LaunchDescription([
gazebo,
robot_state_publisher,
spawn_robot
])

Gazebo Plugins for Physics and Control​

Add plugins to simulate motors and sensors:

<!-- Add to URDF inside robot tag -->
<gazebo>
<plugin name='gazebo_ros_control' filename='libgazebo_ros_control.so'/>
</gazebo>

<!-- Wheel friction properties -->
<gazebo reference="left_wheel">
<mu1>0.5</mu1>
<mu2>0.5</mu2>
<kp>1e6</kp>
<kd>100</kd>
</gazebo>

<gazebo reference="right_wheel">
<mu1>0.5</mu1>
<mu2>0.5</mu2>
<kp>1e6</kp>
<kd>100</kd>
</gazebo>

Creating a Robot Controller​

Write a Python node that publishes joint commands to control the simulated robot:

# robot_sim/robot_sim/gazebo_controller.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import Float64MultiArray

class GazeboController(Node):
def __init__(self):
super().__init__('gazebo_controller')
self.pub = self.create_publisher(
Float64MultiArray,
'/simple_robot/joint_velocity_controller/commands',
10)
self.timer = self.create_timer(0.1, self.timer_callback)
self.step = 0

def timer_callback(self):
msg = Float64MultiArray()

# Oscillate wheel velocities
if self.step < 50:
msg.data = [2.0, 2.0] # Move forward
elif self.step < 100:
msg.data = [2.0, -2.0] # Turn right
else:
self.step = 0

self.pub.publish(msg)
self.step += 1

def main(args=None):
rclpy.init(args=args)
controller = GazeboController()
rclpy.spin(controller)

if __name__ == '__main__':
main()

CLI Commands​

Control Gazebo from the command line:

# Launch Gazebo with empty world
gazebo

# Launch with specific world file
gazebo worlds/empty.world

# Run Gazebo headless (no GUI, faster)
gazebo --headless -s libgazebo_ros_init.so -s libgazebo_ros_factory.so

# List spawned models
ros2 service call /gazebo/get_model_list gazebo_msgs/srv/GetModelList

# Pause simulation
ros2 service call /gazebo/pause_physics std_srvs/srv/Empty

# Unpause simulation
ros2 service call /gazebo/unpause_physics std_srvs/srv/Empty

# Delete a model
ros2 service call /gazebo/delete_model gazebo_msgs/srv/DeleteModel '{model_name: simple_robot}'

Exercise​

Task: Create a Gazebo world with a simple mobile robot that has two differential-drive wheels. Launch it in Gazebo and create a controller node that makes the robot drive in a square pattern (forward 10 steps, turn right 5 steps, repeat 4 times).

Acceptance Criteria:

  • URDF file defines robot with two wheels and proper inertia
  • Gazebo launches successfully with robot spawned
  • Controller node publishes to correct joint velocity topic
  • Robot completes full square pattern in simulation
  • Total simulation runtime under 30 seconds

Next: Chapter 10 - Navigation and SLAM