Chapter 3: Robot Simulation with Gazebo
Why Simulation?
Simulation allows you to:
- ā Test algorithms without hardware
- ā Rapid prototyping and iteration
- ā Safe testing of dangerous scenarios
- ā Multi-robot experiments
- ā Reproducible results
Gazebo Simulator
Gazebo is a 3D robot simulator with:
- Physics engine (gravity, friction, collisions)
- Sensor simulation (LiDAR, cameras, IMU)
- Plugin system for custom behaviors
- ROS2 integration
Installation
sudo apt install ros-humble-gazebo-ros-pkgs
sudo apt install ros-humble-gazebo-ros2-control
URDF (Unified Robot Description Format)
URDF describes robot structure using XML.
Simple Robot Example
<?xml version="1.0"?>
<robot name="simple_robot">
<!-- Base Link -->
<link name="base_link">
<visual>
<geometry>
<box size="0.6 0.4 0.2"/>
</geometry>
<material name="blue">
<color rgba="0 0 0.8 1"/>
</material>
</visual>
<collision>
<geometry>
<box size="0.6 0.4 0.2"/>
</geometry>
</collision>
<inertial>
<mass value="10"/>
<inertia ixx="0.4" ixy="0.0" ixz="0.0"
iyy="0.4" iyz="0.0" izz="0.2"/>
</inertial>
</link>
<!-- Wheel Link -->
<link name="left_wheel">
<visual>
<geometry>
<cylinder radius="0.1" length="0.05"/>
</geometry>
<material name="black">
<color rgba="0 0 0 1"/>
</material>
</visual>
<collision>
<geometry>
<cylinder radius="0.1" length="0.05"/>
</geometry>
</collision>
<inertial>
<mass value="1"/>
<inertia ixx="0.01" ixy="0.0" ixz="0.0"
iyy="0.01" iyz="0.0" izz="0.01"/>
</inertial>
</link>
<!-- Joint -->
<joint name="left_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="left_wheel"/>
<origin xyz="-0.2 0.25 0" rpy="1.57 0 0"/>
<axis xyz="0 0 1"/>
</joint>
</robot>
Launch Robot in Gazebo
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import ExecuteProcess
import os
def generate_launch_description():
# Path to URDF file
urdf_file = os.path.join(
get_package_share_directory('my_robot'),
'urdf',
'robot.urdf'
)
return LaunchDescription([
# Start Gazebo
ExecuteProcess(
cmd=['gazebo', '--verbose', '-s', 'libgazebo_ros_factory.so'],
output='screen'
),
# Spawn robot
Node(
package='gazebo_ros',
executable='spawn_entity.py',
arguments=['-entity', 'my_robot', '-file', urdf_file],
output='screen'
),
# Robot state publisher
Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{'robot_description': urdf_file}]
),
])
Adding Sensors
LiDAR Sensor
<gazebo reference="laser_link">
<sensor name="laser" type="ray">
<pose>0 0 0 0 0 0</pose>
<visualize>true</visualize>
<update_rate>10</update_rate>
<ray>
<scan>
<horizontal>
<samples>360</samples>
<resolution>1</resolution>
<min_angle>-3.14159</min_angle>
<max_angle>3.14159</max_angle>
</horizontal>
</scan>
<range>
<min>0.1</min>
<max>10.0</max>
<resolution>0.01</resolution>
</range>
</ray>
<plugin name="laser_controller" filename="libgazebo_ros_ray_sensor.so">
<ros>
<namespace>/</namespace>
<remapping>~/out:=scan</remapping>
</ros>
<output_type>sensor_msgs/LaserScan</output_type>
<frame_name>laser_link</frame_name>
</plugin>
</sensor>
</gazebo>
Camera Sensor
<gazebo reference="camera_link">
<sensor name="camera" type="camera">
<update_rate>30</update_rate>
<camera>
<horizontal_fov>1.3962634</horizontal_fov>
<image>
<width>640</width>
<height>480</height>
<format>R8G8B8</format>
</image>
<clip>
<near>0.02</near>
<far>300</far>
</clip>
</camera>
<plugin name="camera_controller" filename="libgazebo_ros_camera.so">
<ros>
<namespace>/</namespace>
<remapping>image_raw:=camera/image_raw</remapping>
<remapping>camera_info:=camera/camera_info</remapping>
</ros>
<camera_name>camera</camera_name>
<frame_name>camera_link</frame_name>
</plugin>
</sensor>
</gazebo>
Differential Drive Plugin
<gazebo>
<plugin name="diff_drive" filename="libgazebo_ros_diff_drive.so">
<update_rate>50</update_rate>
<left_joint>left_wheel_joint</left_joint>
<right_joint>right_wheel_joint</right_joint>
<wheel_separation>0.5</wheel_separation>
<wheel_diameter>0.2</wheel_diameter>
<max_wheel_torque>20</max_wheel_torque>
<max_wheel_acceleration>1.0</max_wheel_acceleration>
<command_topic>cmd_vel</command_topic>
<odometry_topic>odom</odometry_topic>
<odometry_frame>odom</odometry_frame>
<robot_base_frame>base_link</robot_base_frame>
<publish_odom>true</publish_odom>
<publish_odom_tf>true</publish_odom_tf>
<publish_wheel_tf>true</publish_wheel_tf>
</plugin>
</gazebo>
Teleoperation
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
import sys, select, termios, tty
class TeleopKeyboard(Node):
def __init__(self):
super().__init__('teleop_keyboard')
self.publisher = self.create_publisher(Twist, 'cmd_vel', 10)
self.linear_speed = 0.5
self.angular_speed = 1.0
def get_key(self):
tty.setraw(sys.stdin.fileno())
select.select([sys.stdin], [], [], 0)
key = sys.stdin.read(1)
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, settings)
return key
def run(self):
print("Control your robot!")
print("w/s: forward/backward")
print("a/d: turn left/right")
print("q: quit")
while True:
key = self.get_key()
twist = Twist()
if key == 'w':
twist.linear.x = self.linear_speed
elif key == 's':
twist.linear.x = -self.linear_speed
elif key == 'a':
twist.angular.z = self.angular_speed
elif key == 'd':
twist.angular.z = -self.angular_speed
elif key == 'q':
break
self.publisher.publish(twist)
Visualizing in RViz2
rviz2
Add displays:
- RobotModel
- LaserScan
- Camera
- TF
Key Takeaways
- ā URDF describes robot structure
- ā Gazebo simulates physics and sensors
- ā Plugins add functionality
- ā RViz2 visualizes robot state
- ā Teleoperation for manual control
Next: Chapter 4 - Navigation and SLAM!