ROS2 Services

Services in ROS2 are another type of communication between different ROS2 nodes. After you learned how to use Topics, you might wonder, why you would need something else for communication, right?

As explained in the overview, different communication types are used for different purposes. While Topics are mainly used for continuous streams of data, Services will be used for tasks where a constant data stream would be overkill, especially when the data exchange is less frequent. Another use case would be when your program relies on receiving feedback from a sent information, for example when your robot finished a task.

You can find more information about Services in the official ROS2 documentation.

What are Services?

A Service is a type of communication that adopts the idea of a handshake protocol as it is implemented by having a client application that will send a request to the server to perform a task. After the server has finished the task, it will send a message back to the client to notify it of the result. The client will patiently wait for a response before it will continue with other tasks. Especially this last behaviour is one of the major drawbacks of using services as the client is basically unresponsive while waiting for the response. The advantage is that it provides a simple and elegant solution that receives a response rather than broadcasting into the unknown while producing a massive amount of unnecessary data traffic.

If you would like to reproduce the same strategy with Topics, you would need to have two publishers and two subscribers that constantly send the request and the answer to make sure the message has been sent and the result has been received. This would create unnecessary data traffic to obtain the same result.

After all these explanations, let’s have a look at a service message. As already mentioned, there is two-way communication which means the messages have two components. An example is the service message /turtle1/set_pen which is a message for the TurtleSim program seen earlier.

Launch the turtlesim node with:

$ ros2 run turtlesim turtlesim_node

Now, you can have a look at the services that are available with the following command:

$ ros2 service list

The following list will appear:

ros2 service list with the turtlesim application running

We can see there is a service called /turtle1/set_pen. We can have a closer look at this service with the following command:

$ ros2 service type /turtle1/set_pen

This command will return the message type for this service. In this case, the service type is turtlesim/srv/SetPen.

/turtle1/set_pen message type

To get more information about this service message type, use the interface command:

$ ros2 interface show turtlesim/srv/SetPen

This command will show you the service message description.

turtlesim/srv/SetPen interface description

For the /turtle1/set_pen message, this message looks as follows:

uint8 r
uint8 g
uint8 b
uint8 width
uint8 off
---

In general, the service message has a request and a response part, with their respective data types (which can be any type such as int8, uint16, string, bool, …), that are separated by three dashes as seen below:

requestType request
---
responseType response

Note: The request can be composed of several components or it can be empty, like in the SetPen interface. The same holds true for the response. In the above example, the values of the three base colours are given as part of the request while the response is an empty message. This means that there is a response, but it does not contain any data. Pay attention that, even though the response is empty, it needs to be sent as otherwise, the client will keep waiting for a response forever.

The message type of the /turtle1/set_pen message is a /turtlesim/srv/SetPen message, a custom message for this application. There are only a few standard message types for services as they are often very specific for a task. It is fairly common to create a new service message for each service used, unlike topics that usually rely on standard message types.

Using Services in Terminal

In case you only want to get some information about existing Services or you just want to test the function of a service, there is no need to write a ROS2 program to do that. Instead, you can simply use the terminal to do the job.

Starting TurtleSim

If you have no turtlesim node running, you can start it with the following command:

$ ros2 run turtlesim turtlesim_node

The first thing you need to know is, how to find which Services are already available. This allows you to use the Services that already exist.

Finding Information about the Services

As seen above, you can show a list of the existing Services, use the following command:

$ ros2 service list

This command will list all the Services that are already available. The Services available with the turtesim node are the following:

/clear
/kill
/reset
/spawn
/turtle1/set_pen
/turtle1/teleport_absolute
/turtle1/teleport_relative
/turtlesim/describe_parameters
/turtlesim/get_parameter_types
/turtlesim/get_parameters
/turtlesim/list_parameters
/turtlesim/set_parameters
/turtlesim/set_parameters_atomically

Let’s have a closer look at the /spawn service:

$ ros2 service type /spawn

This Service is of the type turtlesim/srv/Spawn. Let’s have a look at its description:

float32 x
float32 y
float32 theta
string name # Optional.  A unique name will be created and returned if this is empty
---
string name

Looks like this Service requires 4 different fields to be called.

Calling the Service

You can call the Service as follows:

$ ros2 service call /spawn turtlesim/srv/Spawn "{x: 2, y: 2, theta: 0, name: 'foxy_turtle'}"

When you execute this command, you will see the following response:

waiting for service to become available...
requester: making request: turtlesim.srv.Spawn_Request(x=2.0, y=2.0, theta=0.0, name='foxy_turtle')

response:
turtlesim.srv.Spawn_Response(name='foxy_turtle')

And you will see that a second turtle has appeared in the turtlesim node:

spawning a new turtle with the spawn service

Moreover, the new turtle has its own set of Services. You can find them with the following command:

$ ros2 service list

The following new Services are available now:

/foxy_turtle/set_pen
/foxy_turtle/teleport_absolute
/foxy_turtle/teleport_relative

These new Services use the name specified in the Service call: /foxy_turtle.

ros2 service list to see new services

In the previous part about Topics, you learned how to Subscribe to Topics and how to Publish them. In this case, though a Service also consists of two parts, you can only use one part in the terminal: the Service Client. The Client sends a request (like you did through the terminal) to a Service Server. The Server performs an algorithm with a return value that is returned back to the Client. As the Server part is usually more complex, it cannot be done in the terminal.

As with the Topics, using the terminal to interact with Services is usually only done for testing or quickly checking if the Services are running.

Using Services in Python

The usual method of using Services is through ROS2 nodes. In the next parts, you will see how to use Services with Python.

As mentioned earlier, there are two parts of the Service: the Client and the Server. These two parts can be split up into two separate Python nodes.

Service Servers

The Server is the part of a Service that is being called and performs an action as a result. When the action is finished, the Server provides a response to the Client that sent the Request.

First, go to the directory containing the Python programs that you made earlier.

cd ~/ros2_ws/src/my_turtlesim/my_turtlesim/

Now, create a new empty file and open it with your text editor.

gedit my_service_server.py

Before you can type the code for running your Service Server node, you want to decide which message type you will use for calling the Service. There are some standard message types that you can use (see here). Let’s say you want to create a service that will allow you to make the turtle move in a circle after the service call. The message will not contain any data except for the information on when to start but you do want to know if the message has been received correctly. Therefore, the Trigger Service message seems ideal as there is no input data and you receive feedback.

Have a look at the Trigger message description:

ros2 interface show std_srvs/srv/Trigger

The Trigger message contains the following information:

---
bool success   # indicate successful run of triggered service
string message # informational, e.g. for error messages

The idea is that the client can send a message to the server to request that the turtle starts moving. As soon as the service call is received the server will reply and make the turtle move in circles.

The code for the Service Server will look as seen here below. Note that the code also contains code to publish to a Topic.

import rclpy
from rclpy.node import Node
from std_srvs.srv import Trigger
from geometry_msgs.msg import Twist


class MyServiceServer(Node):

    def __init__(self):
        super().__init__('my_service_server')
        self.my_service = self.create_service(Trigger, 'draw_circle', self.draw_circle_callback)
        self.publisher_ = self.create_publisher(Twist, 'turtle1/cmd_vel', 10)

    def draw_circle_callback(self, request, response):
        request # request must be specified even if it is not used
        self.get_logger().info('Received request to draw a circle!')
        response.success = True
        response.message = "Starting to draw a circle!"
        timer_period = 0.5  # seconds
        self.timer = self.create_timer(timer_period, self.publish_velocity_callback)
        self.i = 0

        return response

    def publish_velocity_callback(self):
        my_velocity = Twist()
        my_velocity.linear.x = 0.5
        my_velocity.angular.z = 0.5
        self.publisher_.publish(my_velocity)
        self.get_logger().info(f"Publishing velocity: \
            \n\t linear.x: {my_velocity.linear.x}; \
            \n\t linear.z: {my_velocity.linear.x}")
        self.i += 1


def main(args=None):
    rclpy.init(args=args)

    my_service_server = MyServiceServer()

    rclpy.spin(my_service_server)
    rclpy.shutdown()


if __name__ == '__main__':
    main()

In the first part, you only tell ROS2 which modules you import. In this case, you need to import the Service message of the type std_srvs/srv/Trigger and also the geometry_msgs/msg/Twist message for publishing the velocity in a Topic.

import rclpy
from rclpy.node import Node
from std_srvs.srv import Trigger
from geometry_msgs.msg import Twist

Next, you create a class that inherits from the Node class. In its __init__() function, you define the node name, the publisher for the velocity commands and the Service server. The Service takes the message type, the Service name and the callback function as arguments.

class MyServiceServer(Node):

    def __init__(self):
        super().__init__('my_service_server')
        self.my_service = self.create_service(Trigger, 'draw_circle', self.draw_circle_callback)
        self.publisher_ = self.create_publisher(Twist, 'turtle1/cmd_vel', 10)

The moment that a service call is received, it will trigger a callback function in the code. In this case, the function is responsible to make the TurtleSim move in circles. This is done by creating a publisher and then sending messages to the topic /turtle1/cmd_vel. In this implementation, the topic is published repeatedly in a timer callback function. The function finishes by returning the service response. At this point, the turtle starts drawing circles.

    def draw_circle_callback(self, request, response):
        request # request must be specified even if it is not used
        self.get_logger().info('Received request to draw a circle!')
        response.success = True
        response.message = "Starting to draw a circle!"
        timer_period = 0.5  # seconds
        self.timer = self.create_timer(timer_period, self.publish_velocity_callback)
        self.i = 0

        return response

The callback function of the timer simply sets the velocity command to be published and prints the command message to the screen.

    def publish_velocity_callback(self):
        my_velocity = Twist()
        my_velocity.linear.x = 0.5
        my_velocity.angular.z = 0.5
        self.publisher_.publish(my_velocity)
        self.get_logger().info(f"Publishing velocity: \
            \n\t linear.x: {my_velocity.linear.x}; \
            \n\t linear.z: {my_velocity.linear.x}")
        self.i += 1

The main function initialises the ROS2 communication and creates an instance of our MyServiceServer class. Then, it keeps the node alive until CTRL+C is pressed.

def main(args=None):
    rclpy.init(args=args)

    my_service_server = MyServiceServer()

    rclpy.spin(my_service_server)
    rclpy.shutdown()


if __name__ == '__main__':
    main()

Save the code with your text editor and close it. Don’t forget to make your Python program executable:

$ chmod +x my_service_server.py

You need to add the new ROS2 Python script to the setup.py file. Open it with:

$ gedit ~/ros2_ws/src/my_turtlesim/setup.py

Add an entry for your new Python program in the entry_points so it should look as follows:

entry_points={
    'console_scripts': [
        'my_first_node = my_turtlesim.my_first_program:main',
        'my_simple_publisher = my_turtlesim.my_simple_publisher:main',
        'my_simple_subscriber = my_turtlesim.my_simple_subscriber:main',
        'my_service_server = my_turtlesim.my_service_server:main',
    ],
},

Now go to the root directory of your workspace and build the package:

$ cd ~/ros2_ws/
$ colcon build --packages-select my_turtlesim --symlink-install
$ source install/local_setup.bash

The colcon build command should build the package without any issues.

colcon build the my_turtlesim package

If you want to run and test the new service server, you will need to have 3 terminals: one for the turtlesim node, one for the Service Server node and one to call the Service.

In the first terminal, start the turtlesim node:

$ ros2 run turtlesim turtlesim_node

In the second terminal, start the Service Server:

$ ros2 run my_turtlesim my_service_server

In the third terminal, call the Service:

$ ros2 service call /draw_circle std_srvs/srv/Trigger {}

You will see that the turtle will start moving after you called the Service:

turtle making circles after calling the /draw_circle service

After executing the command, you can see how the turtle starts moving in a circle.

Calling the Service from the terminal is useful to test if the Service s working properly. On a real robot, it is more likely that the Service is called by another ROS2 program. This is called a Service Client.

Service Clients

The Client is the part that calls the Service. This means it sends a request to the Server and then waits for a response. One node can consist of several Clients that call different Services.

To start, you can go to the package you already made earlier:

$ cd ~/ros2_ws/src/my_turtlesim/my_turtlesim/

Now you can create a new file called my_service_client.py in which you will write the Python code to create a Service Client node:

$ gedit my_service_client.py

Now, an empty text editor window will pop up where you can type down the following code:

import rclpy
from rclpy.node import Node
from std_srvs.srv import Trigger


class MyServiceClient(Node):

    def __init__(self):
        super().__init__('my_service_client')
        self.my_client = self.create_client(Trigger, 'draw_circle')
        while not self.my_client.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('Waiting for service to become avilable...')
        self.req = Trigger.Request()

    def send_request(self):
        self.future = self.my_client.call_async(self.req)
        rclpy.spin_until_future_complete(self, self.future)
        return self.future.result()


def main(args=None):
    rclpy.init(args=args)

    my_service_client = MyServiceClient()
    response = my_service_client.send_request()
    my_service_client.get_logger().info(
        f'Result for triggering "Draw Circle: \
        \n\tSuccessful: {response.success} \
        \n\tMessage: {response.message}')

    my_service_client.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

What this code does, is first import the rclpy library and the Service message type Trigger from the subfolder srv from the ROS package called std_srvs.

import rclpy
from rclpy.node import Node
from std_srvs.srv import Trigger

The next part creates a class inherited from the Node class. This class, called MyServiceClient initiates the node with the node name my_service_client and creates a Service Client instance using the Service message type and the name of the Service. Next, we create an empty Service request message.

class MyServiceClient(Node):

    def __init__(self):
        super().__init__('my_service_client')
        self.my_client = self.create_client(Trigger, 'draw_circle')
        while not self.my_client.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('Waiting for service to become avilable...')
        self.req = Trigger.Request()

The function send_request() defines a variable called self.future which receives the response from the Service Server. Then, the program spins until the response has been received with the rclpy.spin_until_future_complete() function. In this function, you need to specify which variable contains the received response. The variable name self.future can be chosen freely, but for clarity, it contains the word future to specify it is the response that will be obtained in the near future after making the request. The function ends with returning the received answer.

    def send_request(self):
        self.future = self.my_client.call_async(self.req)
        rclpy.spin_until_future_complete(self, self.future)
        return self.future.result()

The main function initialises the ROS2 communication and then creates an instance of the node class. Then, we added a log entry to display in the terminal that we created a service request for the service /draw_circle. We end the program by destroying the node and shutting down the program.

def main(args=None):
    rclpy.init(args=args)

    my_service_client = MyServiceClient()
    response = my_service_client.send_request()
    my_service_client.get_logger().info(
        f'Result for triggering "Draw Circle: \
        \n\tSuccessful: {response.success} \
        \n\tMessage: {response.message}')

    my_service_client.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

Save the code and then make the file executable with the following command:

$ chmod +x my_service_client.py

Now, add the new node to the setup.py file:

$ gedit ~/ros2_ws/src/my_turtlesim/setup.py

And add a new entry:

entry_points={
    'console_scripts': [
        'my_first_node = my_turtlesim.my_first_program:main',
        'my_simple_publisher = my_turtlesim.my_simple_publisher:main',
        'my_simple_subscriber = my_turtlesim.my_simple_subscriber:main',
        'my_service_server = my_turtlesim.my_service_server:main',
        'my_service_client = my_turtlesim.my_service_client:main',
    ],
},

Save the file and close it. Now go to the root directory of your workspace, build the package and source the workspace:

$ cd ~/ros2_ws/
$ colcon build --packages-select my_turtlesim --symlink-install
$ source install/local_setup.bash

Now, you can test the Service Client. For this, you need 3 terminals: one for the turtlesim node, one for the Service Server node and one to call the Service with your new node.

In the first terminal, start the turtlesim node:

$ ros2 run turtlesim turtlesim_node

In the second terminal, start the Service Server:

$ ros2 run my_turtlesim my_service_server

In the third terminal, run the Service Client:

$ ros2 run my_turtlesim my_service_client

You will see that the turtle will start moving after you run the Service Client node:

run service client

If everything went correctly, the turtle should start moving and a message should appear that the Service call was successful.

Since the program is only spinning until the response has been received, the program will stop automatically. This also means that the program is not responsive until the response has been received. If the server is taking longer or is not responding at all, the client node can be stuck. This could disrupt the behaviour of the robot. Therefore, Services should only be used for tasks where the server will finish quickly such as taking a picture, saving a map or maybe closing or opening a gripper. For tasks that take longer, it is recommended to use ROS2 Actions. Since ROS2 Actions are a bit more advanced, they will not be covered here.

Using Services

Services in ROS are another type of communication between different ROS nodes. After you learned how to use Topics, you might wonder, why you would need something else for communication, right?

As explained in the overview, different communication types are used for different purposes. While Topics are mainly used for continuous streams of data, Services will be used for tasks where a constant data stream would be overkill, especially when the data exchange is less frequent. Another use case would be when your program relies on receiving feedback from a sent information, for example when your robot finished a task.

You can find more information about Services in the official ROS wiki (here and here).

What are Services?

A Service is a type of communication that adopts the idea of a handshake protocol as it is implemented by having a client application that will send a request to the server to perform a task. After the server has finished the task, it will send a message back to the client to notify it of the result. The client will patiently wait for a response before it will continue with other tasks. Especially this last behaviour is one of the major drawbacks of using services as the client is basically unresponsive while waiting for the response. The advantage is that it provides a simple and elegant solution that receives a response rather than broadcasting into the unknown while producing a massive amount of unnecessary data traffic.

If you would like to reproduce the same strategy with Topics, you would need to have two publishers and two subscribers that constantly send the request and the answer to make sure the message has been sent and the result has been received. This would create unnecessary data traffic to obtain the same result.

After all these explanations, let’s have a look at a service message. As already mentioned, there is two-way communication which means the messages have two components. An example is the service message /turtle1/set_pen which is a message for the TurtleSim program seen earlier.

The service message has a request and a response part, with their respective data types (which can be any type such as int8, uint16, string, bool, …), that are separated by three dashes as seen below:

requestType request
---
responseType response

For the /turtle1/set_pen message, this message looks as follows:

uint8 r
uint8 g
uint8 b
uint8 width
uint8 off
---

Note: The request can be composed of several components or it can be empty. The same holds true for the response. In the above example, the values of the three base colours are given as part of the request while the response is an empty message. This means that there is a response, but it does not contain any data. Pay attention that, even though the response is empty, it needs to be sent as otherwise, the client will keep waiting for a response forever.

The message type of the /turtle1/set_pen message is a /turtlesim/SetPen message, a custom message for this application. There are only few standard message types for services as they are often very specific for a task. It is fairly common to create a new service message for each service used, unlike topics that usually rely on standard message types.

Using Services in Terminal

In case you only want to get some information about existing Services or you just want to test the function of a service, there is no need to write a ROS program to do that. Instead, you can simply use the terminal to do the job.

Starting TurtleSim

Before you get started, open a new terminal and start a roscore with the following command:

roscore

Then, open another terminal and start the TurtleSim node with the following command:

rosrun turtlesim turtlesim_node

This should open a turtlesim_node window:

The first thing you need to know is, how to find which Services are already available. This allows you to use the Services that already exist.

Finding Information about the Services

To see a list of the existing Services, use the following command:

rosservice list

This command will list all the Services that are already available. This allows you to see if your Server is running or which Services are ready for use already. (More on Servers later.) Your terminal should provide to you the following list:

If you want to have more information concerning a specific Service, use the following command:

rosservice info /spawn

This command should show you some details about the Service called /spawn.

Before you can do something with that Service, you need to know what kind of message type it is expecting. The message type is in this case is called turtlesim/Spawn. You can find out more about this type with the command:

rossrv show turtlesim/Spawn

This command should display the following result in the terminal:

As you can see, the message type of this Service consists of several components:

float32 x
float32 y
float32 theta
string name
---
string name

As you can see, this Service consists of 4 input parameters and one output parameter.

Calling the Service

Now, that you know the details of the Service /spawn, you can use the Service with the following command:

rosservice call /spawn "x: 2.0
y: 1.0
theta: 0.0
name: 'Robbie'"

Once you press enter, the terminal will give you a response:

And a new turtle will appear in the turtlesim_node window:

The new turtle has appeared on the coordinates that you specified earlier. Note: you don’t need to type the entire command by yourself. Instead, make use of the autocomplete function with the TAB key:

rosservice call /spawn [TAB][TAB]

In the previous part about Topics, you learned how to Subscribe to Topics and how to Publish them. In this case, though a Service also consists of two parts, you can only use one part in the terminal. The part that you just used would be called a Client that sends a request (like you did through the terminal). The other part is the Server which performs an algorithm with a return value that is returned back to the Client. As the Server part is usually more complex, it cannot be done in the terminal.

As with the Topics, using the terminal to interact with Services is usually only done for testing or quickly checking if the Services are running.

Using Services in Python

The usual method of using Services is through ROS nodes. In the next parts, you will see how to use Services with Python.

As mentioned earlier, there are two parts of the Service: the Client and the Server. These two parts can be split up into two separate Python nodes.

Service Clients

Let’s start with the Clients. The Client is the part that calls the Service. This means it sends a request to the Server and then waits for a response. One node can consist of several Clients that call different Services.

To start, you can go to the package you already made earlier:

cd ~/catkin_ws/src/my_turtlesim/scripts

Now you can create a new file called simple_client.py in which you will write the Python code to create a Service Client node:

gedit simple_client.py

Now, an empty text editor window will pop up where you can type down the following code:

#!/usr/bin/env python 

import rospy
from turtlesim.srv import Spawn, SpawnRequest

def spawn_turtle_client(x, y, theta, name):
    rospy.wait_for_service('/spawn')
    try:
        spawn_turtle = rospy.ServiceProxy('/spawn', Spawn)
        server_response = spawn_turtle(x, y, theta, name)
        return server_response.name
    except rospy.ServiceException as e:
        print("Service call failes: %s"%e)

    
if __name__=="__main__":
    rospy.init_node("my_client_node")
    rate = rospy.Rate(10)

    print("Calling Spawn Service!")
    service_response = spawn_turtle_client(2.0, 1.0, 0.0, "Robbie")
    print("Turtle %s has spawned!"%service_response)

    while not rospy.is_shutdown():
        rate.sleep()

Save the code and then make the file executable with the following command:

chmod +x ~/catkin_ws/src/my_turtlesim/scripts/simple_client.py

What this code does, is first importing the rospy library and importing the Service message type Spawn from the subfolder srv from the ROS package called turtlesim.

#!/usr/bin/env python 

import rospy
from turtlesim.srv import Spawn, SpawnRequest

The next part creates a function that first waits until the Service with the name /spawn is available. This prevents the node to run before the Service Server is up and running and therefore prevents an unnecessary error that could occur otherwise. This is considered a good practice.

Next, within the try statement, the function creates a Service Proxy that is defined by providing the name of the Service and the type of the message. This proxy is then used to provide the arguments necessary to the Server. The parameters are defined by the message type of the Spawn message. Then, the response of this Service Request is stored in a variable and returned as output of the function.

def spawn_turtle_client(x, y, theta, name):
    rospy.wait_for_service('/spawn')
    try:
        spawn_turtle = rospy.ServiceProxy('/spawn', Spawn)
        server_response = spawn_turtle(x, y, theta, name)
        return server_response.name
    except rospy.ServiceException as e:
        print("Service call failes: %s"%e)

In this part of the code, a ROS node is being created and a rate for the while loop is defined. For this example, the while loop is not necessary and is only there to prevent the program from closing automatically after the Service Response is being received by the Client. Except from that, this part only calls the function that has been explained earlier and then starts an infinite while loop intil the user stops the program.

if __name__=="__main__":
    rospy.init_node("my_client_node")
    rate = rospy.Rate(10)

    print("Calling Spawn Service!")
    service_response = spawn_turtle_client(2.0, 1.0, 0.0, "Robbie")
    print("Turtle %s has spawned!"%service_response)

    while not rospy.is_shutdown():
        rate.sleep()

In short terms, this Python node does the same as you did earlier with the terminal, but then in a Python script.

Make sure that the roscore and the turtlesim_node are running. You can run the Client program with the following command:

rosrun my_turtlesim simple_client.py

If everything went correctly, a new turtle should appear and a message should be displayed in the terminal window in which you started the program.

You can see that the curser in the terminal is waiting. The program does not finish on its own and you need to press CTRL-c to terminate the program.

Note: You can only use this program once to spawn a new turtle. When you run the program a second time, ROS will complain that a turtle with the name “Robbie” already exists.

Service Servers

After setting up a Service Client, you will want to know how to write a Service Server in Python. The Server is the part of a Service that is being called and performs an action as a result. When the action is finished, the Server provides a response to the Client that sent the Request.

First, go to the directory containing your scripts that you made earlier.

cd ~/catkin_ws/src/my_turtlesim/scripts

Now, create a new empty file and open it with your text editor.

gedit simple_server.py

Before you can type the code for running your Service Server node, you want to decide which message type you will use for calling the Service. There are some standard message types that you can use (see here). Let’s say you want to create a service that will allow you to make the turtle move in a circle after the service call. The message will not contain any data except for the information on when to start but you do want to know if the message has been received correctly. Therefore, the Trigger Service message seems ideal as there is no input data and you receive feedback.

The Trigger message contains the following information:

---
bool success   # indicate successful run of triggered service
string message # informational, e.g. for error messages

The idea is that the client can send a message to the server to request that the turtle starts moving. As soon as the service call is received the server will reply and make the turtle move in circles.

The code for the Service Server will look as seen here below. Note that the code also contains code to publish Topic.

#!/usr/bin/env python

import rospy
from geometry_msgs.msg import Twist
from std_srvs.srv import Trigger, TriggerResponse


def handle_make_circle(req):
    print("Service call received: Starting movement")
    response = TriggerResponse(True, "Operation complete")
    velocity_publisher = rospy.Publisher("/turtle1/cmd_vel", Twist, queue_size=10)
    rate = rospy.Rate(10)
    vel_message = Twist()
    vel_message.linear.x = 0.5
    vel_message.angular.z = 0.5
    countdown = 240
    while not rospy.is_shutdown() and countdown > 0:
        velocity_publisher.publish(vel_message)
        countdown -= 1
        rate.sleep()
    return response


def make_circle_server():
    trigger_server = rospy.Service('make_circles', Trigger, handle_make_circle)
    print("Listening to requests")
    rospy.spin()


if __name__ == "__main__":
    rospy.init_node("turtlesim_circle_server")
    make_circle_server()

In the first part, you only tell ROS which python version you use and which modules you import. In this case, you need to import the service message including the response and also the geometry Twist message for publishing the velocity in a Topic.

#!/usr/bin/env python

import rospy
from geometry_msgs.msg import Twist
from std_srvs.srv import Trigger, TriggerResponse

The moment that a service call is received, it will trigger a function in the code. In this case, the function is responsible to make the TurtleSim move in circles. This is done by creating a publisher and then sending messages to the topic /turtle1/cmd_vel. In this implementation, the topic is published repeatedly in a while-loop until approximately two full circles have been accomplished. The function finished by returning the service response.

def handle_make_circle(req):
    print("Service call received: Starting movement")
    response = TriggerResponse(True, "Operation complete")
    vel_publisher = rospy.Publisher("/turtle1/cmd_vel", Twist, queue_size=10)
    rate = rospy.Rate(10)
    vel_message = Twist()
    vel_message.linear.x = 0.5
    vel_message.angular.z = 0.5
    countdown = 240
    while not rospy.is_shutdown() and countdown > 0:
        vel_publisher.publish(vel_message)
        countdown -= 1
        rate.sleep()
    return response

In the function make_circle_server() the name of the service is defined to be make_circles and the message type is set to Trigger. The last parameter defines which function should be called when a new service call is received. It is important to add the rospy.spin() function as otherwise the server will be closed immediately after creating the server.

def make_circle_server():
    trigger_server = rospy.Service('make_circles', Trigger, handle_make_circle)
    print("Listening to requests")
    rospy.spin()

The last part of the code simply creates the ROS node to communicate with the ROS master. Also, it calls the function that actually creates the service server.

if __name__ == "__main__":
    rospy.init_node("turtlesim_circle_server")
    make_circle_server()

Save the code with your text editor and close it. Don’t forget to make your Python program executable:

chmod +x ~/catkin_ws/src/my_turtlesim/scripts/simple_server.py

Also, make sure that the TurtleSim node is running:

rosrun turtlesim turtlesim_node

The TurtleSim should be visible and it should be standing still.

Now, start the server node with the following command:

rosrun my_turtlesim simple_server.py

You should see that the servier is running and waiting. If it closes immediately, make sure that you added the function rospy.spin() in your code after creating the server.

You can now call the server through the terminal. To make shure it is active, use the command:

rosservice list

This should bring up the following list:

To call the service, you can type the name of the service and then use the autocomplete function by double-tapping the TAB key.

rosservice call /make_circles [TAB][TAB]

This should auto complete the command:

After executing the command, you can see how the TurtleSim starts moving in a circle.

After about two completed circles, the turtle will stop moving and you can see a response in the terminal from which you called the service:

The response from the server is sent after the server has finished the task. You can see that the task has been finished successfully and that a message has been sent as well. This can tell you, or your program, that a task has been accomplished so that the next steps can be done. If the process failed, you might want to repeat the service call or you want to abort the mission and if it is successful, you know that the robot can continue with its task.

You may notice that the client is not able to do anything else than waiting for a response from the server. This is not very efficient if the task takes as long as in this last case where the turtle is moving around in circles. If you want your client to do other things while waiting for the response, you probably want to use an Action Server instead.