Server Client
Publishers and Subscribers are good but what if you want a two-way style of communication between nodes?
Server and Clients are similar to Publisher and Subscribers where they have a Service
and Nodes can communicate through those services.
Let’s make a basic service where it just adds 2 numbers
Server
create a file called server.py
and import the ROS
libraries:
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node
Then create a class MinimalService
that implements Node
class MinimalService(Node):
in the constructor run the parent constructor and create a service object:
def __init__(self):
super().__init__("minimal_service")
self.srv = self.create_service(AddTwoInts, "add_two_ints", self.add_two_ints_callback)
The service object takes the type it expects, the name, and the function to handle the request.
Next, create the function to handle the request and have it return the result of the sum.
def add_two_ints_callback(self, request, response):
response.sum = request.a + request.b
self.get_logger().info("Incoming request\na: "+ str(request.a) +" b: " + str(request.b))
return response
Then outside of the class, we want to start the node:
def main():
rclpy.init() # initializes the ROS library
minimal_service = MinimalService() # creates our MinimalService obj
rclpy.spin(minimal_service) # causes minimal_service to loop
rclpy.shutdown() # deinits the ROS library
if __name__ == '__main__':
main()
Solution
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("Incoming request\na: "+ str(request.a) +" b: " + str(request.b))
return response
def main():
rclpy.init() # initializes the ROS library
minimal_service = MinimalService() # creates our MinimalService obj
rclpy.spin(minimal_service) # causes minimal_service to loop
rclpy.shutdown() # deinits the ROS library
if __name__ == '__main__':
main()
Client
For the client lets have it where it takes in the two numbers as input arguments before we run it: python3 client.py 2 3
create a file called client.py
and import the ROS
libraries:
import sys
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node
create a class called MinimalClientAsync
and extend the Node
class
class MinimalClientAsync(Node):
in the constructor run the parent class’s constructor and create a client object and a request object.
Then we try to connect the client with the service by using while
. This will search for 1 second for the service "add_two_ints"
before it gives up.
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...")
self.req = AddTwoInts.Request()
Next lets create a function, send_request()
to take in two numbers and call the service:
def send_request(self, a, b):
self.req.a = a
self.req.b = b
return self.cli.call_async(self.req) # uses client object to call the service
Then outside of the class we want to run our new Node so first init the ROS
library with:
rclpy.init()
Then create a MinimalClientAsync()
object.
We are then going to take the two input arguments with sys.argv[1]
and sys.argv[2]
plug them into send_request
and wait for the result
To wait for a response from the service we use rclpy.spin_until_future_complete()
It takes in 2 arguments, the Client Node and the variable that holds the result.
finally, we get our results with future.result()
and print it out.
minimal_client = MinimalClientAsync()
future = minimal_client.send_request(int(sys.argv[1]), int(sys.argv[2]))
rclpy.spin_until_future_complete(minimal_client, future)
response = future.result()
minimal_client.get_logger().info("Result of add_two_ints: for "+ sys.argv[1] + " + " + sys.argv[2] + " = " + str(response.sum))
Then we shut everything down:
minimal_client.destroy_node()
rclpy.shutdown()
Solution
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...")
self.req = AddTwoInts.Request()
def send_request(self, a, b):
self.req.a = a
self.req.b = b
return self.cli.call_async(self.req)
def main():
rclpy.init()
minimal_client = MinimalClientAsync()
future = minimal_client.send_request(int(sys.argv[1]), int(sys.argv[2]))
rclpy.spin_until_future_complete(minimal_client, future)
response = future.result()
minimal_client.get_logger().info("Result of add_two_ints: for "+ sys.argv[1] + " + " + sys.argv[2] + " = " + str(response.sum))
minimal_client.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Now that we have created a Server and Client we can run them both to see them in action
In two different terminals run the Server first then the client
rqt_graph
ros2 service list
More Exercises!!!! ( YAYYYY :D )
- simple Rock Paper Scissors
- simple Hangman