Robot Operating System: Getting Started with Simulation in ROS2

Essential Robotics Simulation Terminology

  • Kinematics: Kinematics is a branch of physics that governs how body’s move, in the dimensions of time, location, speed and acceleration, without considering the body’s’ weight or outward forces, such as gravity
  • Kinetics: Another branch of physics that considers how a body’s’ location, speed and acceleration changes when the body has a mass and is affected by forces. (In robotics, this is also called robot dynamics)
  • Odometry: A method with which to estimate a bodies’ location by continuously recording movement data and compute the distance and trajectory the body has moved
  • Inertia: This force is the resistance a moving body has to any other force that would change its direction, speed or acceleration
  • Friction: A force that applies when two bodies in proximity to another move and resist this movement
  • Joint: A joint is a mechanical element that connects two bodies. Joints have different types describing how the connected bodies move.
  • RPY Values: This acronym stands for roll, pitch, yaw that describe the movements of an object in 3D space. The object can move on these three axes according to the following graphic:

Step by Step: How to Create a Simulation World

  • Create a new ROS package and setup the directory structure
  • Create an empty world file
  • Create the launch file
  • Add additional physical properties in the robots URDF model
  • Parametrize the robots URDF model for running with Gazebo or RVIZ.
  • Start the empty world with the launch file

Step 1: Package Creation & Directory Structure

radu_gazebo/
├── config
│ └── rviz.config
├── launch
│ └── launch.py
├── radu_bot
│ └── __init__.py
├── resource
├── scripts
├── test
├── urdf
│ └── core.xacro
└── worlds
│ └── room.world
├── package.xml
├── setup.cfg
├── setup.py
ros2 pkg create  --build-type python radu_gazebomkdir radu_gazebo/launch radu_gazebo/world
mkdir radu_gazebo/launch radu_gazebo/world/room.world
touch radu_gazebo/launch/room.launch

Step 2: Empty World File

<!-- FILE: world/room.world -->
<?xml version='1.0'?>
<sdf version="1.6">
<world name="room">
<include>
<uri>model://sun</uri>
</include>
<include>
<uri>model://ground_plane</uri>
</include>
</world>
</sdf>

Step 3: Launch File

#!/usr/bin/python3
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
package_name = 'radu_bot'
world_file = 'room.world'
def generate_launch_description(): pkg_gazebo_ros = get_package_share_directory('gazebo_ros')
pkg_radu_simulation = get_package_share_directory(package_name)
# launch Gazebo by including its definition
gazebo = IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(pkg_gazebo_ros, 'launch', 'gazebo.launch.py'),
)
)
# load the world file
world_arg = DeclareLaunchArgument(
'world',
default_value=[os.path.join(pkg_radu_simulation, 'worlds', world_file), ''],
description='SDF world file')
return LaunchDescription([
gazebo,
world_arg
])

Step 4: Extend the Robot Model with Gazebo Tags

Physical Simulation Properties

<link name="base_link">
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="0.25" />
<inertia ixx="0.000486" ixy="0.0" ixz="0.0" iyy="0.000486" iyz="0.0" izz="0.000729"/>
</inertial>
<visual>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<cylinder radius="0.09" length="0.09"/>
</geometry>
</visual>
</link>
<gazebo reference="base_link">
<kp>100000.0</kp>
<kd>1.0</kd>
<mu1>10.0</mu1>
<mu2>10.0</mu2>
</gazebo>
<link name="base_link">
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="0.25" />
<inertia ixx="0.000486" ixy="0.0" ixz="0.0" iyy="0.000486" iyz="0.0" izz="0.000729"/>
</inertial>
<visual>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<cylinder radius="0.09" length="0.09"/>
</geometry>
</visual>
<collision>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<cylinder radius="0.09" length="0.09"/>
</geometry>
</collision>
</link>
  • For all non-static, non-continuous joints, set <upper> and <lower> values
  • For all continuous joints, add <effort> and <velocity> limits
<joint name="camera_joint">
<limit upper="0.5" lower="-0.5"/>
</joint>
<joint name="left_wheel_joint">
<limit effort="0.1" velocity="0.005"/>
</joint>

Visual Simulation Properties

<material name="blue">
<color rgba="0 0 0.8 1"/>
</material>
<link name="camera">
<visuals>
<material name="blue">
</visuals>
</link>
<gazebo reference="base_link">
<material>Gazebo/Grey</material>
</gazebo>
<link name="camera">
<mesh filename:"package://radu_bot/model/meshes/camera.dae" />
</link>

Step 5: Parametrize the Robots URDF Model for Running with Gazebo or RViz

  • core - contains the core macros for rendering the links and the joints of the robot
  • rviz - the rviz master file, it defines parameters and imports other files
  • rviz_viusals - defines how the robot is visualized in RViz
  • gazebo - the gazebo core file, as before it defines parameters and imports other files
  • gazebo_visuals - defines how the robot is visualized in gazebo
  • gazebo_physics - additional macros that calculate the <inertial> and <collision> tags of the links
  • gazebo_sensor - adds sensor data
  • gazebo_controll - adds the ros-control plugin and defines macros for rendering the <transmission> tags
  1. Import the other required files
<xacro:include filename="$(find radu_bot)/urdf2/core.xacro"/>
<xacro:include filename="$(find radu_bot)/urdf2/visuals.xacro"/>
<xacro:property name="gazebo_build" value="false" />
<xacro:property name="rviz_build" value="true" />
<xacro:box_link name="base_link" size="0.6 0.3 0.05" color="${torso_color_name}" color_rgb="${torso_color_rgb}" /><xacro:wheel_link name="right_wheel_frontside" />
<xacro:wheel_joint name="base_link_right_wheel_frontside" parent="base_link" child="right_wheel_frontside" xyz="0.2 -0.2 -0.05" />
<xacro:wheel_link name="right_wheel_backside" />
<xacro:wheel_joint name="base_link_right_wheel_backside" parent="base_link" child="right_wheel_backside" xyz="-0.2 -0.2 -0.05" />
<xacro:wheel_link name="left_wheel_frontside" />
<xacro:wheel_joint name="base_link_left_wheel_frontside" parent="base_link" child="left_wheel_frontside" xyz="0.2 0.2 -0.05" />
<xacro:wheel_link name="left_wheel_backside" />
<xacro:wheel_joint name="base_link_left_wheel_backside" parent="base_link" child="left_wheel_backside" xyz="-0.2 0.2 -0.05" />
<xacro:macro name="box_link" params="name size color color_rgb" >
<link name="${name}">
<xacro:if value="${rviz_build}">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box size="${size}"/>
</geometry>
<material name="${color}">
<color rgba="${color_rgb}"/>
</material>
</visual>
</xacro:if>
<xacro:if value="${gazebo_build}">
<pose>0 0 0 0 0 0</pose>
<xacro:box_inertia m="0.6" x="0.7" y="0.4" z="0.2"/>
<collision name="collision_${name}">
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box size="${size}"/>
</geometry>
</collision>
</xacro:if>
</link>
</xacro:macro>

Step 6: Spawning a Robot

#!/usr/bin/python3
import os
import sys
import rclpy
from gazebo_msgs.srv import SpawnEntity
from ament_index_python.packages import get_package_share_directory
package_name = 'radu_bot'def main(args=None):
rclpy.init(args=args)
node = rclpy.create_node('minimal_client')
cli = node.create_client(SpawnEntity, '/spawn_entity')
sdf_file_path = (os.path.join(get_package_share_directory(package_name), 'urdf', 'radu_gazebo_compiled.urdf')),
model = open(sdf_file_path[0], 'r').read()
print("MODEL %s" %model)

req = SpawnEntity.Request(
name = "radu_bot",
xml = model,
robot_namespace = "radu",
reference_frame = "world",
)
while not cli.wait_for_service(timeout_sec=1.0):
node.get_logger().info('service not available, waiting again...')
future = cli.call_async(req)
rclpy.spin_until_future_complete(node, future)
if future.result() is not None:
node.get_logger().info(
'Result ' + str(future.result().success) + " " + future.result().status_message)
else:
node.get_logger().info('Service call failed %r' % (future.exception(),))
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
import xacrodef generate_launch_description():    pkg_radu_simulation = get_package_share_directory(package_name)    robot_description_path =  os.path.join(
pkg_radu_simulation,
"urdf",
"gazebo.xacro",
)

robot_description = {"robot_description": xacro.process_file(robot_description_path).toxml()}

Launching the Robot

$> colcon build --symlink-install --cmake-clean-first --event-handlers console_direct+ --packages-up-to radu_bot
$> ros2 launch radu_bot gazebo.launch.py 
[INFO] [launch]: All log files can be found below /home/devcon/.ros/log/2021-05-30-09-07-30-541933-giga-36879
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [gzserver-1]: process started with pid [36886]
[INFO] [gzclient -2]: process started with pid [36889]
$> ros2 run radu_bot spawn_radu
[INFO] [1622358479.722919377] [minimal_client]: Result True SpawnEntity: Successfully spawned entity [radu_bot]

Conclusion

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Sebastian

Sebastian

IT Project Manager & Developer