What Is a Robot Digital Twin and How Do You Build One with ROS 2 and Gazebo?
Adrian Krzemiński,

TL;DR: A robot digital twin is a simulated, physics-aware replica of a physical robot that shares its kinematics, sensors, control interfaces, and ROS 2 topics with the real hardware. With ROS 2 (Humble or Jazzy) and Gazebo (Fortress for Humble, Harmonic for Jazzy, or Harmonic on Humble with matching packages), you can build a working digital twin of a mobile robot like the Leo Rover using a URDF/Xacro description, the ros_gz packages, and a differential-drive plugin. This article explains what a digital twin is, how it differs from a plain simulation, and walks through a reproducible setup with working code.
What a Robot Digital Twin Is in the Context of ROS 2
A robot digital twin in ROS 2 is more than a 3D model in a simulator. It is a bidirectional, continuously synchronized representation of a physical robot: the same URDF, the same controller interfaces, the same topic names, the same sensor message types, and ideally the same timing characteristics. When you publish a /cmd_vel command, both the real robot and the twin should respond in equivalent ways.
A plain simulation answers “does my algorithm work in principle?” A digital twin answers “does my algorithm work on this specific robot, with this specific sensor placement, with these specific wheel radii and gear ratios?” The distinction matters in field robotics, where wheel slip, sensor noise, and chassis geometry change behavior in ways generic simulations hide.
Three properties separate a digital twin from a demo scene:
- Identity of interfaces: the twin exposes the same ROS 2 topics, services, and actions as the hardware.
- Identity of parameters: mass, inertia, wheel radius, sensor frame transforms, and joint limits come from the same source of truth (typically a Xacro file).
- Continuous synchronization: updates to the physical robot (a new lidar, a recalibrated IMU) are reflected in the twin, and vice versa.
Why You Build a Digital Twin Instead of Just a Simulation
Field robotics deployments, including research missions and space analog testing, share a constraint: hardware access is limited, expensive, or risky. A digital twin lets you iterate on autonomy stacks (Nav2, SLAM Toolbox, behavior trees) without booking time on the physical platform.
Concrete use cases where a digital twin pays off:
- Regression testing of navigation stacks after a ROS 2 update, run headlessly in CI.
- Training and evaluating perception models with synthetic data that matches your real camera intrinsics.
- Reproducing field failures: replay a rosbag from the real rover into the twin and inspect what the planner saw.
- Onboarding new team members without giving them root access to a physical rover.
What You Need to Build a Leo Rover Digital Twin
The reference stack for this tutorial uses widely supported, currently maintained components. The Leo Rover is a 4-wheel skid-steer UGV with a Raspberry Pi compute unit, and its URDF and ROS 2 packages are published openly on GitHub.
Prerequisites for the robot digital twin in ROS 2 workflow:
- Ubuntu 22.04 with ROS 2 Humble, or Ubuntu 24.04 with ROS 2 Jazzy.
- Gazebo Fortress for Humble, or Gazebo Harmonic for Jazzy. If you use Harmonic with Humble, install the matching
ros-humble-ros-gzharmonicpackages instead of the default Humble/Fortress set. - Packages:
ros-${ROS_DISTRO}-ros-gz,ros-${ROS_DISTRO}-xacro,ros-${ROS_DISTRO}-robot-state-publisher,ros-${ROS_DISTRO}-ros2-control,ros-${ROS_DISTRO}-ros2-controllers. - The official Leo Rover description package from
github.com/LeoRover/leo_common-ros2.
Install the dependencies with a single command for Jazzy/Harmonic:
sudo apt install ros-${ROS_DISTRO}-ros-gz ros-${ROS_DISTRO}-xacro ros-${ROS_DISTRO}-robot-state-publisher ros-${ROS_DISTRO}-ros2-control ros-${ROS_DISTRO}-ros2-controllers ros-${ROS_DISTRO}-gz-ros2-control
For Humble/Fortress, use the Fortress-compatible package names from your ROS package index; in some setups the Gazebo ROS 2 control package is published as ros-humble-ign-ros2-control.
How to Structure the URDF/Xacro for a Digital Twin
The single source of truth for geometry and dynamics is a Xacro file. The same file feeds the real robot’s robot_state_publisher and the Gazebo simulation. This is what makes the model a twin rather than a lookalike.
A minimal Xacro snippet for the Leo Rover chassis, with the Gazebo-specific blocks kept in the same file using conditional includes:
<?xml version="1.0"?>
<robot name="leo" xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:arg name="use_sim" default="false"/>
<xacro:include filename="$(find-pkg-share leo_description)/urdf/leo.urdf.xacro"/>
<xacro:if value="$(arg use_sim)">
<xacro:include filename="$(find-pkg-share leo_gz_bringup)/urdf/leo.gazebo.xacro"/>
</xacro:if>
</robot>
The Gazebo-specific Xacro adds the differential-drive plugin, sensor plugins, and material properties. For a skid-steer rover, you publish four wheel joint states but use a differential-drive controller that treats left and right wheel pairs as virtual axles:
<gazebo>
<plugin filename="gz-sim-diff-drive-system" name="gz::sim::systems::DiffDrive">
<left_joint>wheel_FL_joint</left_joint>
<left_joint>wheel_RL_joint</left_joint>
<right_joint>wheel_FR_joint</right_joint>
<right_joint>wheel_RR_joint</right_joint>
<wheel_separation>0.358</wheel_separation>
<wheel_radius>0.0625</wheel_radius>
<topic>/cmd_vel</topic>
<odom_topic>/odom</odom_topic>
<frame_id>odom</frame_id>
<child_frame_id>base_footprint</child_frame_id>
</plugin>
</gazebo>
The wheel separation (0.358 m) and wheel radius (0.0625 m) values should match the Leo Rover URDF or measurements from your hardware. Using approximate or rounded values here is the most common source of odometry drift between twin and hardware.
How to Launch the Twin and Bridge Topics to ROS 2
Gazebo Sim communicates over its own transport layer (Gazebo Transport; in Fortress-era tooling, Ignition Transport). To make topics appear in ROS 2, you run ros_gz_bridge. A launch file ties together robot_state_publisher, the Gazebo server, the spawner, and the bridge.
A minimal launch sequence, ordered by what must start first:
- Start
robot_state_publisherwith the Xacro-expanded URDF anduse_sim_time:=true. - Start Gazebo with an empty or terrain world:
gz sim -r empty.sdffor Harmonic, orign gazebo -r empty.sdffor Fortress. - Spawn the robot from the
/robot_descriptiontopic usingros2 run ros_gz_sim create -topic /robot_description. - Start
ros_gz_bridgewith a YAML config mapping/clock,/cmd_vel,/odom,/tf,/imu, and camera topics.
A bridge configuration entry for the velocity command and odometry looks like this:
- ros_topic_name: "/cmd_vel"
gz_topic_name: "/cmd_vel"
ros_type_name: "geometry_msgs/msg/Twist"
gz_type_name: "gz.msgs.Twist"
direction: ROS_TO_GZ
- ros_topic_name: "/odom"
gz_topic_name: "/odom"
ros_type_name: "nav_msgs/msg/Odometry"
gz_type_name: "gz.msgs.Odometry"
direction: GZ_TO_ROS
After launching, verify the twin responds to commands:
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.2}, angular: {z: 0.0}}" -r 10
You should see the rover move forward in Gazebo and odometry increase on /odom. If the linear velocity in /odom does not match 0.2 m/s within a small tolerance, your wheel radius, controller config, or bridge/plugin config is wrong. Use an angular velocity test to validate the wheel separation.
How to Validate That the Twin Matches the Real Robot
Building the model is the easy part. Validating it is what turns a simulation into a digital twin. For mobile robots used in research deployments, three checks catch most drift:
- Open-loop kinematics test: command a fixed velocity for a fixed duration on both real hardware and twin, then compare measured displacement. Differences above approximately 5% point to wrong wheel radius, slip parameters, or controller limits.
- Sensor frame check: publish a static fiducial in front of the camera in both worlds and compare the detected pose. Mismatches indicate wrong
<origin>blocks in the URDF. - Rosbag replay: record
/cmd_velfrom a real field run, replay it into the twin, and overlay the resulting/odomtraces. Systematic curvature differences point to skid-steer friction parameters that need tuning.
Frequently Asked Questions
Is a robot digital twin in ROS 2 the same as a Gazebo simulation?
No. A Gazebo simulation becomes a digital twin only when it shares the URDF, topic interfaces, and parameters with a specific physical robot and is kept synchronized with it. A generic demo world is not a twin.
Which Gazebo version should you use with ROS 2?
For ROS 2 Jazzy, use Gazebo Harmonic. For ROS 2 Humble, Gazebo Fortress is the default pairing, with Harmonic available via the ros-humble-ros-gzharmonic package set. Classic Gazebo 11 reached end of life in January 2025.
Can you run the Leo Rover digital twin headlessly in CI?
Yes. Start Gazebo with gz sim -s -r world.sdf for Harmonic, or ign gazebo -s -r world.sdf for Fortress, and run your test nodes against the bridged topics. This can work on GitHub Actions runners when rendering is disabled or configured for headless/software rendering.
How do you simulate sensor noise realistically?
Gazebo sensor plugins accept <noise> blocks with Gaussian parameters. Measure your real IMU or lidar noise from a stationary recording and copy the standard deviation into the twin. Without this step, perception code trained in simulation will overperform on the twin.
Does the twin need the same compute constraints as the real robot?
Not for functional testing, but for timing-sensitive validation you should throttle the simulation real-time factor to 1.0 and run nodes with the same QoS profiles you use on hardware. Otherwise you will miss race conditions that only appear under realistic CPU load.
Where is the Leo Rover URDF maintained?
The official description, Gazebo bringup, and ROS 2 control packages are maintained at github.com/LeoRover/leo_common-ros2 and github.com/LeoRover/leo_simulator-ros2. These are the canonical sources to fork for a custom twin.
Can you use the same twin for Nav2 and SLAM Toolbox development?
Yes. Once /odom, /tf, /scan, and /cmd_vel are bridged, Nav2 and SLAM Toolbox treat the twin identically to the real robot. This is the main practical payoff of building a twin rather than a one-off simulation.
Start Building Your Twin
The fastest path to a working Leo Rover digital twin is to clone the official simulation repository, expand the Xacro for your sensor configuration, and validate against an open-loop displacement test. Documentation, URDF sources, and ready-to-run launch files are available at the Leo Rover product page and on the Leo Rover GitHub organization.