I thought this might be helpful for newbies wanting to use ros2 and Gazebo.
Instead of using a service call or generating a .world
file containing the robot, you can create a node that can place robots in gazebo and use them in a launch file. For this example, I’m going to assume you have the turtlebot3 ros2 files installed and setup and have done the simulations section of the guide. To start, go into the src
folder of the ros2 workspace that you created for the turtlebot3 (As an additional assumption, I will assume the workspace is the same name as the one created in the turtlebot3 guide):
~$ cd ~/turtlebot3_ws/src
~$ ros2 pkg create robot_spawner_pkg
Next, we are going to make this a python file only, so we will need to change delete the CMakeLists.txt
file and create a setup.py
and setup.cfg
file containing the following:
from setuptools import setup
PACKAGE_NAME = 'robot_spawner_pkg'
setup(
name=PACKAGE_NAME,
version='1.0.0',
package_dir={'': 'src'}
packages=[PACKAGE_NAME],
install_requires=['setuptools'],
zip_safe=True,
tests_require=['pytest'],
entry_points={
'console_scripts': [
'spawn_turtlebot = robot_spawner_pkg.spawn_turtlebot:main',
],
},
)
[develop]
script-dir=$base/lib/robot_spawner_pkg
[install]
install-scripts=$base/lib/robot_spawner_pkg
Now, we edit the package.xml
file to add the necessary dependencies to run the program:
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>robot_controller_utils</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="your_email@email.com">your_name</maintainer>
<license>TODO: License declaration</license>
<exec_depend>rclpy</exec_depend>
<export>
<build_type>ament_python</build_type>
</export>
</package>
Finally, we create our spawn_turtlebot.py
file. Before we do that, however, we need to look at the current structure of our package.
robot_spawner_pkg/
|
----include/
|
---src/
|
----setup.cfg
----setup.py
----package.xml
because in our setup.py
file we defined the package_dir={'': 'src'}
and the packages=[PACKAGE_NAME]
, we need to make our package structure look like this:
robot_spawner_pkg/
|
----include/
|
---src/
| |
| ----robot_spawner_pkg/
|
----setup.cfg
----setup.py
----package.xml
Within the second robot_spwaner_pkg
folder will we be placing our spawn_turtlebot.py
file!
The Robot Spawner Script
In this node, we will essentially be making the service call within the node like the following:
"""
spawn_turtlebot.py
Script used to spawn a turtlebot in a generic position
"""
import os
import sys
import rclpy
from ament_index_python.packages import get_package_share_directory
from gazebo_msgs.srv import SpawnEntity
def main():
""" Main for spwaning turtlebot node """
# Get input arguments from user
argv = sys.argv[1:]
# Start node
rclpy.init()
node = rclpy.create_node("entity_spawner")
node.get_logger().info(
'Creating Service client to connect to `/spawn_entity`')
client = node.create_client(SpawnEntity, "/spawn_entity")
node.get_logger().info("Connecting to `/spawn_entity` service...")
if not client.service_is_ready():
client.wait_for_service()
node.get_logger().info("...connected!")
# Get path to the turtlebot3 burgerbot
sdf_file_path = os.path.join(
get_package_share_directory("turtlebot3_gazebo"), "models",
"turtlebot3_burger", "model.sdf")
# Set data for request
request = SpawnEntity.Request()
request.name = argv[0]
request.xml = open(sdf_file_path, 'r').read()
request.robot_namespace = argv[1]
request.initial_pose.position.x = float(argv[2])
request.initial_pose.position.y = float(argv[3])
request.initial_pose.position.z = float(argv[4])
node.get_logger().info("Sending service request to `/spawn_entity`")
future = client.call_async(request)
rclpy.spin_until_future_complete(node, future)
if future.result() is not None:
print('response: %r' % future.result())
else:
raise RuntimeError(
'exception while calling service: %r' % future.exception())
node.get_logger().info("Done! Shutting down node.")
node.destroy_node()
rclpy.shutdown()
if __name__ == "__main__":
main()
I want to give credit to Dirk Thomas (can’t seem to find the link to the code…) for this, because it is a modification of his code that inspired me to do this. Now build using colcon
, source the install, and export the path to the turtlebot3 model like this:
~$ cd ~/turtlebot3_ws
~$ colcon build --symlink-install
~$ . install/setup.bash
~$ export GAZEBO_MODEL_PATH=$GAZEBO_MODEL_PATH:~/turlebot3_ws/src/turtlebot3/turtlebot3_simulations/turtlebot3_gazebo/models
To run the node, we first start a new terminal, source the installation, and start gazebo:
~$ cd ~/turtlebot3_ws
~$ . install/setup.bash
~$ export GAZEBO_MODEL_PATH=$GAZEBO_MODEL_PATH:~/turlebot3_ws/src/turtlebot3/turtlebot3_simulations/turtlebot3_gazebo/models
~$ gazebo --verbose -s libgazebo_ros_factory.so
Notice that we are using libgazebo_ros_factory.so
instead of libgazebo_ros_init.so
as used in the turtlebot3 and other tutorials. That is because only libgazebo_ros_factory.so
contains the service call to /spawn_entity
! Finally, back in your original terminal, use the following command to add a robot!
~$ ros2 run robot_spawner_pkg spawn_turtlebot the_robot_name robot_namespace 0.0 0.0 0.1
The first two arguments are the robot’s name in gazebo and a namespace for the robot if you would like to add more than one. Hope that helps!
EDIT: An initial version of this post used the model-1_4.sdf
for the model loaded in gazebo. This version of the sdf file does not work. If you try giving a linear velocity to the model in gazebo, it will results in the wheels rotating off axis…it’s weird. Anyways, the one that works is model.sdf
which I fixed in the spawn_turtlebot
code above.