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.
#!/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()
-
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. -
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
andsrv_name
must match the definition in the server, in order to be able to communicate and send requests to it. -
Here we're building a simple Command-line Interface (CLI) for the node, using
argparse
. -
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. -
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 toFalse
. If we do pass in this argument then the value will beTrue
.(You'll see how this all works shortly, when we actually run the node.)
-
Here we "parse" the arguments that have been passed to the node from the CLI, so that we can access them from
self.args
. -
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!
-
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 useros2 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. -
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.
-
We then call our client's
send_request()
class method, supplying theguess
andcheat
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 calledfuture
. -
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. -
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... -
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 usingros2 interface show
: