Integrating ROS 2 Control with Gazebo via the gazebo_ros2_control Plugin: Architecture Note
A concise architecture note on using gazebo_ros2_control to bridge ROS 2 controllers with Gazebo simulation, covering requirements, minimal design, boundaries, checks, failures, and redesign triggers.
02 Sept 2026, 03:15 UTC

Requirements
To drive simulated joints with standard ROS 2 controllers, the Gazebo simulation must expose joint command and state interfaces through a ROS 2 node that runs inside the Gazebo process. The required inputs are:
- A robot description (SDF or URDF) that lists each joint by name and declares its limits.
- A Gazebo world file that loads the
gazebo_ros2_controlplugin. - A ROS 2 controller configuration YAML that maps controller types (e.g.,
effort_controllers/JointVelocityController) to the joint names exposed in the description.
Minimal Viable Design
The smallest configuration that satisfies the requirements consists of three files:
- robot.sdf – contains the
<plugin>tag:
<plugin filename="libgazebo_ros2_control.so" name="gazebo_ros2_control">
<robotNamespace>/my_robot</robotNamespace>
</plugin>
- controller.yaml – example for a differential‑drive robot:
controller_manager:
ros__parameters:
update_rate: 100 # Hz
diff_drive_controller:
ros__parameters:
left_wheel_names: ['left_wheel_joint']
right_wheel_names: ['right_wheel_joint']
wheel_separation: 0.5
wheel_radius: 0.1
# odometry topic optional
joint_state_broadcaster:
ros__parameters:
# defaults are fine
- empty.world – a minimal world that includes the robot SDF via
<include>.
When Gazebo starts, the plugin loads a ROS 2 node named gazebo_ros2_control inside the Gazebo process. This node subscribes to controller command topics (e.g., /my_robot/diff_drive_controller/cmd_vel) and publishes joint states on /my_robot/joint_states.
Trust and Data Boundaries
The plugin resides in the Gazebo address space but communicates with the rest of the ROS 2 graph solely over DDS. The only data that cross the boundary are:
- Controller command messages (twist, effort, position, etc.) arriving on ROS 2 topics.
- Joint state messages (position, velocity, effort) published by the plugin.
Gazebo’s physics engine, collision detection, and rendering remain isolated from arbitrary ROS 2 code, preserving the simulation’s integrity.
Operational Checks
After launching the simulation, verify the following:
- Gazebo console shows a line similar to
[Info] [plugin.hh:123] Loaded gazebo_ros2_control plugin. - Run
ros2 controller list(requires the controller manager to be active) and see the expected controllers listed asactive. - Check the joint state topic rate:
ros2 topic hz /my_robot/joint_statesshould report approximately the configured update rate (default 100 Hz). - Send a test velocity command, e.g.,
ros2 topic pub /my_robot/diff_drive_controller/cmd_vel geometry_msgs/msg/Twist '{linear: {x: 0.5}}', and observe the robot model move in the Gazebo view.
Failure Modes
- Plugin load failure – missing library or version mismatch results in no joint state updates; Gazebo logs will contain
Failed to load plugin gazebo_ros2_controland joints remain stationary. - Controller misconfiguration – incorrect joint names or missing gains cause commands to be ignored or joint limits to be exceeded, leading to unstable or jerky motion.
- DDS network issues – high latency or packet loss manifests as delayed joint response or occasional command drops, visible as lag in the Gazebo visualization.
- Namespace collisions – running multiple robots with the same
robotNamespacecreates duplicate topic names, causing the controller manager to show conflicting entries and unpredictable behavior.
Conditions That Would Change the Design
The current architecture would need revision if any of the following occur:
- Gazebo adopts a different middleware for ROS 2 communication (e.g., Zenoh) – the plugin would have to be rewritten to use the new transport while preserving the same command/state interface.
- Real‑time control loops require sub‑millisecond jitter – moving controller execution into Gazebo’s real‑time thread or using an external hard‑real‑time controller would become necessary.
- Security policies prohibit cross‑process DDS – a shared‑memory or intra‑process interface could replace the DDS boundary, keeping the plugin inside Gazebo but exchanging data via memory buffers.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.