Tutorial 5: Communications using services

Objective: This tutorial session is devoted to learn the ROS synchronous request/response remote procedure calls.

Contents

After this session you will be able to:

  • Understand the client mechanism: the client object and the service call.

  • Understand the server mechanism: the server object, the service callback function and how to grant its execution.

  • Use the rqt service type browser tool to look for the standard service types.

  • Define non-standard services: the .srv files and the required commands in the CMakeLists.txt and package.xml files.

  • Write nodes able to call/offer a service.

Provided packages:

This tutorial will use the following packages:

Package agitr_chapter8
Package agitr_chapter8_plus

Prepare the catkin workspace to work with them:

  1. If you have not yet done so, follow the steps in Exercise 0 to:

  • clone in your ~/git-projects folder the meta-repository containing all the required packages,

  • create the catkin workspace folders (use catkin_ws5 for the current tutorial).

  1. Follow the procedure shown in Procedure to prepare the exercises to import in the intro2ros/2023/teamXX/EXERCISE3 subgroup of your GitLab account the package:

  1. In your git-projects folder clone the agitr_chapter8_plus and solution_exercise3 projects of your GitLab account inside an exercise3 subfolder (change XX by your team number). You can use https:

$ git clone https://gitlab.com/intro2ros/2023/teamXX/exercise3/agitr_chapter8_plus.git exercise3/agitr_chapter8_plus
$ git clone https://gitlab.com/intro2ros/2023/teamXX/exercise3/solution_exercise3.git exercise3/solution_exercise3

or ssh:

$ git clone git@gitlab.com:intro2ros/2023/teamXX/exercise3/agitr_chapter8_plus.git exercise3/agitr_chapter8_plus
$ git clone git@gitlab.com:intro2ros/2023/teamXX/exercise3/solution_exercise3.git exercise3/solution_exercise3
  1. In your catkin_ws5/src folder create a symbolic link to the packages to be used:

$ ln -s ~/git-projects/all_introduction_to_ros_packages/agitr_chapter8
$ ln -s ~/git-projects/exercise3/agitr_chapter8_plus
  1. Edit the build.sh file of the solution_exercise3 package to add your credencials (token_user and token_code) and push the change.

References:

This tutorial is partially based on chapter 8 of A gentle introduction ROS by Jason M. O’Kane.

Complementary resources:

Services

ROS service calls communication has the following features:

  • It is bi-directional.

  • It one-to-one.

A client node sends some data (called a request) to a server node and waits for a reply. The server, having received this request, takes some action (computing something, configuring hardware or software, changing its own behavior, etc.) and sends some data (called a response) back to the client.

../_images/clientserver.png

A service data type determines the content of messages by a collection of named fields, and is divided into two parts, the request and the response.

Understanding services by using the command line

Run the turtlesim_node and the turtle_teleop_key executable files (that start the /turtlesim and /teleop_turtle nodes), and use the following command line tools to get insight of the ROS service calls communication.

  • Listing of all services offered by all the active nodes:

    $ rosservice list
    

Nodes usually use their node’s name as namespace, like /turtlesim/get_loggers, which prevents names collisions. Some services, however, give more general capabilities not conceptually tied to any particular node and have a global name, like the service /spawn, that creates a new turtle.

  • Listing services offered by a a given node, e.g. those of node turtlesim:

    $ rosnode info turtlesim
    
  • Finding the node offering a given service, e.g. /spawn:

    $ rosservice node /spawn
    
  • Finding the data type of a service:

    $ rosservice info /spawn
    
  • Inspecting service data types, e.g. turtlesim/Spawn, to know the request message type and the response message type:

    $ rossrv show turtlesim/Spawn
    
  • Calling services from the command line. e.g. the /spawn service offerd by node turtlesim:

    $ rosservice call /spawn 3 3 0 MyTurtle
    

The call has created a new turtle located at (3,3) with an orientation of zero radians and named “MyTurtle”, that comes with its own set of resources that belong to the new namespace MyTurtle (e.g. topics MyTurtle/cmd_vel or service /MyTurtle/teleport_absolute).

The service returns the name of the turtle. Also the information of whether the call has succeded or not is returned, e.g. if the same call is repeated it will return an error because no two turtles can have the same name.

Tip

Tab completion is very useful. For instance, you can type rosservice call /spawn and then press the tab key. You will see all the arguments that this service is waiting for.

Exercise

  • Check the new created topics and services.

  • Check the node the new services belongs to.

  • Verify that no two turtles of the same name can be created.

The package agitr_chapter8

This package contains:

  • The program spawn_turtle.cpp, whose executable file is called spawn_turtle. This program implements a node, also called spawn_turtle, that calls a service, namely the spawn service provided by the turtlesim node.

  • The program pubvel_toggle.cpp, whose executable file is called pubvel_toggle. This program implements a node, also called pubvel_toggle, that publishes velocities to drive the turtle and also offers a service that allows to toggle between translational and rotational motion.

Take a look at the package.xml file:

  • Note that because spawn_turtle uses a service type from the turtlesim package, we must declare a dependency on that package.

  • Note that because pubvel_toggle uses a message type from the geometry_msgs package, we must declare a dependency on that package.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?xml version="1.0"?>
<package  format="2">
 <name>agitr_chapter8</name>
 <version>0.0.1</version>
 <description>
   Examples from A Gentle Introduction to ROS
 </description>
 <maintainer email="jokane@cse.sc.edu">
   Jason O'Kane
 </maintainer>
 <license>TODO</license>
 <buildtool_depend>catkin</buildtool_depend>
 <depend>roscpp</depend>
 <depend>turtlesim</depend>
 <depend>geometry_msgs</depend>
</package>

Take a look at the CMakeLists.txt file:

  • Note that the find_package includes turtlesim.

  • Note that the find_package includes geometry_msgs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# What version of CMake is needed?
cmake_minimum_required(VERSION 2.8.3)

# The name of this package.
project(agitr_chapter8)

# Find the catkin build system, and any other packages on which we depend.
find_package(catkin REQUIRED COMPONENTS
   roscpp
   geometry_msgs
   turtlesim
)

# Declare our catkin package.
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES demopkg
   CATKIN_DEPENDS roscpp
#  DEPENDS system_lib
)

# Specify locations of header files.
include_directories(include ${catkin_INCLUDE_DIRS})

# Declare the executable, along with its source files.
add_executable(pubvel_toggle src/pubvel_toggle.cpp)
add_executable(spawn_turtle src/spawn_turtle.cpp)

# Specify libraries against which to link.
target_link_libraries(pubvel_toggle ${catkin_LIBRARIES})
target_link_libraries(spawn_turtle ${catkin_LIBRARIES})

A client program

The spawn_turtle.cpp program is a client that calls the service /spawn offered by the turtlesim node to create a new turtle called Leo.

  • Kill all nodes and rerun the turtlesim:

$ rosnode kill -a
$ rosrun turtlesim turtlesim_node
  • Compile and execute the spawn_turtle program

$ catkin build agitr_chapter8
$ source devel/setup.bash
$ rosrun agitr_chapter8 spawn_turtle

The code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//This program spawns a new turtlesim turtle by calling
// the appropriate service.
#include <ros/ros.h>
//The srv class for the service.
#include <turtlesim/Spawn.h>

int main(int argc, char **argv){

   ros::init(argc, argv, "spawn_turtle");
   ros::NodeHandle nh;

//Create a client object for the spawn service. This
//needs to know the data type of the service and its name.
   ros::ServiceClient spawnClient
       = nh.serviceClient<turtlesim::Spawn>("spawn");

//Create the request and response objects.
   turtlesim::Spawn::Request req;
   turtlesim::Spawn::Response resp;

   req.x = 2;
   req.y = 3;
   req.theta = M_PI/2;
   req.name = "Leo";

   ros::service::waitForService("spawn", ros::Duration(5));
   bool success = spawnClient.call(req,resp);

   if(success){
       ROS_INFO_STREAM("Spawned a turtle named "
                       << resp.name);
   }else{
       ROS_ERROR_STREAM("Failed to spawn.");
   }
}

Calling the service

  1. The request and response types: Each service is associated with a service type, and the corresponding header file has to be included. In this case:

    #include<turtlesim/Spawn.h>

where turtlesim is the package containing the service type.

  1. The client object: To carry out a service call, an object of class ros::ServiceClient is created:

    ros::ServiceClient client = node_handle.serviceClient<service_type>(service_name);

where:

  • The node_handle is an object of class ros::NodeHandle, nh in the example.

  • The service_type part inside the angle brackets — formally called the template parameter — is the data type for the service we want to call, turtlesim::Spawn in the example.

  • The service_name is a string containing the name of the service we want to call (a relative or global name), the relative name spawn in the example.

  1. Creating the request and calling the service: A request object is created to contain the data to be sent to the server, e.g.:

    package_name::service_type::Request req;
    package_name::service_type::Response resp;

In fact, Request and Response are data members of a strcture, usually called srv, defined in the service type header file.

Once the fields of the request and the response are filled with the data to be sent, the service can actually be called:

bool success=service_client.call(request, response)

or alternatively:

bool success=service_client.call(srv)

Note that before calling the service, the function waitForService() has been inserted to verify that the service is available. This is useful when nodes are run using a launch file.

Exercise

Kill all nodes and rerun them, but starting first with the spawn_turtle and then the turtlesim. Verify that if more than 5 s elapses between these runs, an error is trigged because the service that has to be called is not available.

A server program

The pubvel_toggle.cpp program creates the node /pubvel_toggle that publishes a velocity that is either translational or rotational. A service is offered to toggle between the two options.

  • Compile and execute the pubvel_toggle program.

$ rosrun agitr_chapter8 pubvel_toggle
  • Repeatedly call the service /toggle_forward using the command line in order to change between modes.

$ rosservice call /toggle_forward

Important

Do not forget to type source devel/setup.bash from the terminal you are calling the service.

The code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//this program toggles between rotation and translation
//commands,based on calls to a service.
#include <ros/ros.h>
#include <std_srvs/Empty.h>
#include <geometry_msgs/Twist.h>

bool forward = true;

bool toggleForward(
       std_srvs::Empty::Request &req,
       std_srvs::Empty::Response &resp){
       forward = !forward;
       ROS_INFO_STREAM("Now sending "<<(forward?
               "forward":"rotate")<< " commands.");
       return true;
}

int main(int argc, char **argv){
       ros::init(argc,argv,"pubvel_toggle");
       ros::NodeHandle nh;

       ros::ServiceServer server =
               nh.advertiseService("toggle_forward",&toggleForward);

       ros::Publisher pub=nh.advertise<geometry_msgs::Twist>(
               "turtle1/cmd_vel",1000);

       ros::Rate rate(2);
       while(ros::ok()){
               geometry_msgs::Twist msg;
               msg.linear.x = forward?1.0:0.0;
               msg.angular.z=forward?0.0:1.0;
               pub.publish(msg);
               ros::spinOnce();
               rate.sleep();
       }
}

The service callback

A callback function has to be defined, that is called once per service call received by the node. The Request parameter contains the data passed by the client, and the Response parameter is filled by the function. A boolean must be returned, that indicates either failure or success.

bool function_name(
   package_name::service_type::Request &req,
   package_name::service_type::Response &resp
){ .... }

The server object

A server object is created to advertise the service offered, and to associate it with the callback function:

ros::ServiceServer server = node_handle.advertiseService(service_name, pointer_to_callback_function);

The service_name is usually a relative name, although it could be a global name (no private names are accepted).

The service will be available until the server object is destroyed.

Giving ROS control

As done with the subscriber, we need to use ros::spin() or ros::spinOnce() to let ROS execute the callback.

Standard services

std_srvs contains three service types called Empty, SetBool and Trigger, which are common service patterns for sending a signal to a ROS node.

  • The Empty service does not exchange any actual data between the service and the client.

  • The SetBool service is used to set bolean values.

  • The Trigger service adds the possibility to check if triggering was successful or not.

Exercise

Use the rqt tool to browse the std_srvs types:

$ rqt -s rqt_srv
../_images/rqtsrv.png

Defining non-standard services

To allow a server to offer services with customized arguments and return values, a text file (with extension .srv) is generated with the input and output message types, e.g.:

1
2
3
float64 newrate
---
bool ret

These files are stored in a subfolder of the package which is usually called srv.

Note that message types are lowercase, e.g. float64, while the standard messages that wrap them have the first letter uppercase, e.g. Float64.

To illustrate the definition of non-standard messages, the previous example has been extended by adding a service (called /change_rate) that allows to change the frequency at which the command velocity is published. These changes are coded in the package agitr_chapter8_plus.

The CMakeLists.txt

Take a look at the CMakeLists.txt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# What version of CMake is needed?
cmake_minimum_required(VERSION 2.8.3)

# The name of this package.
project(agitr_chapter8_plus)

# Find the catkin build system, and any other packages on which we depend.
 find_package(catkin REQUIRED COMPONENTS
   roscpp
   geometry_msgs
   turtlesim
   std_msgs
   message_generation
)

## Generate services in the 'srv' folder
add_service_files(
  FILES
  Changerate.srv
)

## Generate added messages and services with any dependencies listed here
generate_messages(
  DEPENDENCIES
  std_msgs
)

# Declare our catkin package.
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES demopkg
   CATKIN_DEPENDS roscpp std_msgs message_runtime
 #  DEPENDS system_lib
)

# Specify locations of header files.
include_directories(include ${catkin_INCLUDE_DIRS})

# Declare the executable, along with its source files.
add_executable(pubvel_toggle src/pubvel_toggle.cpp)
add_executable(pubvel_toggle_plus src/pubvel_toggle_plus.cpp)
add_executable(spawn_turtle src/spawn_turtle.cpp)

# Specify libraries against which to link.
target_link_libraries(pubvel_toggle ${catkin_LIBRARIES})
target_link_libraries(pubvel_toggle_plus ${catkin_LIBRARIES})
add_dependencies(pubvel_toggle_plus ${catkin_EXPORTED_TARGETS} ${${PROJECT_NAME}_EXPORTED_TARGETS})
target_link_libraries(spawn_turtle ${catkin_LIBRARIES})

The following are the relevant changes:

  • In find_package() the dependences on std_msgs and on message_generation have been included.

  • The add_service_files field has been added to include the files which contain the information of the services that have to be generated.

  • The generate_message section has been added:

    generate_messages(
       DEPENDENCIES
        std_msgs
    )
    
  • In catkin_package() the dependences on std_msgs and on message_runtime have been included.

  • The add_dependencies() is added to specify that the specific target will only be built after the indicated dependencies have been built. Therefore, to guarantee that they are build in the correct order you need:

    • To add a dependency on target catkin_EXPORTED_TARGETS if you have a target which depends on some other target that needs messages/services/actions to be built (this case applies almost always).

    • To add a dependency on target ${PROJECT_NAME}_EXPORTED_TARGETS if you have a package which builds messages and/or services (as in the current example) as well as executables that use these.

    add_dependencies(your_program ${catkin_EXPORTED_TARGETS} ${${PROJECT_NAME}_EXPORTED_TARGETS})
    

The package manifest

Take a look at the package.xml file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0"?>
<package  format="2">
 <name>agitr_chapter8_plus</name>
 <version>0.0.1</version>
 <description>
   Examples from A Gentle Introduction to ROS
 </description>
 <maintainer email="jokane@cse.sc.edu">
   Jason O'Kane
 </maintainer>
 <license>TODO</license>

 <buildtool_depend>catkin</buildtool_depend>
 <build_export_depend>message_runtime</build_export_depend>
 <exec_depend>message_runtime</exec_depend>

 <depend>message_generation</depend>
 <depend>std_msgs</depend>
 <depend>roscpp</depend>
 <depend>turtlesim</depend>
 <depend>geometry_msgs</depend>
</package>

The following are the relevant added lines for building services. More detailed info here.

<build_export_depend>message_runtime</build_export_depend>
<exec_depend>message_runtime</exec_depend>
<depend>message_generation</depend>
<depend>std_msgs</depend>

Exercise

  • Take a look at the pubvel_toggle_plus.cpp file.

  • Build the new package agitr_chapter8_plus.

  • Run the nodes turtlesim_node (from the turtlesim package) and pubvel_toggle_plus (from the agitr_chapter8_plus package).

  • Use the new service /change_rate to change the publishing rate to 10 in order to have a better response time when toggling the commanded velocity with the /toggle_forward service.

EXERCISE 3

To solve Exercise 3 you must modify the agitr_chapter8_plus package that you have cloned in your git-projects folder.

Recall to keep commiting the changes when coding the solution (do small commits with good commit messages!) and write a README.md file in the solution_exercise3 package explaining your solution (Markdown Cheat Sheet).

Exercise 3a

Make a copy of pubvel_toggle_plus.cpp and name it improved_pubvel_toggle.cpp. Modify this new file to include:

  • A service to start/stop the turtle.

  • A service that allows to change the speed.

Name the executable file as improved_pubvel_toggle.

Exercise 3b

Make a copy of spawn_turtle.cpp and name it spawn_turtle_plus.cpp. Modify this new file in order that, besides calling the /spawn service to create a new turtle called MyTurtle, this node also:

  • Calls the /toggle_forward service so that the turtles start rotating.

  • Calls the /change_speed service to set the initial velocity to 10.

  • Subscribes to the topic turtle1/cmd_vel (that will be provided by the pubvel_toggle_plus node).

  • Publishes the received command velocity to the topic MyTurtle/cmd_vel in order to move both of the turtles simultaneously.

Name the executable file as spawn_turtle_plus.

Hint: Create the publisher as a global pointer, initialize it in the main function and used it in the subscriber callback function.

Exercise 3c:

Create a launch file named exercise3.launch that runs:

  • the turtlesim and improved_pubvel_toggle nodes

  • and, optionally, also runs the spawn_turtle_plus node of Exercise 3b, by using an argument called addturtle.

SOLUTION EXERCISE 3

Download the zipped solution.