Creating a Python Service Client

Copy all the code below into your number_game_client.py file and then review the annotations to understand how it all works.

number_game_client.py
#!/usr/bin/env python3

import rclpy
from rclpy.node import Node

from part4_services.srv import MyNumberGame

import argparse # (1)!

class NumberGameClient(Node):

    def __init__(self):
        super().__init__('number_game_client')

        self.client = self.create_client(
            srv_type=MyNumberGame, 
            srv_name='guess_the_number'
        ) # (2)!

        cli = argparse.ArgumentParser() # (3)!
        cli.add_argument(
            "-g", "--guess", default=0, type=int
        ) # (4)!
        cli.add_argument(
            "-c", "--cheat", action="store_true"
        ) # (5)!
        self.args = cli.parse_args() # (6)!

        while not self.client.wait_for_service(timeout_sec=1.0):
            self.get_logger().info(
                "Waiting for service..."
            ) # (7)!

    def send_request(self, guess, cheat): # (8)!
        request = MyNumberGame.Request()
        request.guess = guess
        request.cheat = cheat

        return self.client.call_async(request)

def main():
    rclpy.init()
    client = NumberGameClient()

    client.get_logger().info(
        f"Sending the request:\n"
        f" - guess: {client.args.guess}\n"
        f" - cheat: {client.args.cheat}\n"
        f"   Awaiting response..."
    ) # (9)!

    future = client.send_request(client.args.guess, client.args.cheat) # (10)!
    rclpy.spin_until_future_complete(client, future) # (11)!
    response = future.result() # (12)!

    client.get_logger().info(
        f"The server has responded with:\n"
        f" - {'You guessed correctly! :)' if response.correct else 'Incorrect guess :('}\n"
        f" - Number of attempts so far: {response.num_guesses}\n"
        f" - A hint: '{response.hint}'."
    ) # (13)!

    client.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()
  1. Nothing above this should be new to you.

    Here however, we're importing a standard Python module called argparse, which we'll use to build a command-line interface for our node.

  2. Creating a Service Client is done using the create_client() class method, providing the name of the service that we want to call (srv_name), and specifying the interface type used by it (srv_type).

    srv_type and srv_name must match the definition in the server, in order to be able to communicate and send requests to it.

  3. Here we're building a simple Command-line Interface (CLI) for the node, using argparse.

  4. We add an argument here called "guess", which will be used to pass our guesses for the number game from the command line, into this node.

    The node itself can then access the value that we've passed to it (an int) and use this to construct a service request.

    We've assigned a default value of 0 here, for cases where we don't pass a guess from the CLI.

  5. Here we're adding a second Command-line Argument (CLA) called "cheat".

    action="store_true" ensures that if we don't specify this argument as a CLA then the value will be set to False. If we do pass in this argument then the value will be True.

    (You'll see how this all works shortly, when we actually run the node.)

  6. Here we "parse" the arguments that have been passed to the node from the CLI, so that we can access them from self.args.

  7. We use a while loop here to halt the execution of the code at this point and wait for the service to become available (if it isn't already).

    We can't send a request to a service that isn't actually running!

  8. In this class method we construct a service request, based on the values that have been passed via the CLI.

    (This method is called in the main() function below.)

    We know what the request attributes are called, because we defined them in the MyNumberGame.srv file, and we can also use ros2 interface show to recall them at any point:

    $ ros2 interface show part4_services/srv/MyNumberGame
    
    int32 guess
    bool cheat
    ---
    int32 num_guesses
    string hint
    bool correct
    

    call_async(request) then actually sends this request to the server.

  9. Here we're grabbing the values passed via the CLI and printing them as a log message to the terminal, in order to verify exactly what request will be sent.

  10. We then call our client's send_request() class method, supplying the guess and cheat values from the CLI to this too, in order for the request to be constructed accordingly, and sent to the server.

    The output of this function is the output of the call_async(request) call, which we assign to a variable called future.

  11. We use the rclpy.spin_until_future_complete() method here, which (as the name suggests) will allow our node (client) to spin only until our service request (future) has completed.

  12. Once we've reached this point then the service has completed and returned its Response.

    We obtain the response from our future object so that we can read its values...

  13. To finish off, we construct another log message to contain all the values returned by the Server (i.e. the Response).

    We know what these attributes are called, because we defined them in the MyNumberGame.srv file, which we can recall at any point using ros2 interface show:

    $ ros2 interface show part4_services/srv/MyNumberGame
    
    int32 guess
    bool cheat
    ---
    int32 num_guesses
    string hint
    bool correct
    

← Back to Part 4