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.

image.png

Service-MultipleServiceClient.gif

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

image.png

image.png

rqt_graph

image.png

ros2 service list

image.png

More Exercises!!!! ( YAYYYY :D )

  • simple Rock Paper Scissors
  • simple Hangman