Unit 2: Adapt URDF For Gazebo Simulator: 0. What You Need To Appear in Gazebo
Unit 2: Adapt URDF For Gazebo Simulator: 0. What You Need To Appear in Gazebo
URDF files are very useful, as you have seen, for having a virtual representation of the different links
and joints.
But you are not simulating its weight, its inertia, what sensors it has, how it collides with other
objects, the friction with the floor, or how the servo position control will react to the robot. These are
just some of the topics that you are going to learn about in this unit.
roscd my_mira_description
touch urdf/mira_simple_collisions_inertias.urdf
We have to add the collisions, innertias and gazebo physical properties to make it appear in the
simulated world.
1.1 Innertias
Here is an example of how the inertias would be added to the base_link:
In [ ]:
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="0.18" />
<inertia ixx="0.0002835" ixy="0.0" ixz="0.0" iyy="0.0002835" iyz="0.0" izz="0.000324"/>
</inertial>
The Inertial tag has:
origin: As you already know, this will move the inertia element based on the axis frame of
the base_link. For example, it's used for lowering the center of mass of the link element, if
needed.
mass: It's very important that the weight of this link element in Kg is coherent.
The reason is that this will condition the dynamics and the power of the actuators used
afterwards to move the joints. But because it is not always possible to get the weight of each
part of a robot, it at least has to be proportional to the total weight, which is much easier to
get.
inertia: As it states, this is the inertia matrix of the link. It will condition how the link behaves
when spinning and moving around.
There could be a whole course on inertia calculations. But for you, as the user, you will
always assume that the material the link is made of is homogeneous, and the matrix is
diagonal. This means that you will only have values in the ixx, iyy, and izz. They will always
be calculated based on primary geometric shapes.
This link has the basic inertia moments :
https://en.wikipedia.org/wiki/List_of_moments_of_inertia
Although this way of calculating the Inertial tag data might seem poor, bear in mind that by
approximating inertias to basic shapes, the results are quite close to reality. And if you really need a
perfect simulation, you will have to dedicate time to obtaining the exact data from the real model.
Here you have a simple python script created to calculate the three basic inertia moments based on
the mass and different variables.
In [ ]:
roscd my_mira_description
mkdir scripts
touch scripts/inertia_calculator.py
chmod +x scripts/inertia_calculator.py
inertia_calculator.py
In [ ]:
#!/usr/bin/env python
import math
class InertialCalculator(object):
def __init__(self):
print "InertialCalculator Initialised..."
def start_ask_loop(self):
selection = "START"
if __name__ == "__main__":
inertial_object = InertialCalculator()
inertial_object.start_ask_loop()
END inertia_calculator.py
This will speed up the process of obtaining the inertias of all the links you work on in this course.
To calculate the innertias of the base_link cylinder execute the inertial_calculator.py input the
cylinders values and mass:
In [ ]:
1.3 Collisions
One of the most crucial elements in being able to simulate a robot is how it interacts with the world
around it.
At the moment, your URDF Mira Robot would be a Ghost in a simulation. There is a visual
representation, but it can't interact with anything. It will go through the floor and the objects.
So, the first thing you have to do is add collisions to your URDF model. Here you have an example
for the base_link:
In [ ]:
<link name="base_link">
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="0.18" />
<inertia ixx="0.0002835" ixy="0.0" ixz="0.0" iyy="0.0002835" iyz="0.0" izz="0.000324"/>
</inertial>
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<cylinder radius="0.06" length="0.09"/>
</geometry>
</collision>
<visual>
<origin rpy="0.0 0 0" xyz="0 0 0"/>
<geometry>
<cylinder radius="0.06" length="0.09"/>
</geometry>
</visual>
</link>
As you can see, the only difference is that there is a new tag called collision that specifies the
collision geometry. This is the shape used for calculating the physical contacts.
As you might have guessed, you can also use 3D meshes here, like this:
In [ ]:
<link name="base_link">
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="0.18" />
<inertia ixx="0.0002835" ixy="0.0" ixz="0.0" iyy="0.0002835" iyz="0.0" izz="0.000324"/>
</inertial>
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<mesh filename="package://my_mira_description/models/mira/meshes/mira_body_v3.dae"/>
</geometry>
</collision>
<visual>
<origin rpy="0.0 0 0" xyz="0 0 0"/>
<geometry>
<mesh filename="package://my_mira_description/models/mira/meshes/mira_body_v3.dae"/>
</geometry>
</visual>
</link>
This is not advised, as the physics calculations are more intensive as the mesh gets more complex.
That's why the collisions are normally basic geometric shapes, while the visuals are meshes.
Another alternative if the geometry of the contact is crucial, is to use a lower poly version of the
virtual mesh. That way, the shape is maintained, but less calculation power is needed.
In [ ]:
<link name="base_link">
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="0.18" />
<inertia ixx="0.0002835" ixy="0.0" ixz="0.0" iyy="0.0002835" iyz="0.0" izz="0.000324"/>
</inertial>
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<mesh filename="package://mira_description/models/mira/meshes/mira_body_v3_lowpolygons.dae"/>
</geometry>
</collision>
<visual>
<origin rpy="0.0 0 0" xyz="0 0 0"/>
<geometry>
<mesh filename="package://mira_description/models/mira/meshes/mira_body_v3.dae"/>
</geometry>
</visual>
</link>
<material name="grey">
<color rgba="0.75 0.75 0.75 1"/>
</material>
<link name="base_link">
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="0.18" />
<inertia ixx="0.0002835" ixy="0.0" ixz="0.0" iyy="0.0002835" iyz="0.0" izz="0.000324"/>
</inertial>
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<cylinder radius="0.06" length="0.09"/>
</geometry>
</collision>
<visual>
<origin rpy="0.0 0 0" xyz="0 0 0"/>
<geometry>
<cylinder radius="0.06" length="0.09"/>
</geometry>
<material name="grey"/>
</visual>
</link>
This is defined before as you can see. We define all the colours we might need in the visuals tags:
In [ ]:
<material name="grey">
<color rgba="0.75 0.75 0.75 1"/>
</material>
...
<material name="grey"/>
Exercise U2-0
Add the necessary elements for the roll_M1_link
Note that the roll_M1_link doesnt have to collide with anything because its an internal
elemnt, so sollisions **wont be needed.
Note also that we need it to have inertias for the physiscs calculations, but it has to be
negligible, therefore use a mas of 0.001 Kg.
END Exercise U2-0
Solution Exercise U2.0
Please try to do it by yourself, unless you get stuck or need some inspiration. You will learn much
more if you fight for each exercise.
Follow this link to open the solutions notebook for Adapt URDF for Gazebo
Simulator: solutions_Course_urdfROS_Unit_2
roscd my_mira_description
touch launch/spawn_urdf.launch
In [ ]:
<launch>
<!-- This Version was created due to some errors seen in the V1 that crashed GAzebo or went too slow in spawn -->
<!-- Load the URDF into the ROS Parameter Server -->
<param name="robot_description" command="cat $(arg urdf_robot_file)" />
<!-- Run a python script to the send a service call to gazebo_ros to spawn a URDF robot -->
<node name="urdf_spawner" pkg="gazebo_ros" type="spawn_model" respawn="false" output="screen"
args="-urdf -x $(arg x) -y $(arg y) -z $(arg z) -model $(arg robot_name) -param robot_description"/>
</launch>
This first launch spawns the given URDF file into the given point in space, if a gazebo simulation is
running.
You can call this first launch through a second launch, passing the necessary arguments to it:
This second one's name is spawn_mira.launch:
roscd my_mira_description
touch launch/spawn_mira_simple_collisions_inertias.launch
In [ ]:
In [ ]:
In [ ]:
roscd my_mira_description
touch scripts/delete_mira.py
chmod +x scripts/delete_mira.py
In [ ]:
#! /usr/bin/env python
import rospy
from gazebo_msgs.srv import DeleteModel, DeleteModelRequest # Import the service message used by the service
/gazebo/delete_model
import sys
In [ ]:
<gazebo reference="LINK_NAME">
<kp>100000.0</kp>
<kd>100000.0</kd>
<mu1>10.0</mu1>
<mu2>10.0</mu2>
<material>Gazebo/COLOUR</material>
</gazebo>
Choose the colours you like and the other properties set them the same as the base_link to avoid
any problems. Actually the only link that touches anything is the base link, the other don't touch the
ground or interact in anyway with anything.
Here you have a list of all the Gazebo
Materials available: https://bitbucket.org/osrf/gazebo/src/default/media/materials/scripts/gazebo.mat
erial
Visual Properties: Choose the colours that will apear in RVIZ. I recommend choosoing the
same as the materials defined in the Gazbeo Physical properties material.
Finally, spawn your URDF and see what happens.
END Exercise U2-1
Please try to do it by yourself, unless you get stuck or need some inspiration. You will learn much
more if you fight for each exercise.
Follow this link to open the solutions notebook for Adapt URDF for Gazebo
Simulator: solutions_Course_urdfROS_Unit_2
When executing the following command you should get something like this in Gazebo Simulator
window:
Execute in WebShell #1
In [ ]:
roslaunch my_mira_description spawn_mira_geometric_collisions_inertias.launch
Check also the URDF visualization in RVIZ
Execute in WebShell #1
2. Add 3D meshes:
Now to add the meshes instead of the geometric shapes you will have to edit only the visual tags.
The collisions will be used the geometric shapes. This is a common practice because it reduces
significantly the physics calculations vs using meshes.
THIS is VERY IMPORTANT:
Gazebo needs the 3D models to be inside a certain folder to find them. Depends on the
system, but RobotIgniteAcademy, the package with the meshes has to be inside:
/usr/share/gazebo/models/
So you have to always clean and copy your files inside that folder if the meshes have been changed:
Execute in WebShell #1
rm -rf /usr/share/gazebo/models/my_mira_description
cp -r /home/user/catkin_ws/src/my_mira_description /usr/share/gazebo/models/
ALWAYS WHEN YOU SIGN IN AGAIN IN ROBOT IGNITE ACADEMY, you will have to copy this
folder AGAIN. (this is a limitation of our learning platform, not necessary on any other ROS system)
Exercise U2-2
Create a new spawn launch file and a urdf file called:
mira_no_controllers.urdf
spawn_mira_no_controllers.launch
Add the meshes for:
Cromulon (Rick & Morty) by unbrandedlx is licensed under the Creative Commons - Attribution
license. Extracted from here: https://www.thingiverse.com/thing:1587203
rm -rf /usr/share/gazebo/models/my_mira_description
cp -r /home/user/catkin_ws/src/my_mira_description /usr/share/gazebo/models/
ALWAYS WHEN YOU SIGN IN AGAIN IN RIOBOTIGNTE ACADEMY, you will have to copy this
folder AGAIN.
END Exercise U2-2-EXTRA
Solution Exercise U2.2
Please try to do it by yourself, unless you get stuck or need some inspiration. You will learn much
more if you fight for each exercise.
Follow this link to open the solutions notebook for Adapt URDF for Gazebo
Simulator: solutions_Course_urdfROS_Unit_2
If you used the provided cromulon.dae you should see something like this:
roscd my_mira_description
touch urdf/mira_with_onecontroller.urdf
roscd my_mira_description
touch launch/spawn_mira_with_onecontroller.launch
spawn_mira_with_onecontroller.launch
<?xml version="1.0" encoding="UTF-8"?>
<launch>
<include file="$(find my_mira_description)/launch/spawn_urdf.launch">
<arg name="x" value="0.0" />
<arg name="y" value="0.0" />
<arg name="z" value="0.2" />
<arg
name="urdf_robot_file"
value="$(find my_mira_description)/urdf/mira_with_onecontroller.urdf"
/>
<arg name="robot_name" value="mira" />
</include>
</launch>
END spawn_mira_with_onecontroller.launch
Step 1: Add a transmission tag linked to the joint you want to move:
In [ ]:
<gazebo>
<plugin name="gazebo_ros_control" filename="libgazebo_ros_control.so">
<robotNamespace>/mira</robotNamespace>
</plugin>
</gazebo>
<transmission name="tran1">
<type>transmission_interface/SimpleTransmission</type>
<joint name="roll_joint">
<hardwareInterface>EffortJointInterface</hardwareInterface>
</joint>
<actuator name="motor1">
<hardwareInterface>EffortJointInterface</hardwareInterface>
<mechanicalReduction>1</mechanicalReduction>
</actuator>
</transmission>
Tags explained:
First, let's have a look at the joint limits:
In [ ]:
<gazebo>
<plugin name="gazebo_ros_control" filename="libgazebo_ros_control.so">
<robotNamespace>/mira</robotNamespace>
</plugin>
</gazebo>
This activates the gazebo control plugin for the Mira namespace. This namespace will be set in the
main spawn launch file.
In [ ]:
<transmission name="tran1">
Here you define the name of the transmission, which will have to be unique through the URDF file.
In [ ]:
<type>transmission_interface/SimpleTransmission</type>
The type of transmission that, for the moment, is the only one implemented:
"transmission_interface/SimpleTransmission"
In [ ]:
<joint name="roll_joint">
<hardwareInterface>EffortJointInterface</hardwareInterface>
</joint>
Here you link the joint to the transmission and select the hardware interface, which also only has an
effort interface working.
In [ ]:
<actuator name="motor1">
<hardwareInterface>EffortJointInterface</hardwareInterface>
<mechanicalReduction>1</mechanicalReduction>
</actuator>
Here you set a unique name for the actuator and again select the hardware interface. As for the
reduction, it's self-explanatory.
All these values are almost always the same, so don't worry too much because, in most cases,
putting these values is more than enough.
Step 2: Create a Control config yaml to set the PID for each
transmission/motor
Create a file called mira_control.yaml inside the config folder of your
package, my_mira_description.
Execute in WebShell #1
roscd my_mira_description
touch config/mira_onecontroller.yaml
In [ ]:
mira:
# Publish all joint states -----------------------------------
joint_state_controller:
type: joint_state_controller/JointStateController
publish_rate: 50
In [ ]:
Step 3: Create the control launch file that gets everything running
Now, it's time to turn on the switch for the controllers and the actuators. For this, you have to create
the following launch file, called mira_control.launch:
Execute in WebShell #1
roscd my_mira_description
touch launch/mira_onejoint_control.launch
mira_onejoint_control.launch
In [ ]:
<launch>
<!-- Load joint controller configurations from YAML file to parameter server -->
<rosparam file="$(find my_mira_description)/config/mira_onecontroller.yaml" command="load"/>
</launch>
END mira_onejoint_control.launch
Be careful with name spaces; they're a source of errors.
If you don't understand what's going on in this launch, please refer to our course on TF_ROS for a
more detailed explanation.
Esentially here we are loading the yaml file and launching the controllers with the robot state
publisher for the TF publication.
In [ ]:
Step 4: Spawn and launch the controllers in their own name space:
The last step is creating a launch that spawns the Mira Robot in its own name space and starts the
controllers.
Spawning in its own name space allows it to be spawned alongside more robots using the
urdf_spawner and other systems.
You have to define the "spawn_with_controllers_mira.launch" that will do the real work.
Execute in WebShell #1
roscd my_mira_description
touch launch/start_mira_withonecontroller.launch
start_mira_withonecontroller.launch
In [ ]:
But now you have the power of moving the roll_joint, so lets have a look.
To test that the controllers are working, you can:
Publish directly in the controllers topics:
Publish through the rqt_gui Topic Publishing.
To publish directly, pick a controller command topic; in this case, there must be only one; and
publish a position inside the limits of that joint (in this case lower="-0.2" upper="0.2"):
Execute in WebShell #2
In [ ]:
Exercise U2-3
Add two more transmissions; one for the pitch_joint and the other for the yaw_joint.
When you have it working by commands, create a python script that moves Mira.
Try to make a simple set of movements and try to mimic how it moves in reality.
Remember that if you find that the movements are too fast or oscillate too much, you can always use
the rqt_reconfigure to tune the PID values.
END Exercise U2-3
Warning
If you have to remove the robot to restart again, due to a gazebo issue, you have to delete the model
by RIGHT-clicking and hitting Delete. Don't forget to also close the control launch.
Solution Exercise U2.3
Please try to do it by yourself, unless you get stuck or need some inspiration. You will learn much
more if you fight for each exercise.
Follow this link to open the solutions notebook for Adapt URDF for Gazebo
Simulator: solutions_Course_urdfROS_Unit_2
Remember to delete the previous mira you spawned:
rosrun my_mira_description delete_mira.py
or
rm -rf /usr/share/gazebo/models/my_mira_description
cp -r /home/user/catkin_ws/src/my_mira_description /usr/share/gazebo/models/
ALWAYS WHEN YOU SIGN IN AGAIN IN RIOBOTIGNTE ACADEMY, you will have to copy this
folder AGAIN.
Execute in WebShell #1
roscd my_mira_description
touch scripts/my_mira_demo.py
chmod +x scripts/my_mira_demo.py
my_mira_demo.py
In [ ]:
#!/usr/bin/env python
import time
import rospy
from math import pi, sin, cos, acos
import random
from std_msgs.msg import Float64
from sensor_msgs.msg import JointState
"""
Topics To Write on:
type: std_msgs/Float64
/mira/pitch_joint_position_controller/command
/mira/roll_joint_position_controller/command
/mira/yaw_joint_position_controller/command
"""
class MiraJointMover(object):
def __init__(self):
rospy.init_node('jointmover_demo', anonymous=True)
rospy.loginfo("Mira JointMover Initialising...")
self.pub_mira_roll_joint_position = rospy.Publisher('/mira/roll_joint_position_controller/command',
Float64,
queue_size=1)
self.pub_mira_pitch_joint_position = rospy.Publisher('/mira/pitch_joint_position_controller/command',
Float64,
queue_size=1)
self.pub_mira_yaw_joint_position = rospy.Publisher('/mira/yaw_joint_position_controller/command',
Float64,
queue_size=1)
joint_states_topic_name = "/mira/joint_states"
rospy.Subscriber(joint_states_topic_name, JointState, self.mira_joints_callback)
mira_joints_data = None
while mira_joints_data is None:
try:
mira_joints_data = rospy.wait_for_message(joint_states_topic_name, JointState, timeout=5)
except:
rospy.logwarn("Time out " + str(joint_states_topic_name))
pass
return similar
return clean_angle
return similar
def mira_movement_sayno(self):
"""
Make Mira say no with the head
:return:
"""
check_rate = 5.0
position = 0.7
rate = rospy.Rate(check_rate)
for repetition in range(2):
similar = False
while not similar:
self.move_mira_yaw_joint(position=position)
# WARNING: THE COMMAND HAS TO BE PUBLISHED UNTIL THE GOAL IS REACHED
# This is because, when publishing a topic, the first time doesn't always work.
similar = self.mira_check_continuous_joint_value(joint_name="yaw_joint", value=position)
rate.sleep()
position *= -1
similar_roll = False
similar_pitch = False
similar_yaw = False
rate = rospy.Rate(check_rate)
while not (similar_roll and similar_pitch and similar_yaw):
self.move_mira_all_joints(position_roll, position_pitch, position_yaw)
similar_roll = self.mira_check_continuous_joint_value(joint_name="roll_joint", value=position_roll)
similar_pitch = self.mira_check_continuous_joint_value(joint_name="pitch_joint", value=position_pitch)
similar_yaw = self.mira_check_continuous_joint_value(joint_name="yaw_joint", value=position_yaw)
rate.sleep()
def mira_lookup(self):
self.mira_movement_look(roll=0.0,pitch=0.3,yaw=1.57)
def mira_lookdown(self):
self.mira_movement_look(roll=0.0,pitch=0.0,yaw=1.57)
def mira_moverandomly(self):
roll = random.uniform(-0.15, 0.15)
pitch = random.uniform(0.0, 0.3)
yaw = random.uniform(0.0, 2*pi)
self.mira_movement_look(roll, pitch, yaw)
def movement_random_loop(self):
"""
Executed movements in a random way
:return:
"""
rospy.loginfo("Start Moving Mira...")
while not rospy.is_shutdown():
self.mira_moverandomly()
self.mira_movement_laugh()
if __name__ == "__main__":
mira_jointmover_object = MiraJointMover()
mira_jointmover_object.movement_random_loop()
END my_mira_demo.py
Execute in WebShell #2
4. Adding Sensors
The last step is to add sensors to the robot. In this case, you have to add a camera where the
"camera_link" is.
But thanks to the gazebo plugin system, this is a plug and play operation. You just have to add the
following tag to the end of the URDF file:
In [ ]:
<!-- camera -->
<gazebo reference="camera_link">
<sensor type="camera" name="camera1">
<update_rate>15.0</update_rate>
<camera name="head">
<pose>0 0 0 0 0 1.57</pose>
<horizontal_fov>1.3962634</horizontal_fov>
<image>
<width>400</width>
<height>400</height>
<format>R8G8B8</format>
</image>
<clip>
<near>0.01</near>
<far>100</far>
</clip>
<noise>
<type>gaussian</type>
<stddev>0.007</stddev>
</noise>
</camera>
<plugin name="camera_controller" filename="libgazebo_ros_camera.so">
<alwaysOn>true</alwaysOn>
<updateRate>0.0</updateRate>
<cameraName>mira/camera1</cameraName>
<imageTopicName>image_raw</imageTopicName>
<cameraInfoTopicName>camera_info</cameraInfoTopicName>
<frameName>camera_link</frameName>
<hackBaseline>0.07</hackBaseline>
<distortionK1>0.0</distortionK1>
<distortionK2>0.0</distortionK2>
<distortionK3>0.0</distortionK3>
<distortionT1>0.0</distortionT1>
<distortionT2>0.0</distortionT2>
</plugin>
</sensor>
</gazebo>
As you can see, there are many variables that can be changed to adapt the camera to the real
specifications. But the most important variable to set is the reference="camera_link," which will link
the camera to the camera_link position. The pose tag is also important. This allows it to point the
camera where it should go and not based on the camera_link axis. In this case, you can see that it's
turned 90º to point in the correct direction.
Exercise U2-4
Add the camera sensor into a final version of URDF called mira.urdf.
Also create a spawn_mira.launch and start_mira.launch to spawn this mira.urdf and start
the mira_control.launch.
END Exercise U2-4
Solution Exercise U2.4
Please try to do it by yourself, unless you get stuck or need some inspiration. You will learn much
more if you fight for each exercise.
Follow this link to open the solutions notebook for Adapt URDF for Gazebo
Simulator: solutions_Course_urdfROS_Unit_2
Spawn an object to make the camera see something more than an empty world. Lets spawn
a banana and a tennisball:
Execute in WebShell #2
In [ ]:
In [ ]:
In [ ]:
rosrun rqt_image_view rqt_image_view
Then, open GraphicalTools:
Warning
In this case, removing the robot with sensors will crash Gazebo in any system. This is a known
issue and they are working on solving it. For you as user, when you delete a robot model with
sensors, just restart your Gazebo or, in RobotIgniteCase, just go to Unit 0 and come back again,
restarting all of the systems.
Another solution is executing the command "rosservice call /gazebo/reset_simulation TAB-TAB."
This will kill the Gazebo process that might be a zombie. Once Gazebo is killed, RobotIgniteSystem
will detect it and relaunch a fresh, new Gazebo for you.
Don't forget that you can remove any object of the scene like this:
In [ ]: