Simple Service in Python
Introduction
ROS 2 services implement request-response communication between nodes. A client sends a request, a server processes it, and the server returns one response. Services are useful for operations that should be performed on demand rather than continuously.
This exercise creates a Python service server and an asynchronous client for
the example_interfaces/srv/AddTwoInts service. The request contains two
integers:
int64 a
int64 b
---
int64 sum
The fields above --- belong to the request. The field below it belongs to
the response.
1. Create the package
Source ROS 2 and create a workspace if one does not already exist:
source /opt/ros/$ROS_DISTRO/setup.bash
mkdir -p ~/dev_ws/src
cd ~/dev_ws/src
Create an ament_python package:
ros2 pkg create --build-type ament_python --license Apache-2.0 \
py_srvcli --dependencies rclpy example_interfaces
The --dependencies option adds rclpy and example_interfaces to
package.xml.
The generated package has this structure:
py_srvcli/
├── package.xml
├── py_srvcli/
│ └── __init__.py
├── resource/
│ └── py_srvcli
├── setup.cfg
└── setup.py
2. Write the server
Create service.py in the Python module directory:
cd ~/dev_ws/src/py_srvcli/py_srvcli
touch service.py
Add the following code:
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node
class MinimalService(Node):
def __init__(self):
super().__init__('minimal_service')
self.srv = self.create_service(
AddTwoInts,
'add_two_ints',
self.add_two_ints_callback
)
def add_two_ints_callback(self, request, response):
response.sum = request.a + request.b
self.get_logger().info(
f'Incoming request: {request.a} + {request.b}'
)
return response
def main():
rclpy.init()
minimal_service = MinimalService()
rclpy.spin(minimal_service)
minimal_service.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
2.1 Create the service
self.srv = self.create_service(
AddTwoInts,
'add_two_ints',
self.add_two_ints_callback
)
create_service() specifies:
the service type:
AddTwoIntsthe service name:
add_two_intsthe callback that handles incoming requests
The server stores the returned service object in self.srv so that it remains
available for the lifetime of the node.
2.2 Process the request
def add_two_ints_callback(self, request, response):
response.sum = request.a + request.b
self.get_logger().info(
f'Incoming request: {request.a} + {request.b}'
)
return response
The callback receives an AddTwoInts.Request and an empty
AddTwoInts.Response. It adds request.a and request.b, stores the
result in response.sum, and returns the completed response to the client.
2.3 Spin the server
rclpy.spin(minimal_service)
The server must keep spinning so that its executor can receive requests and
invoke add_two_ints_callback. Stop it with Ctrl+C.
3. Write the client
Create client.py in the Python module directory:
cd ~/dev_ws/src/py_srvcli/py_srvcli
touch client.py
Add the following code:
import sys
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node
class MinimalClientAsync(Node):
def __init__(self):
super().__init__('minimal_client_async')
self.cli = self.create_client(AddTwoInts, 'add_two_ints')
while not self.cli.wait_for_service(timeout_sec=1.0):
self.get_logger().info(
'service not available, waiting again...'
)
def send_request(self, a, b):
req = AddTwoInts.Request()
req.a = a
req.b = b
future = self.cli.call_async(req)
future.add_done_callback(self.response_callback)
def response_callback(self, future):
try:
response = future.result()
self.get_logger().info(
f'Result of add_two_ints: {response.sum}'
)
except Exception as e:
self.get_logger().error(
f'Service call failed: {e}'
)
def main():
rclpy.init()
minimal_client = MinimalClientAsync()
a = int(sys.argv[1])
b = int(sys.argv[2])
minimal_client.send_request(a, b)
rclpy.spin(minimal_client)
minimal_client.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
4. Understand the client
4.1 Create the node and client
class MinimalClientAsync(Node):
def __init__(self):
super().__init__('minimal_client_async')
self.cli = self.create_client(AddTwoInts, 'add_two_ints')
MinimalClientAsync inherits from Node. The call to create_client
specifies:
the service type:
AddTwoIntsthe service name:
add_two_ints
Both must match the server.
4.2 Wait for the server
while not self.cli.wait_for_service(timeout_sec=1.0):
self.get_logger().info(
'service not available, waiting again...'
)
The client checks once per second until a matching service is available. This prevents it from sending a request before the server is ready.
4.3 Send an asynchronous request
req = AddTwoInts.Request()
req.a = a
req.b = b
future = self.cli.call_async(req)
future.add_done_callback(self.response_callback)
call_async() returns immediately with a future representing the pending
result. Registering response_callback tells ROS 2 what to execute when that
future finishes.
4.4 Process the response
def response_callback(self, future):
try:
response = future.result()
self.get_logger().info(
f'Result of add_two_ints: {response.sum}'
)
except Exception as e:
self.get_logger().error(
f'Service call failed: {e}'
)
future.result() returns the response or raises an exception if the request
failed.
4.5 Spin the node
rclpy.spin(minimal_client)
Spinning lets the executor process the response callback. This client continues
spinning after it prints the result; stop it with Ctrl+C.
The program expects exactly two integer command-line arguments. Running it without both arguments, or with non-integer values, causes an error.
5. Register the executables
Open ~/dev_ws/src/py_srvcli/setup.py and add entry points for both nodes:
entry_points={
'console_scripts': [
'service = py_srvcli.service:main',
'client = py_srvcli.client:main',
],
},
The name before = is the executable passed to ros2 run. The value after
= identifies the Python module and its main function.
6. Build the package
From the workspace root, install dependencies and build:
cd ~/dev_ws
rosdep install --from-paths src --ignore-src --rosdistro $ROS_DISTRO -y
colcon build --symlink-install --packages-select py_srvcli
source install/setup.bash
7. Run the server and client
In the first terminal, source ROS 2 and the workspace, then start the server:
source /opt/ros/$ROS_DISTRO/setup.bash
cd ~/dev_ws
source install/setup.bash
ros2 run py_srvcli service
In a second terminal, source ROS 2 and the workspace:
source /opt/ros/$ROS_DISTRO/setup.bash
cd ~/dev_ws
source install/setup.bash
Run the client with two integers:
ros2 run py_srvcli client 2 3
The client prints:
[INFO] [minimal_client_async]: Result of add_two_ints: 5
Press Ctrl+C to stop the client after receiving the response. Use
Ctrl+C in the first terminal to stop the server.
8. Inspect the service
With the server running, inspect the available service and its interface:
ros2 service list
ros2 service type /add_two_ints
ros2 interface show example_interfaces/srv/AddTwoInts
The service can also be tested directly from the command line:
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts \
"{a: 2, b: 3}"
The response contains sum: 5.