A Basic Introduction to ROS

Published At 2015-09-26 12:19:32



A Hello World Tutorial


By Somesh Daga



Overview

This guide aims to introduce a few basic elements of ROS through a "Hello World" example. For those of you that have had some programming experience, you will recognize the "Hello World" example, as it is the custom for most introductory tutorials. While it is important to understand that ROS is not a programming language (programming aspects will be implemented through C++/Python), we will nonetheless try to keep the tradition alive.
This tutorial will walk through setting up your ROS Workspace, Creating and Building ROS packages and using Publisher and Subscriber objects to communicate between ROS nodes through ROS Topics. Note that the content has been taken from the ROS Tutorials sections, for the most part, and reorganized to provide a quick overview of some concepts to give new users an idea about the principles on which ROS operates.

Creating a ROS Workspace

A workspace is basically a directory within which you will be creating ROS packages for your projects. ROS uses a build system (to compile your project files) called Catkin to manage your packages. To set this up perform the following steps (through the terminal, of course):
  1. First, create a directory called catkin_ws (this could actually be called anything) and then create a directory called src within it. We can do this in one step using the following command (the -p option creates all intermediate directories).
    mkdir -p ~/catkin_ws/src
  2. Change directory to the 'src' folder:
    cd ~/catkin_ws/src
  3. Run the following command to initialize the workspace:
    catkin_init_workspace
    This creates a CMakeLists.txt file in the src directory. We will look at these types of files later in the tutorial.
  4. Go back one level up (i.e. the catkin_ws directory) by:
    cd ~/catkin_ws
    Note that you can use cd .. to go up one level without specifying the path of the directory
  5. Now, we 'build' the workspace. Executing the following will create a 'build' and 'devel' folder in the root of your workspace (the catkin_ws directory):
    catkin_make
    This command should always be executed in the root of your workspace.
  6. At this point you will notice a 'setup.bash' script located in your 'devel' directory. The purpose of this script is that by sourcing this script (sourcing is a way to load some variables contained in a file to your current Terminal environment), this tells the terminal where to find your ROS packages. You can source this file by running:
    source devel/setup.bash
    However, this will require you to execute this instruction every time you start a new Terminal session. To automatically do this every time a new Terminal session is started, execute:
    echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc
    What this does is to append the line 'source ~/catkin_ws/devel/setup.bash' to the file .bashrc. The .bashrc file runs all the instructions included within it every time a new Terminal sessions comes up.
  7. If you only executed the second command in the previous step, close the terminal and start another session (for the commands to take effect). Now run:
    echo $ROS_PACKAGE_PATH
    The result of the command should be:
    /home/youruser/catkin_ws/src:/opt/ros/indigo/share:/opt/ros/indigo/stacks
    This tells ROS the locations to search for all ROS packages (the different locations are separated by ':').

Create a ROS Package

A ROS or Catkin package is the directory in which you will be creating a particular application, in our case the "Hello World" application. A ROS package has a general structure that will be described below. All ROS packages will be created under the src folder in your workspace (i.e. ~/catkin_ws/src). After you have created a number of ROS packages, your directory structure should look as follows:

workspace_folder/ -- WORKSPACE
src/ -- SOURCE SPACE
CMakeLists.txt -- 'Toplevel' CMake file, provided by catkin
package_1/
CMakeLists.txt -- CMakeLists.txt file for package_1
package.xml -- Package manifest for package_1
...
package_n/
CMakeLists.txt -- CMakeLists.txt file for package_n
package.xml -- Package manifest for package_n
Each individual ROS package will contain a package.xml and CMakeLists.txt file (more on this below), along with other files (code, configurations etc.) for your application.
Let's get started by performing the following steps:
  1. Change your directory to the src folder:
    cd ~/catkin_ws/src
  2. Next, we are going to use the catkin_create_pkg command to create a ROS package named "hello_world":
    catkin_create_pkg hello_world std_msgs roscpp rospy

    The first argument (parameter) right after the catkin_create_pkg command is the name of the ROS package. The rest of the arguments that follow are used to specify other resources that this package will need (which are located somewhere within the ROS directories). In our case we have 3 dependencies: std_msgs (to publish and subscribe to a particular class of messages included in this resource), roscpp (for code development in C++) and rospy (for code development in Python).

    You will find that the result of the operation was to produce a new directory in your src folder as follows:

    hello_world/
    CMakeLists.txt
    include/ -- Look at CMakeLists.txt
    hello_world/ -- Includes C++ Headers
    package.xml -- Look at package.xml
    src/ -- Contains C++/Python Source Code
  3. We now have to make some changes and/or additions to the package.xml file in the hello_world directory. You can open this file using any text editor. A common one used by beginners is gedit as it comes as part of your Ubuntu install and is pretty similar to notepad. To start editing, execute:
    gedit package.xml

    The top portion of this file contains administrative details about the package (who the author is, their email etc.). It is generally good practice to fill in these details especially when these packages are distributed to others. The lower part of the file specifies the build and run time dependencies which should have been automatically filled in when the package was created using the dependency arguments. If you may have forgotten one before, you can manually add them here and rebuild the package. Look at section 7.1 of the official ROS tutorials for a bit more info.
  4. We should now go ahead and 'build' our package. All you need to know is that it generates a bunch of files in the build/ and devel/ folder of our workspace. At this point, there is no need to get into the specifics of what actually happens but this is an important step that needs to be done after any change or a series of changes have been made to any project(s). Remember the building has to be done in the root of your workspace:
    cd ~/catkin_ws
    catkin_make

Creating a Publisher in C++

These next 2 sections will focus on creating our application by coding in C++. Before we get started with this, we need to understand the concepts of ROS Nodes, Topics, Publishers and Subscribers.

ROS Nodes are independent executables (individual pieces of code running simultaneously yet independently).
ROS Topics can be thought of as a file(s) where certain nodes dump some data, which other nodes can access and use. So it is basically a medium of communication between nodes.
When a node dumps data to a topic, it is known as a publisher to that topic (pretty obvious).
As you can probably tell, a node that takes data from a certain topic is a subscriber.

Note that a node can publish to any number of topics. It is also important to recognize that a node can be a publisher and subscriber to any amount of topics simultaneously. With that let's create our "Hello World" application where we will have a node that publishes the hello message and another node responds to it.

  1. Change directory into the src directory of the hello_world ROS package:
    cd ~/catkin_ws/src/hello_world/src
  2. Let's paste the following C++ code to create our publisher node. First, we have to open up a text editor, let's use gedit again. All C++ code files should have the extension .cpp, and let's call our file hello_pub.cpp:
    gedit hello_pub.cpp

    Now paste the following code and save the file:
    /*
     * Copyright (C) 2008, Morgan Quigley and Willow Garage, Inc.
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions are met:
     *   * Redistributions of source code must retain the above copyright notice,
     *     this list of conditions and the following disclaimer.
     *   * Redistributions in binary form must reproduce the above copyright
     *     notice, this list of conditions and the following disclaimer in the
     *     documentation and/or other materials provided with the distribution.
     *   * Neither the names of Stanford University or Willow Garage, Inc. nor the names of its
     *     contributors may be used to endorse or promote products derived from
     *     this software without specific prior written permission.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     * POSSIBILITY OF SUCH DAMAGE.
     */
    #include "ros/ros.h"
    #include "std_msgs/String.h"
    
    #include <sstream>
    
    /**
     * This tutorial demonstrates simple sending of messages over the ROS system.
     */
    int main(int argc, char **argv)
    {
      /**
       * The ros::init() function needs to see argc and argv so that it can perform
       * any ROS arguments and name remapping that were provided at the command line.
       * For programmatic remappings you can use a different version of init() which takes
       * remappings directly, but for most command-line programs, passing argc and argv is
       * the easiest way to do it.  The third argument to init() is the name of the node.
       *
       * You must call one of the versions of ros::init() before using any other
       * part of the ROS system.
       */
      ros::init(argc, argv, "talker");
    
      /**
       * NodeHandle is the main access point to communications with the ROS system.
       * The first NodeHandle constructed will fully initialize this node, and the last
       * NodeHandle destructed will close down the node.
       */
      ros::NodeHandle n;
    
      /**
       * The advertise() function is how you tell ROS that you want to
       * publish on a given topic name. This invokes a call to the ROS
       * master node, which keeps a registry of who is publishing and who
       * is subscribing. After this advertise() call is made, the master
       * node will notify anyone who is trying to subscribe to this topic name,
       * and they will in turn negotiate a peer-to-peer connection with this
       * node.  advertise() returns a Publisher object which allows you to
       * publish messages on that topic through a call to publish().  Once
       * all copies of the returned Publisher object are destroyed, the topic
       * will be automatically unadvertised.
       *
       * The second parameter to advertise() is the size of the message queue
       * used for publishing messages.  If messages are published more quickly
       * than we can send them, the number here specifies how many messages to
       * buffer up before throwing some away.
       */
      ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
    
      ros::Rate loop_rate(10);
    
      /**
       * A count of how many messages we have sent. This is used to create
       * a unique string for each message.
       */
    
      int count = 0;
      while (ros::ok())
      {
        /**
         * This is a message object. You stuff it with data, and then publish it.
         */
    
        std_msgs::String msg;
    
        std::stringstream ss;
        ss << "hello world " << count;
        msg.data = ss.str();
    
        ROS_INFO("%s", msg.data.c_str());
    
        /**
         * The publish() function is how you send messages. The parameter
         * is the message object. The type of this object must agree with the type
         * given as a template parameter to the advertise<>() call, as was done
         * in the constructor above.
         */
    
        chatter_pub.publish(msg);
        ros::spinOnce();
        loop_rate.sleep();
        ++count;
      }
    
      return 0;
    }
    

    Note that to develop at the very least a conceptual understanding of the above code, you would need to have basic knowledge of C or C++ (or any other programming language should work as well). You need to be familiar with the concept of variables, functions and objects. I think it is a fair assumption that most of you are in the Applied Science department and have some knowledge of C through your first year (or the first few weeks of your first year). Through the first few weeks you should probably be familiar with the first 2 concepts. For those of you not familiar with objects, these are basically user-created data types that are made up of a number of primitive (int, char, boolean etc.) and/or other user-created data types. They can have specialized functions that can only be performed on objects of their own type. This is a very basic level overview of the largely complex field of object-oriented programming, which I will leave upon you to explore. As you go through the code, pay attention to the comments (the parts starting with *) to gain an understanding of the concepts. Refer to section 1.1.2 of the ROS tutorials which provides a breakdown of the code.

What we accomplished though these 2 steps was to create a ROS node named "talker" that will continuously publish the message "hello world" (a string data type) on the ROS topic "chatter".

Creating a Subscriber node

Now, we create the subscriber node which will listen in on the messages being posted to the ROS topic "chatter".
  1. Ensure that you are in the src directory of the hello_world package:
    cd ~/catkin_ws/src/hello_world/src
  2. Let's create the C++ file called hello_sub.cpp with the following code:
    gedit hello_sub.cpp
    Paste:
    /*
     * Copyright (C) 2008, Morgan Quigley and Willow Garage, Inc.
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions are met:
     *   * Redistributions of source code must retain the above copyright notice,
     *     this list of conditions and the following disclaimer.
     *   * Redistributions in binary form must reproduce the above copyright
     *     notice, this list of conditions and the following disclaimer in the
     *     documentation and/or other materials provided with the distribution.
     *   * Neither the names of Stanford University or Willow Garage, Inc. nor the names of its
     *     contributors may be used to endorse or promote products derived from
     *     this software without specific prior written permission.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     * POSSIBILITY OF SUCH DAMAGE.
     */
    
    #include "ros/ros.h"
    #include "std_msgs/String.h"
    
    /**
     * This tutorial demonstrates simple receipt of messages over the ROS system.
     */
    void chatterCallback(const std_msgs::String::ConstPtr& msg)
    {
      ROS_INFO("I heard: [%s]", msg->data.c_str());
    }
    
    int main(int argc, char **argv)
    {
      /**
       * The ros::init() function needs to see argc and argv so that it can perform
       * any ROS arguments and name remapping that were provided at the command line.
       * For programmatic remappings you can use a different version of init() which takes
       * remappings directly, but for most command-line programs, passing argc and argv is
       * the easiest way to do it.  The third argument to init() is the name of the node.
       *
       * You must call one of the versions of ros::init() before using any other
       * part of the ROS system.
       */
      ros::init(argc, argv, "listener");
    
      /**
       * NodeHandle is the main access point to communications with the ROS system.
       * The first NodeHandle constructed will fully initialize this node, and the last
       * NodeHandle destructed will close down the node.
       */
      ros::NodeHandle n;
    
      /**
       * The subscribe() call is how you tell ROS that you want to receive messages
       * on a given topic.  This invokes a call to the ROS
       * master node, which keeps a registry of who is publishing and who
       * is subscribing.  Messages are passed to a callback function, here
       * called chatterCallback.  subscribe() returns a Subscriber object that you
       * must hold on to until you want to unsubscribe.  When all copies of the Subscriber
       * object go out of scope, this callback will automatically be unsubscribed from
       * this topic.
       *
       * The second parameter to the subscribe() function is the size of the message
       * queue.  If messages are arriving faster than they are being processed, this
       * is the number of messages that will be buffered up before beginning to throw
       * away the oldest ones.
       */
      ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
    
      /**
       * ros::spin() will enter a loop, pumping callbacks.  With this version, all
       * callbacks will be called from within this thread (the main one).  ros::spin()
       * will exit when Ctrl-C is pressed, or the node is shutdown by the master.
       */
      ros::spin();
      return 0;
    }
    

    The same recommendations apply here as for the publisher step. To get a breakdown of this code, refer to section 1.2.2 of the ROS tutorials.

What this piece of code effectively does is to listen in on the "chatter" topic and execute the "chatterCallback" function every time it sees a new message posted to the topic. This function then posts the message "I heard hello world" to the ROS ecosystem using the ROS_INFO function (this will also post the message to your terminal when the node is run later).

Configuring Node Information and Building the Nodes

To be able to run your nodes later on, the ROS package needs to know where the code is located to run your node and the name of the node associated with the code. We will relay this information through the CMakeLists.txt file located in the hello_world package. Once we have done that , we can build our node.
  1. Change directory to the top-level of your hello_world package:
    cd ~/catkin_ws/src/hello_world
  2. Open your CMakeLists.txt file for editing:
    gedit CMakeLists.txt
    Paste the following lines to the end of your file:
    include_directories(include ${catkin_INCLUDE_DIRS})
    
    add_executable(talker src/hello_pub.cpp)
    target_link_libraries(talker ${catkin_LIBRARIES})
    add_dependencies(talker hello_world_generate_messages_cpp)
      
    add_executable(listener src/hello_sub.cpp)
    target_link_libraries(listener ${catkin_LIBRARIES})
    add_dependencies(listener hello_world_generate_messages_cpp)
    

    Save the file and close it.
  3. We can now build our package:
    cd ~/catkin_ws
    catkin_make

Running your application

  1. To start running any application on ROS, we always have to execute the following first:
    roscore
    This takes up the whole terminal session (it is continuously on) and we have to open up a new terminal for every node we want to run. You can right click in the terminal window and choose 'Open Tab'. Do this twice as we want to run two nodes.
  2. Switch to one of the new tabs and execute:
    rosrun hello_world talker
    You will see the following being printed out on your screen:
    Now switch to the other new tab and execute: rosrun hello_world listener
    You should see the following (as expected):
    The rosrun command is used to run a node. You have to give it the name of the package and the name of the executable (talker/listener in this example) that you want to run.

Next Steps

This guide has only introduced you to a few very basic but important concepts of ROS. Go through the tutorials on the ROS Tutorials page and through other resources (more in the training section).



Required Reading
Required Exercises


Additional Reading Resources

Existing Implementations

Other Tutorials
  • Writing A Teleop Node http://www.ict.griffith.edu.au/~vlad/teaching/robotics.d/RESOURCES/ROS_Summary_Overview.pdf