Next Tutorial: Python Support Previous Tutorial: Messages
Overview
In this tutorial, we are going to create two nodes that are going to communicate via services. You can see a service as a function that is going to be executed in a different node. Services have two main components: a service provider and a service consumer. A service provider is the node that offers the service to the rest of the world. The service consumers are the nodes that request the function offered by the provider. Note that in Gazebo Transport the location of the service is hidden. The discovery layer of the library is in charge of discovering and keeping and updated list of services available.
In the next tutorial, one node will be the service provider that offers an echo service, whereas the other node will be the service consumer requesting an echo call.
Responser
Download the responser.cc file within the gz_transport_tutorial
folder and open it with your favorite editor:
Walkthrough
The line #include <gz/transport.hh>
contains the Gazebo Transport header for using the transport library.
The next line includes the generated Protobuf code that we are going to use for our messages. We are going to use StringMsg
type Protobuf messages for our services.
As a service provider, our node needs to register a function callback that will execute every time a new service request is received. The signature of the callback is always similar to the one shown in this example with the exception of the Protobuf messages types for the _req
(request) and _rep
(response). The request parameter contains the input parameters of the request. The response message contains any resulting data from the service call. The return value denotes if the overall service call was considered successful or not. In our example, as a simple echo service, we just fill the response with the same data contained in the request.
We declare a Node that will offer all the transport functionality. In our case, we are interested in offering a service, so the first step is to announce our service name. Once a service name is advertised, we can accept service requests.
If you don't have any other tasks to do besides waiting for service requests, you can use the call waitForShutdown()
that will block your current thread until you hit CTRL-C. Note that this function captures the SIGINT and SIGTERM signals.
Synchronous requester
Download the requester.cc file within the gz_transport_tutorial
folder and open it with your favorite editor:
Walkthrough
We declare the Node that allows us to request a service. Next, we declare and fill the message used as an input parameter for our echo request. Then, we declare the Protobuf message that will contain the response and the variable that will tell us if the service request succeed or failed. In this example, we will use a synchronous request, meaning that our code will block until the response is received or a timeout expires. The value of the timeout is expressed in milliseconds.
In this section of the code we use the method Request()
for forwarding the service call to any service provider of the service /echo
. Gazebo Transport will find a node, communicate the input data, capture the response and pass it to your output parameter. The return value will tell you if the request expired or the response was received. The result
value will tell you if the service provider considered the operation valid.
Imagine for example that we are using a division service, where our input message contains the numerator and denominator. If there are no nodes offering this service, our request will timeout (return value false
). On the other hand, if there's at least one node providing the service, the request will return true
signaling that the request was received. However, if we set our denominator to 0
in the input message, result
will be false
reporting that something went wrong in the request. If the input parameters are valid, we'll receive a result value of true
and we can use our response message.
Asynchronous requester
Download the requester_async.cc file within the gz_transport_tutorial
folder and open it with your favorite editor:
Walkthrough
We need to register a function callback that will execute when we receive our service response. The signature of the callback is always similar to the one shown in this example with the only exception of the Protobuf message type used in the response. You should create a function callback with the appropriate Protobuf type depending on the response type of the service requested. In our case, we know that the service /echo
will answer with a Protobuf StringMsg
type.
In this section of the code we declare a node and a Protobuf message that is filled with the input parameters for our request. Next, we just use the asynchronous variant of the Request()
method that forwards a service call to any service provider of the service /echo
. Gazebo Transport will find a node, communicate the data, capture the response and pass it to your callback, in addition of the service call result. Note that this variant of Request()
is asynchronous, so your code will not block while your service request is handled.
Oneway responser
Not all the service requests require a response. In these cases we can use a oneway service to process service requests without sending back responses. Oneway services don't accept any output parameters nor the requests have to wait for the response.
Download the responser_oneway.cc file within the gz_transport_tutorial
folder and open it with your favorite editor:
Walkthrough
As a oneway service provider, our node needs to advertise a service that doesn't send a response back. The signature of the callback contains only one parameter that is the input parameter, _req
(request). We don't need _rep
(response) or _result
as there is no response expected. In our example, the value of the input parameter is printed on the screen.
We declare a Node that will offer all the transport functionality. In our case, we are interested in offering a oneway service, so the first step is to announce our service name. Once a service name is advertised, we can accept service requests.
Oneway requester
This case is similar to the oneway service provider. This code can be used for requesting a service that does not need a response back. We don't need any output parameters in this case nor we have to wait for the response.
Download the requester_oneway.cc file within the gz_transport_tutorial
folder and open it with your favorite editor:
Walkthrough
First of all we declare a node and a Protobuf message that is filled with the input parameters for our /oneway
service. Next, we just use the oneway variant of the Request()
method that forwards a service call to any service provider of the service /oneway
. Gazebo Transport will find a node and communicate the data without waiting for the response. The return value of Request()
indicates if the request was successfully queued. Note that this variant of Request()
is also asynchronous, so your code will not block while your service request is handled. In this example, we also call waitForShutdown()
to minimize the risk of terminating the program before the request was already published.
Service without input parameter
Sometimes we want to receive some result but don't have any input parameter to send.
Download the responser_no_input.cc file within the gz_transport_tutorial
folder and open it with your favorite editor:
Walkthrough
Service doesn't receive anything. The signature of the callback contains the parameters _rep
(response). In our example, we return the quote.
We declare a Node that will offer all the transport functionality. In our case, we are interested in offering service without input, so the first step is to announce the service name. Once a service name is advertised, we can accept service requests.
Empty requester sync and async
This case is similar to the service without input parameter. We don't send any request.
Download the requester_no_input.cc file within the gz_transport_tutorial
folder and open it with your favorite editor:
Walkthrough
First of all we declare a node and a message that will contain the response from /quote
service. Next, we use the variant without input parameter of the Request()
method. The return value of Request()
indicates whether the request timed out or reached the service provider and result
shows if the service was successfully executed.
We also have the async version for service request without input. You should download requester_async_no_input.cc file within the gz_transport_tutorial
folder.
Building the code
Download the CMakeLists.txt file within the gz_transport_tutorial
folder. Then, create a msgs
directory and download CMakeLists.txt and stringmsg.proto inside the msgs
directory.
Once you have all your files, go ahead and create a build/
folder within the gz_transport_tutorial
directory.
Run cmake
and build the code.
Running the examples
NOTE It is essential to have a valid value of
GZ_PARTITION
environment variable and to have it set to the same value in all open terminals. AsGZ_PARTITION
is based on hostname and username, especially Windows and Mac users might have problems due to spaces in their username, which are not a valid character inGZ_PARTITION
. gz-transport prints errorInvalid partition name
in such case. To resolve that, setGZ_PARTITION
explicitly to a valid value:# Linux and Macexport GZ_PARTITION=test# Windowsset GZ_PARTITION=test
NOTE On Windows, you can see firewall or antivirus prompts when running the examples. For them to work properly, you should allow all communication to the example programs.
Open three new terminals and from your build/
directory run the executables.
From terminal 1:
From terminal 2:
From terminal 3:
In your requester terminals, you should expect an output similar to this one, showing that your requesters have received their responses:
For running the oneway examples, open two terminals and from your build/
directory run the executables.
From terminal 1:
From terminal 2:
In your responser terminal, you should expect an output similar to this one, showing that your service provider has received a request:
For running the examples without input, open three terminals and from your build/
directory run the executables.
From terminal 1:
From terminal 2:
From terminal 3:
In your requesters' terminals, you should expect an output similar to this one, showing that you have received a response: