ROB-110 · Lesson 11 of 12 · 28 min

Simulation, visualization and recording data

Testing every change on a real robot is slow, expensive and sometimes dangerous. Batteries run down, hardware breaks, the lab is booked, and a bug in a new navigation algorithm can drive a robot down a staircase. Robot programmers therefore lean on three kinds of tool. Simulators let you run your nodes against a virtual robot in a virtual world. Visualization tools let you see what the robot believes: its sensor data, its transforms, its planned path. And recording tools capture real data so you can replay a test run on your laptop as often as you like. This lesson introduces Gazebo, RViz and rosbag2, the standard tools for each job in ROS 2, and the time handling that ties them together.

Why simulate?

A simulator models a robot's body, sensors and actuators and the world around it, computing physics in small time steps. Your nodes talk to the simulated robot through exactly the same topics as the real one: the simulator publishes laser scans and odometry and subscribes to velocity commands. That means the same code can be developed and tested in simulation and then run unchanged on hardware. Simulation lets you test dangerous situations safely, run hundreds of automated tests overnight, try a robot design before building it and develop software before the hardware exists.

Simulation also has limits, summed up in the phrase sim-to-real gap. Simulated wheels do not slip quite like real ones, simulated sensors are cleaner than real ones unless noise is added deliberately, and real environments contain glass, sunlight, dust and people that simulators rarely capture well. Treat simulation as a fast, safe first test, never as proof that something works. ROB-360 returns to the sim-to-real problem in depth.

Some simulators commonly used with ROS 2.
SimulatorStrengthsTypical use with ROS 2
GazeboOpen source, tight ROS 2 integration, many sensors and pluginsMobile robots, arms, general development and teaching
NVIDIA Isaac SimPhotorealistic rendering, GPU physics, synthetic dataPerception, learning and industrial digital twins
WebotsOpen source, easy to start, many robot modelsEducation and research
MuJoCoFast, accurate contact physicsRobot learning and manipulation research

Gazebo and ROS 2

Gazebo is the simulator most closely associated with ROS. The older Gazebo Classic reached end of life in January 2025; today's Gazebo is a separate, modular rewrite with named releases. Each ROS 2 distribution has a recommended Gazebo release: Jazzy pairs with Gazebo Harmonic, and Lyrical with Gazebo Jetty, a long-term support release supported until 2031. Worlds and robot models in Gazebo are described in SDF, the Simulation Description Format, an XML format similar to the URDF robot descriptions used by ROS; Gazebo can load URDF files directly.

Gazebo has its own messaging system, separate from ROS 2, so a bridge connects the two. The ros_gz_bridge package translates chosen topics between Gazebo and ROS 2 message types. In its command-line form, each argument names a topic, its ROS type and its Gazebo type, with a symbol showing the direction: a closing square bracket for ROS to Gazebo, an opening one for Gazebo to ROS and an @ sign alone for both directions.

Running Gazebo and bridging topics to ROS 2
# start Gazebo with a world through ROS 2
ros2 launch ros_gz_sim gz_sim.launch.py gz_args:=empty.sdf
# bridge velocity commands, laser scans and the clock
ros2 run ros_gz_bridge parameter_bridge \
  /cmd_vel@geometry_msgs/msg/Twist]gz.msgs.Twist \
  /scan@sensor_msgs/msg/LaserScan[gz.msgs.LaserScan \
  /clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock

In practice you rarely type these commands: robot vendors and tutorials supply launch files that start Gazebo with a world, spawn the robot model, start the bridge with a configuration file and launch the robot's ROS 2 nodes. Many popular teaching robots, such as the TurtleBot series, come with ready-made simulation packages, which are the easiest way to practice everything in this course without hardware.

Simulated time

A simulator does not necessarily run at the speed of real time. A simple world may run five times faster than real time, which is useful for long automated tests; a complex one may manage only half speed. The ratio of simulated time to real time is the real-time factor. If your nodes used the computer's clock, their timers and timestamps would not match the simulation, and controllers tuned in seconds would behave strangely. ROS 2 solves this with the /clock topic, on which the simulator publishes simulated time, and the use_sim_time parameter, which every node has. When use_sim_time is true, the node's clock follows /clock, so self.get_clock().now(), timers and timestamps all use simulated time. Set it for every node in a simulation, usually from the launch file, and never mix nodes using real time with nodes using simulated time.

Worked problem

Real-time factor and test duration

An automated test drives a simulated robot through a 15-minute delivery route. On a powerful build server the simulation runs at a real-time factor of 4; on a laptop, at 0.6. How long does the test take on each, and how many runs fit in an 8-hour night on the server?

Show solution
  1. Server: \( 15 / 4 = 3.75 \) minutes of real time.
  2. Laptop: \( 15 / 0.6 = 25 \) minutes.
  3. Runs per night on the server: \( 8 \times 60 / 3.75 = 128 \).

Answer: About 3.75 minutes on the server and 25 minutes on the laptop; 128 runs overnight on the server. Because nodes follow /clock, their behavior is the same at either speed, provided they keep up.

The last condition matters. A node that needs 30 ms of computer time per cycle can keep up with a 20 Hz timer in real time, but not when simulated time runs four times faster, because each 50 ms of simulated time now lasts only 12.5 ms of real time. If tests pass at a real-time factor of 1 and fail at 4, suspect a node that cannot keep up rather than a bug in its logic.

Visualizing with RViz and rqt

RViz, run as rviz2, is the standard 3-D visualization tool for ROS 2. It subscribes to topics and draws them: laser scans as points, maps as images, paths as lines, the robot's model from its URDF, and the transform tree as labeled axes. Every display needs a fixed frame, the frame in which everything is drawn, set in the Global Options panel; choose odom or map for a moving robot, and base_link to see sensor data from the robot's point of view. If a display shows nothing or reports an error, the most common causes are a fixed frame that does not exist, a missing transform from the data's frame to the fixed frame, or a QoS mismatch, all of which RViz reports in its status panel.

Save RViz's configuration to a file with File, Save Config, put it in your package and start RViz from your launch file with that configuration, so that everyone on the team sees the same displays. The rqt suite complements RViz with 2-D tools: rqt_graph draws the computation graph, rqt_plot plots numeric fields over time, which is invaluable for tuning controllers, and rqt_console filters log messages by node and severity. Foxglove, a separate visualization application, can connect to a live system or open recorded files and is widely used for reviewing data.

Recording and replaying with rosbag2

ros2 bag records messages from chosen topics to a file, called a bag, and plays them back later with their original timing. A bag captures exactly what the robot saw and did during a test, so you can debug a failure at your desk, replay the same data through a new version of a node to compare results and share real data with teammates. Since the Iron release, rosbag2 writes bags in the MCAP format by default, a single-file format that many tools, including Foxglove, can read.

Recording, inspecting and playing back a bag
# record chosen topics (always include the transforms)
ros2 bag record -o run1 /scan /odom /tf /tf_static /cmd_vel
# what is in it?
ros2 bag info run1
# play back at half speed, publishing /clock
ros2 bag play run1 --rate 0.5 --clock

Record the transforms along with the data, or the replayed scans cannot be placed in the world. When you play a bag with --clock, it publishes /clock from the recorded timestamps; set use_sim_time to true for the nodes receiving the replay so that their clocks follow the recording. Recording everything with ros2 bag record -a is tempting but can produce enormous files, especially with uncompressed images, so plan what you record.

Planning storage for a recording session
# Plan the storage for recording a test session
topics = {            # name: data rate in megabytes per second
    "/scan": 0.35,
    "/odom": 0.04,
    "/tf": 0.02,
    "/camera/image_raw/compressed": 1.80,
}
hours = 2.5
card_gb = 128

rate = sum(topics.values())                    # MB/s
size_gb = rate * hours * 3600 / 1000
print(f"total rate {rate:.2f} MB/s")
print(f"{hours} h session: {size_gb:.1f} GB")
print(f"sessions per {card_gb} GB card: {int(card_gb // size_gb)}")
Output
total rate 2.21 MB/s
2.5 h session: 19.9 GB
sessions per 128 GB card: 6

Bags are also data for analysis. rosbag2 provides a Python API for reading messages, and tools can export topics to CSV files for spreadsheets or NumPy. Once the data is in Python, simple analyses answer questions that are hard to judge by eye, such as how far the robot actually drove and how fast it went. The program below works on odometry positions extracted from a bag.

Analyzing odometry extracted from a recorded bag
import math

# (time s, x m, y m) samples extracted from a recorded /odom topic
odom = [(0.0, 0.00, 0.00), (1.0, 0.30, 0.00), (2.0, 0.62, 0.05),
        (3.0, 0.90, 0.25), (4.0, 1.05, 0.55), (5.0, 1.10, 0.90)]

dist, top = 0.0, 0.0
for (t0, x0, y0), (t1, x1, y1) in zip(odom, odom[1:]):
    step = math.hypot(x1 - x0, y1 - y0)
    dist += step
    top = max(top, step / (t1 - t0))
print(f"path length {dist:.2f} m in {odom[-1][0]:.0f} s")
print(f"average speed {dist / odom[-1][0]:.2f} m/s, "
      f"fastest segment {top:.2f} m/s")
Output
path length 1.66 m in 5 s
average speed 0.33 m/s, fastest segment 0.35 m/s

Worked problem

Why the path length is longer than the straight line

The odometry above starts at (0, 0) and ends at (1.10, 0.90). Compare the straight-line distance with the path length of 1.66 m.

Show solution
  1. Straight line: \( \sqrt{1.10^2 + 0.90^2} = \sqrt{2.02} \approx 1.42 \) m.
  2. The path length sums each 1-second segment, following the curve: 1.66 m.
  3. The difference, \( 1.66 - 1.42 = 0.24 \) m, comes from the robot turning left as it drove.

Answer: The straight-line distance is about 1.42 m, shorter than the 1.66 m path. Sampling at only 1 Hz also slightly underestimates the true path length, because it cuts corners on the curve.

A development workflow that uses all three

Experienced teams combine these tools into a routine. New code is first exercised against recorded bags of real data, which is fast and repeatable. Then it runs in simulation, where it can act and see the consequences, including in scenarios too risky to try for real. Only then does it go on the robot, with a bag recording running, so that any surprise can be replayed and examined afterwards. RViz, with a shared configuration, is open at every stage. This loop of record, replay, simulate and test is how robot software gets better quickly without wearing out robots or people.

Common mistakes

  • MistakeRunning some nodes with use_sim_time true and others with it false.

    InsteadSet use_sim_time consistently for every node in a simulation or bag replay, usually from the launch file.

  • MistakeRecording scans without /tf and /tf_static.

    InsteadAlways record the transforms so data can be placed in its frames on replay.

  • MistakeChoosing a fixed frame in RViz that does not exist or has no transform to the data.

    InsteadPick map, odom or base_link as appropriate and check the status panel for transform errors.

  • MistakeRecording every topic, including raw camera images, for hours.

    InsteadEstimate data rates, record only what you need and prefer compressed image topics.

  • MistakeTreating success in simulation as proof that code works on the robot.

    InsteadUse simulation as a first test, then test on hardware with recordings running.

Summary and key terms

  • Simulators let the same ROS 2 nodes run against virtual robots; the sim-to-real gap means simulation is a first test, not proof.
  • Gazebo pairs with ROS 2 distributions (Harmonic with Jazzy, Jetty with Lyrical); ros_gz_bridge connects Gazebo topics to ROS 2.
  • The real-time factor is simulated time divided by real time; /clock and use_sim_time keep node clocks in step with the simulation.
  • RViz draws topics and transforms in a chosen fixed frame; rqt tools plot data, draw the graph and filter logs.
  • ros2 bag records chosen topics to MCAP files and replays them with --clock for repeatable debugging.
  • Estimate bag sizes from data rates; always record transforms with sensor data.
  • Recorded data can be analyzed in Python to measure distance, speed and other quantities.
Simulator
Software that models a robot, its sensors and its world so robot software can run against it.
Sim-to-real gap
The differences between simulated and real behavior that can make simulated results misleading.
SDF
The Simulation Description Format used by Gazebo for worlds and models.
ros_gz_bridge
The package that translates topics between Gazebo and ROS 2.
Real-time factor
The ratio of simulated time to elapsed real time.
/clock
The topic on which simulated or replayed time is published.
RViz
The ROS 2 3-D visualization tool for sensor data, transforms and robot models.
Fixed frame
The frame in which RViz draws all its displays.
Bag
A file of recorded ROS 2 messages that can be replayed with original timing.
MCAP
The default single-file storage format for rosbag2 recordings.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. Which Gazebo release is recommended for ROS 2 Lyrical?

    Show answer

    Gazebo Jetty.

  2. In ros_gz_bridge syntax, which direction does /cmd_vel@geometry_msgs/msg/Twist]gz.msgs.Twist bridge?

    Show answer

    From ROS 2 to Gazebo.

  3. A simulation runs 10 simulated minutes in 25 real minutes. What is the real-time factor?

    Show answer

    10 / 25 = 0.4.

  4. Why must use_sim_time be set when replaying a bag with --clock?

    Show answer

    So the nodes' clocks follow the recorded time published on /clock.

  5. RViz shows the laser display in error with a message about a missing transform. Name two likely causes.

    Show answer

    The fixed frame has no transform path to the laser frame, or /tf or /tf_static is not being published or recorded.

  6. Estimate the size of a 1-hour bag at 3 MB/s.

    Show answer

    3 × 3600 = 10 800 MB, about 10.8 GB.

  7. Which command shows the topics, message counts and duration of a bag?

    Show answer

    ros2 bag info followed by the bag name.

  8. Give two reasons to test new code against recorded bags before simulation.

    Show answer

    It is fast and repeatable, and it uses real sensor data with real noise.

  9. A node needs 40 ms per cycle and runs a 20 Hz timer. Can it keep up at a real-time factor of 2?

    Show answer

    No: 50 ms of simulated time lasts 25 ms of real time, less than the 40 ms needed.

  10. Which rqt tool would you use to tune a controller by watching a value over time?

    Show answer

    rqt_plot.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz