In the previous chapter, the golden sentence was: "RViz is the robot's information window." Now it's time to complete the second sentence: Gazebo is the robot's virtual laboratory. The difference between the two becomes clear with a simple experiment: if you place ARCHO one meter above the ground, in RViz it will just stay suspended in the air โ because RViz only displays what it receives over the network. But in Gazebo, gravity is applied, ARCHO falls, and collides with the ground.
If you've designed a car, CAD software only shows what it looks like. But a driving simulator shows whether it moves, whether it tips over, whether it collides with a wall, whether the brakes work. Gazebo does exactly this for ARCHO โ before even a single real part is purchased or assembled.
Gazebo has a full physics engine that computes the following:
| Physical concept | Effect on ARCHO |
|---|---|
| Gravity | If Collision or Inertia is wrong, the robot falls or sinks into the ground |
| Mass and inertia | The same values we calculated in Chapter 4 for the body and wheels |
| Friction | If it's too low, the wheel spins but the robot doesn't move (like driving on ice) |
| Contact (Collision) | Wheel-to-ground contact, body-to-warehouse-wall contact |
The ratio of simulation speed to real time. RTF = 1.0 means one second of simulation
equals exactly one real second. RTF = 0.5 means the simulation runs at half real-time
speed โ usually due to a heavy mesh, too many sensors, or weak hardware. For everyday testing, an
RTF close to 1 is ideal.
Gazebo computes physics in small time steps (e.g., 0.001 seconds). A smaller step means higher accuracy but lower speed; a larger step means higher speed but lower accuracy. This is the same trade-off we saw in Chapter 4 between a detailed Visual mesh and a simple Collision mesh โ again, we're dealing with the trade-off between accuracy and performance.
In Gazebo there isn't just a robot โ we have a complete World: floor, walls, shelves,
lighting, and obstacles. The World file (extension .sdf, or the older .world)
describes this environment.
archo_ws/src/archo_gazebo/
โโโ worlds/
โ โโโ warehouse.world
โโโ models/
โโโ launch/
โโโ config/
A simple warehouse.world can include a floor, a few walls, several metal shelving units, and
ambient lighting โ exactly the scene in which ARCHO will need to build a map and find a path in later
chapters.
The typical model-building path: design in a CAD program โ export a mesh โ define it in URDF/Xacro (Chapter 4) โ load it into Gazebo. But here's the important point: URDF doesn't just define appearance; for Gazebo to show correct physical behavior, the mass, inertia, collision, and joints must also be valid โ the very same ones we wrote carefully in Chapter 4.
For ROS 2 Jazzy, the standard path is gz_ros2_control (the new generation of Gazebo, not the
old Gazebo Classic). First, we install the required packages:
sudo apt update
sudo apt install \
ros-jazzy-ros-gz \
ros-jazzy-gz-ros2-control \
ros-jazzy-ros2-control \
ros-jazzy-ros2-controllers
Now inside archo.urdf.xacro, we tell each wheel joint what interface it has with Gazebo:
<ros2_control name="ArchoGazeboSystem" type="system">
<hardware>
<plugin>gz_ros2_control/GazeboSimSystem</plugin>
</hardware>
<joint name="left_wheel_joint">
<command_interface name="velocity"/>
<state_interface name="position"/>
<state_interface name="velocity"/>
</joint>
<joint name="right_wheel_joint">
<command_interface name="velocity"/>
<state_interface name="position"/>
<state_interface name="velocity"/>
</joint>
</ros2_control>
<gazebo>
<plugin
filename="gz_ros2_control-system"
name="gz_ros2_control::GazeboSimROS2ControlPlugin">
<parameters>$(find archo_bringup)/config/controllers.yaml</parameters>
</plugin>
</gazebo>
| Concept | Meaning |
|---|---|
command_interface | What we command this joint from the outside (here: velocity) |
state_interface | What we read from this joint (position, actual velocity) |
This block only defines the interface; exactly how we distribute the velocity command between the two wheels โ meaning the Controller itself โ is the topic of the next chapter (ros2_control).
The complete launch architecture follows these four stages:
ros2 launch ros_gz_sim gz_sim.launch.py gz_args:=warehouse.world
ros2 run ros_gz_sim create -topic robot_description -name archo -z 0.2
After the robot appears, check that the controllers have activated:
If they didn't activate automatically:
ros2 run controller_manager spawner joint_state_broadcaster
ros2 run controller_manager spawner diff_drive_controller
One of Gazebo's best capabilities is sensor simulation โ LiDAR, camera, IMU, and encoders. We define
ARCHO's LiDAR on the same laser_link we built in Chapter 4:
<gazebo reference="laser_link">
<sensor name="lidar_sensor" type="gpu_lidar">
<topic>scan</topic>
<update_rate>10</update_rate>
<lidar>
<scan>
<horizontal>
<samples>720</samples>
<min_angle>-3.14159</min_angle>
<max_angle>3.14159</max_angle>
</horizontal>
</scan>
<range>
<min>0.10</min>
<max>12.0</max>
<resolution>0.01</resolution>
</range>
</lidar>
<always_on>true</always_on>
</sensor>
</gazebo>
ros2 topic list | grep scan
ros2 topic hz /scan
In the real world, no sensor is perfect โ a LiDAR might measure a distance of 2.000 meters as 1.994 or 2.006. If the simulation has no noise at all, algorithms that are tested and tuned against it may fail when transferred to the real robot. Gazebo lets you add controlled noise to LiDAR, IMU, and camera sensors so the simulation stays closer to reality.
Important point: algorithms like SLAM Toolbox or Nav2, which we'll see in later chapters, have no idea
whether the LiDAR is real or virtual. They just listen to the /scan topic with message
type sensor_msgs/LaserScan. This is exactly what makes Gazebo such a powerful tool for
development without real hardware.
Now everything is ready to see the complete loop of ARCHO's motion:
For a manual test, send a straight-line motion command:
ros2 topic pub /diff_drive_controller/cmd_vel geometry_msgs/msg/TwistStamped \
"{twist: {linear: {x: 0.3}, angular: {z: 0.0}}}"
And for a turn:
ros2 topic pub /diff_drive_controller/cmd_vel geometry_msgs/msg/TwistStamped \
"{twist: {linear: {x: 0.0}, angular: {z: 0.7}}}"
When you tell the robot "move forward at a linear speed of 0.4 meters per second," in reality the speed
of each of the two wheels must be computed separately. If v is the linear speed,
ฯ the angular speed, L the distance between the wheels, and r
the wheel radius:
ฯ_left = (v โ ฯL/2) / r
ฯ_right = (v + ฯL/2) / r
For example, with v=0.4, ฯ=0, L=0.4, r=0.08, both
wheels must rotate at 5 radians per second. We don't write this calculation ourselves โ
diff_drive_controller performs this conversion for us in the next chapter.
ros2 control list_controllers shows both controllers as active./scan is publishing, and LiDAR points are visible in RViz.
ARCHO is no longer just a static model โ it now lives in a physical world, feels its own weight, collides
with the warehouse floor, and for the first time actually moves with a cmd_vel command. Its
LiDAR publishes realistic (noisy) data on /scan โ the very topic and concept that SLAM and
Nav2 will build on in later chapters.
ARCHO Project now has a new package โ archo_gazebo โ with a warehouse world and a full connection to physics. We're still sending velocity commands manually; the next chapter shows how ros2_control correctly distributes these commands between the two wheels.
In Chapter 8, we'll take a close look at the ros2_control layer: the Controller Manager, the types of controllers, and the detailed configuration of the diff_drive_controller that we saw briefly today.
ros-jazzy-ros2-controllers and seeing a Controller not found error.-z 0.2).