Skip to main content

Chapter 8: Gazebo ROS 2 Integration

Overview​

This chapter explores the integration between Gazebo and ROS 2, enabling seamless communication between simulated robots and ROS 2 control systems. You'll learn how to bridge simulation and real-world robotics workflows.

Learning Objectives​

Learning Objectives

By the end of this chapter, you will be able to:

  • Integrate Gazebo with ROS 2 for robot simulation
  • Use gazebo_ros_pkgs for communication bridges
  • Control simulated robots through ROS 2 interfaces
  • Configure physics parameters dynamically from ROS 2

SDF (Simulation Description Format)​

SDF is the native format for describing simulation worlds in Gazebo. It extends URDF capabilities to include simulation-specific features like physics properties, sensors, and plugins. An SDF file typically contains world definitions, model definitions, light sources, and plugins.

Basic SDF structure:

<sdf version="1.7">
<world name="default">
<physics type="ode">
<gravity>0 0 -9.8</gravity>
</physics>

<model name="robot">
<!-- Model definition -->
</model>

<light name="sun" type="directional">
<pose>0 0 10 0 0 0</pose>
</light>
</world>
</sdf>

Gazebo-ROS 2 Integration​

The integration between Gazebo and ROS 2 is facilitated by the gazebo_ros_pkgs package, which provides plugins and tools for seamless communication. Key integration points include message bridges, TF tree integration, and service interfaces.

Message Bridges​

The integration provides automatic bridges between Gazebo topics and ROS 2 topics:

  • /clock synchronization for simulation time
  • Sensor data publishing to ROS 2 topics
  • Actuator command subscription from ROS 2 topics
  • Model state publishing and subscription

TF Tree Integration​

Gazebo automatically publishes transforms for all simulated models, creating a complete TF tree that ROS 2 nodes can use for spatial reasoning.

Service Interfaces​

The integration provides ROS 2 services for:

  • Model spawning and deletion
  • World state management
  • Simulation control (pause, reset, step)
  • Physics parameter adjustment

Code Examples​

ROS 2 Node for Gazebo Interaction​

import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
from gazebo_msgs.srv import SpawnEntity, DeleteEntity
from gazebo_msgs.msg import ModelStates
from std_srvs.srv import Empty
import time

class GazeboControllerNode(Node):
def __init__(self):
super().__init__('gazebo_controller_node')

self.spawn_client = self.create_client(SpawnEntity, '/spawn_entity')
self.delete_client = self.create_client(DeleteEntity, '/delete_entity')
self.pause_client = self.create_client(Empty, '/pause_physics')
self.unpause_client = self.create_client(Empty, '/unpause_physics')
self.reset_client = self.create_client(Empty, '/reset_simulation')

self.model_states_sub = self.create_subscription(
ModelStates,
'/model_states',
self.model_states_callback,
10
)

self.cmd_vel_pub = self.create_publisher(
Twist,
'/cmd_vel',
10
)

while not self.spawn_client.wait_for_service(timeout_sec=1.0):
self.get_logger().info('Waiting for spawn service...')

while not self.delete_client.wait_for_service(timeout_sec=1.0):
self.get_logger().info('Waiting for delete service...')

self.get_logger().info('Gazebo Controller Node initialized')

def spawn_model(self, model_name, model_xml, robot_namespace=''):
"""Spawn a model in Gazebo"""
request = SpawnEntity.Request()
request.name = model_name
request.xml = model_xml
request.robot_namespace = robot_namespace

future = self.spawn_client.call_async(request)
return future

def delete_model(self, model_name):
"""Delete a model from Gazebo"""
request = DeleteEntity.Request()
request.name = model_name

future = self.delete_client.call_async(request)
return future

def pause_simulation(self):
"""Pause the physics simulation"""
future = self.pause_client.call_async(Empty.Request())
return future

def unpause_simulation(self):
"""Unpause the physics simulation"""
future = self.unpause_client.call_async(Empty.Request())
return future

def reset_simulation(self):
"""Reset the entire simulation"""
future = self.reset_client.call_async(Empty.Request())
return future

def model_states_callback(self, msg):
"""Handle model states updates"""
for i, name in enumerate(msg.name):
if name == 'mobile_robot':
position = msg.pose[i].position
velocity = msg.twist[i].linear
self.get_logger().info(
f'Robot {name} position: ({position.x:.2f}, {position.y:.2f}, {position.z:.2f}), '
f'velocity: ({velocity.x:.2f}, {velocity.y:.2f}, {velocity.z:.2f})'
)

def send_velocity_command(self, linear_x=0.0, angular_z=0.0):
"""Send velocity command to simulated robot"""
cmd_msg = Twist()
cmd_msg.linear.x = linear_x
cmd_msg.angular.z = angular_z
self.cmd_vel_pub.publish(cmd_msg)

def main(args=None):
rclpy.init(args=args)
gazebo_controller = GazeboControllerNode()

try:
gazebo_controller.send_velocity_command(linear_x=0.5, angular_z=0.2)
time.sleep(2.0)
gazebo_controller.send_velocity_command()
rclpy.spin_once(gazebo_controller, timeout_sec=1.0)
except KeyboardInterrupt:
pass
finally:
gazebo_controller.destroy_node()
rclpy.shutdown()

if __name__ == '__main__':
main()

Physics Parameter Configuration Node​

import rclpy
from rclpy.node import Node
from std_msgs.msg import Float64
from gazebo_msgs.srv import SetPhysicsProperties, GetPhysicsProperties
from gazebo_msgs.msg import ODEPhysics
from geometry_msgs.msg import Vector3

class PhysicsConfigNode(Node):
def __init__(self):
super().__init__('physics_config_node')

self.set_physics_client = self.create_client(
SetPhysicsProperties,
'/set_physics_properties'
)
self.get_physics_client = self.create_client(
GetPhysicsProperties,
'/get_physics_properties'
)

self.timer = self.create_timer(5.0, self.adjust_physics_properties)

self.get_logger().info('Physics Configuration Node initialized')

def get_current_physics_properties(self):
"""Get current physics properties from Gazebo"""
while not self.get_physics_client.wait_for_service(timeout_sec=1.0):
self.get_logger().info('Waiting for get physics service...')

request = GetPhysicsProperties.Request()
future = self.get_physics_client.call_async(request)

rclpy.spin_until_future_complete(self, future)
return future.result()

def set_physics_properties(self, time_step=0.001, max_update_rate=1000.0,
gravity=(0, 0, -9.8)):
"""Set physics properties in Gazebo"""
while not self.set_physics_client.wait_for_service(timeout_sec=1.0):
self.get_logger().info('Waiting for set physics service...')

request = SetPhysicsProperties.Request()
request.time_step = time_step
request.max_update_rate = max_update_rate

gravity_msg = Vector3()
gravity_msg.x = gravity[0]
gravity_msg.y = gravity[1]
gravity_msg.z = gravity[2]
request.gravity = gravity_msg

request.ode_config.auto_disable_bodies = False
request.ode_config.sor_pgs_precon_iters = 2
request.ode_config.sor_pgs_iters = 50
request.ode_config.sor_pgs_w = 1.3
request.ode_config.contact_surface_layer = 0.001
request.ode_config.contact_max_correcting_vel = 100.0
request.ode_config.cfm = 0.0
request.ode_config.erp = 0.2
request.ode_config.max_contacts = 20

future = self.set_physics_client.call_async(request)
return future

def adjust_physics_properties(self):
"""Adjust physics properties based on simulation requirements"""
current_props = self.get_current_physics_properties()

if current_props:
self.get_logger().info(f'Current time step: {current_props.time_step}')
self.get_logger().info(f'Current max update rate: {current_props.max_update_rate}')
self.get_logger().info(f'Current gravity: {current_props.gravity}')

self.get_logger().info('Physics properties checked/adjusted')

def main(args=None):
rclpy.init(args=args)
physics_config_node = PhysicsConfigNode()

try:
rclpy.spin(physics_config_node)
except KeyboardInterrupt:
pass
finally:
physics_config_node.destroy_node()
rclpy.shutdown()

if __name__ == '__main__':
main()

Advanced Physics Simulation with Custom Contact Detection​

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import LaserScan
from geometry_msgs.msg import Twist
from gazebo_msgs.msg import ContactsState
import math

class AdvancedPhysicsController(Node):
def __init__(self):
super().__init__('advanced_physics_controller')

self.contact_sub = self.create_subscription(
ContactsState,
'/contact_sensor_state',
self.contact_callback,
10
)

self.scan_sub = self.create_subscription(
LaserScan,
'/laser_scan',
self.scan_callback,
10
)

self.cmd_vel_pub = self.create_publisher(
Twist,
'/cmd_vel',
10
)

self.timer = self.create_timer(0.1, self.physics_control_loop)

self.collision_detected = False
self.collision_force = 0.0
self.scan_ranges = []
self.safe_distance = 1.0

self.get_logger().info('Advanced Physics Controller initialized')

def contact_callback(self, msg):
"""Handle contact sensor messages"""
if len(msg.states) > 0:
self.collision_detected = True
if len(msg.states[0].wrenches) > 0:
force = msg.states[0].wrenches[0].force
self.collision_force = math.sqrt(
force.x**2 + force.y**2 + force.z**2
)
self.get_logger().info(f'Collision detected with force: {self.collision_force:.2f}')
else:
self.collision_detected = False
self.collision_force = 0.0

def scan_callback(self, msg):
"""Handle laser scan messages"""
self.scan_ranges = msg.ranges

def physics_control_loop(self):
"""Main physics-based control loop"""
cmd_msg = Twist()

if self.scan_ranges:
front_scan = self.scan_ranges[len(self.scan_ranges)//2]
if not math.isinf(front_scan) and front_scan < self.safe_distance:
cmd_msg.linear.x = 0.2 * (front_scan / self.safe_distance)
cmd_msg.angular.z = 0.0
else:
cmd_msg.linear.x = 0.5
cmd_msg.angular.z = 0.0
else:
cmd_msg.linear.x = 0.3
cmd_msg.angular.z = 0.0

if self.collision_detected:
cmd_msg.linear.x = -0.3
cmd_msg.angular.z = 0.5
self.get_logger().info('Collision response: reversing and turning')

self.cmd_vel_pub.publish(cmd_msg)

def main(args=None):
rclpy.init(args=args)
physics_controller = AdvancedPhysicsController()

try:
rclpy.spin(physics_controller)
except KeyboardInterrupt:
pass
finally:
physics_controller.destroy_node()
rclpy.shutdown()

if __name__ == '__main__':
main()

Summary​

Gazebo-ROS 2 integration enables comprehensive robot simulation with seamless communication between simulated environments and ROS 2 control systems. This integration supports model spawning, physics configuration, and sensor data streaming for effective robot development and testing.

Key Takeaways​

Key Takeaways
  • SDF format provides simulation-specific features beyond URDF capabilities
  • gazebo_ros_pkgs bridges Gazebo and ROS 2 communication
  • Dynamic physics configuration enables adaptive simulation environments
  • Contact sensors and collision detection support safety-critical testing

What's Next​

In the next chapter, we'll explore high-fidelity rendering in Unity, learning how to create photorealistic environments for advanced robot perception training.

AI Assistant
How can I help you today?