ROS2 Service - Client Server Project
In the previous blog, we saw how to create a publisher subscriber project.
This project makes remakes it into a service based client server project.
For developing a service, a service package needs to be created using ament_cmake build tool.
Go to the src folder in the root of the workspace and create a package called ‘sum_nums_srv’ using ament_cmake.
$ cd ~/ros2_ws/src
$ ros2 pkg create --build-type ament_cmake sum_nums_srv
If we look at the directory structure of the new package ‘sum_nums_srv’, we can see that there is an include and src folder which we don't need. We will add a srv folder to place our service structure file called ‘SumNumsSrv.srv’.
$ cd sum_nums_srv
$ rm -rf include src
$ mkdir srv
$ cd srv
$ touch SumNumsSrv.srv
NOTE: A service structure file is treated as an interface file in ROS2 and hence needs to start with a Capital letter.
Now our package ‘sum_nums_srv’ folder should look like the following structure:
sum_nums_srv/
├── CMakeLists.txt
├── package.xml
└── srv
└── SumNumsSrv.srv
Populate the srv file with the following service request and response structure and save it:
int8 n
---
int64 sum
This srv file informs ROS2 that this service will request an integer n, and responds with an integer called sum.
To convert the service interfaces into language-specific code (like C++ and Python) we should add the following lines to CMakeLists.txt, including any dependencies if any:
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"srv/SumNumsSrv.srv"
)
Add the following lines within the <package> element of package.xml:
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
Since the interfaces rely on rosidl_default_generators for generating language-specific code, we need to declare a build tool dependency on it. rosidl_default_runtime is a runtime or execution-stage dependency, needed for using the interfaces later. The rosidl_interface_packages is the name of the dependency group that our package, ‘sum_nums_srv’, should be associated with, declared using the <member_of_group> tag.
Build the ‘sum_nums_srv’ package (selecting only the new package).
$ source install/setup.bash
$ ros2 interface show sum_nums_srv/srv/SumNumsSrv
Test the new service.
$ source install/setup.bash
$ ros2 interface show sum_nums_srv/srv/SumNumsSrv
This will display the service structure.
Now go back to our ‘sum_nums’ package and use this newly created service package.
Create two files called ‘sum_nums_service_server.py’ and ‘sum_nums_service_client.py’ in the same folder as our publisher subscriber files are.
$ cd src/sum_nums/sum_nums
$ touch sum_nums_service_server.py sum_nums_service_client.py
Populate the sum_nums_service_server.py file with the service server code:
from sum_nums_srv.srv import SumNumsSrv
import rclpy
from rclpy.node import Node
class SumNumsClient(Node):
"""
Node to send requests to compute the sum of first n integer numbers.
"""
def __init__(self):
"""
Initializes the SumNumsClient class.
"""
super().__init__('sum_nums_client_node')
self.cli = self.create_client(SumNumsSrv, 'sum_n_nums')
while not self.cli.wait_for_service(timeout_sec=1.0):
self.get_logger().info(' SumNumsSrv service not available, waiting...')
self.req = SumNumsSrv.Request()
def send_request(self, n):
"""
Sends a request to compute the sum of first n integer numbers.
Parameters:
n: The number of integer numbers to sum.
Returns:
The response containing the computed sum.
"""
self.req.n = n
self.future = self.cli.call_async(self.req)
rclpy.spin_until_future_complete(self, self.future)
return self.future.result()
def main(args=None):
"""
Main function to initialize the node and send a request to compute the sum of first n integer numbers.
"""
rclpy.init(args=args)
sum_nums_client_node = SumNumsClient()
num_input = int(input("Enter a positive integer number: "))
if num_input < 0:
print("Input number is not positive")
else:
print('Entered:', num_input)
response = sum_nums_client_node.send_request(num_input)
print("Sum of first", num_input, "integer numbers is:", response.sum)
# Destroy the node explicitly
sum_nums_client_node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Populate the sum_nums_service_client.py file with the service client code:
from sum_nums_srv.srv import SumNumsSrv
import rclpy
from rclpy.node import Node
class SumNumsClient(Node):
"""
Node to send requests to compute the sum of first n integer numbers.
"""
def __init__(self):
"""
Initializes the SumNumsClient class.
"""
super().__init__('sum_nums_client_node')
self.cli = self.create_client(SumNumsSrv, 'sum_n_nums')
while not self.cli.wait_for_service(timeout_sec=1.0):
self.get_logger().info(' SumNumsSrv service not available, waiting...')
self.req = SumNumsSrv.Request()
def send_request(self, n):
"""
Sends a request to compute the sum of first n integer numbers.
Parameters:
n: The number of integer numbers to sum.
Returns:
The response containing the computed sum.
"""
self.req.n = n
self.future = self.cli.call_async(self.req)
rclpy.spin_until_future_complete(self, self.future)
return self.future.result()
def main(args=None):
"""
Main function to initialize the node and send a request to compute the sum of first n integer numbers.
"""
rclpy.init(args=args)
sum_nums_client_node = SumNumsClient()
num_input = int(input("Enter a positive integer number: "))
if num_input < 0:
print("Input number is not positive")
else:
print('Entered:', num_input)
response = sum_nums_client_node.send_request(num_input)
print("Sum of first", num_input, "integer numbers is:", response.sum)
# Destroy the node explicitly
sum_nums_client_node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Add the entry points to setup.py inside console_scripts section:
'sum_nums_server = sum_nums.sum_nums_service_server:main',
'sum_nums_client = sum_nums.sum_nums_service_client:main',
Since our ‘sum_nums’ package depends on ‘sum_nums_srv’ package for the service structure, add it as a dependency inside the package.xml file:
<exec_depend>sum_nums_srv</exec_depend>
Build the ‘sum_nums’ package again.
$ cd ~/ros2_ws/
$ colcon build --packages-select sum_nums
$ source install/setup.bash
Execute the service node in the same terminal window.
$ ros2 run sum_nums sum_nums_server
Execute the client node in a new terminal window.
$ source install/setup.bash
$ ros2 run sum_nums sum_nums_client
Enter a positive integer number and press Enter.
The sum will be displayed by the sum_nums_client node.