Using the gazebo_ros2_control Plugin to Connect ROS 2 Control with Gazebo Harmonic
Learn how to expose robot hardware interfaces in Gazebo Harmonic via the gazebo_ros2_control plugin, load standard ros2_control controllers, and verify simulation fidelity.
04 Oct 2025, 06:36 UTC

When you try to move a simulated robot in Gazebo using ROS 2 commands, the joint states may not update or the robot may drift unexpectedly.
Problem
Many tutorials show how to spawn a robot model in Gazebo and then drive it with a ROS 2 twist message, but the connection between the physics engine and the ROS 2 control loop is often missing. Without the proper plugin, the controller manager cannot read joint states or send commands, leaving the robot unresponsive.
Thesis
The gazebo_ros2_control plugin bridges Gazebo Harmonic (or later) and the ros2_control framework, letting you reuse standard ROS 2 controllers while Gazebo handles the physics.
Preparing the robot description
Add a gazebo block to your URDF or SDF that loads the plugin and exposes the hardware interfaces.
<gazebo>
<plugin filename='libgazebo_ros2_control.so' name='gazebo_ros2_control'>
<robotNamespace>/</robotNamespace>
</plugin>
</gazebo>
Make sure the URDF also contains <transmission> tags or <joint> definitions that ros2_control can interpret, and that each joint declares the command interface you plan to use (position, velocity, or effort).
Worked example: launching a differential‑drive robot
- Source your ROS 2 workspace:
source ~/ros2_ws/install/setup.bash- Launch Gazebo with the world that contains your model:
ros2 launch my_robot_gazebo world.launch.py world:=my_world.sdf- In another terminal, start the controller manager and spawn the standard controllers:
ros2 run controller_manager spawner joint_state_broadcaster --controller-manager /controller_managerros2 run controller_manager spawner diff_drive_controller --controller-manager /controller_manager- Verify that the plugin is active by checking the Gazebo console for a line similar to:
[gazebo_ros2_control] Loaded plugin for robot my_robot- Send a velocity command:
ros2 topic pub /diff_drive_controller/cmd_vel geometry_msgs/msg/Twist '{linear: {x: 0.5}, angular: {z: 0.2}}' -r 10- Monitor the joint states:
ros2 topic echo /joint_states- Watch the real‑time factor in Gazebo’s GUI; a value close to 1.0 indicates the control loop is keeping pace with physics.
Limitations and practical checks
- The plugin is only available in Gazebo Harmonic (2023.04) and later; older Gazebo Classic releases require the deprecated gazebo_ros_pkgs stack, which does not support the full ros2_control API.
- If the Gazebo solver timestep (set in the world SDF
<physics>tag) differs significantly from the ROS 2 control loop period, you may see jitter or instability. Reduce the max step size or use a fixed‑step update rate for the controller manager to mitigate this. - Joint limits, damping, and friction must be mirrored in both the URDF/SDF and the controller YAML configuration; mismatches can cause the simulator to apply forces the controller does not expect.
- Practical way to check the result: after sending a command, confirm that the joint positions in /joint_states change proportionally and that the odometry topic (e.g., /odom) reflects the expected motion. Additionally, run
ros2 topic hz /joint_statesto verify a steady publish rate.
Actionable closing
Start with a simple differential‑drive model, add the gazebo_ros2_control plugin tag, launch the simulation, and spawn the joint_state_broadcaster and your chosen controller. Verify the joint states and real‑time factor, then iterate on more complex robots or custom controllers. This approach gives you a reproducible pipeline where Gazebo handles the physics and ROS 2 handles the control logic, without modifying the simulator source.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.