Next Tutorial: Security Previous Tutorial: Services
Overview
In this tutorial, we are going to show the functionalities implemented in Python. These features are brought up by creating bindings from the C++ implementation using pybind11. It is important to note that not all of C++ features are available yet, on this tutorial we will go over the most relevant features. For more information, refer to the init.py file which is a wrapper for all the bindings.
For this tutorial, we will create two nodes that communicate via messages. One node will be a publisher that generates the information, whereas the other node will be the subscriber consuming the information. Our nodes will be running on different processes within the same machine.
Prerequisites
Before you begin, make sure you have the following installed:
- python3-gz-transport13
To install the required package in Linux, run:
Publisher
Download the publisher.py file within the gz_transport_tutorial
folder and open it with your favorite editor:
Walkthrough
The library gz.transport13
contains all the Gazebo Transport elements that can be used in Python. The final API we will use is contained inside the class Node
.
The lines from gz.msgs10.stringmsg_pb2 import StringMsg
and from gz.msgs10.vector3d_pb2 import Vector3d
includes the generated protobuf code that we are going to use for our messages. We are going to publish two types of messages: StringMsg
and Vector3d
protobuf messages.
First of all we declare a Node that will offer some of the transport functionality. In our case, we are interested in publishing topic updates, so the first step is to announce our topics names and their types. Once a topic name is advertised, we can start publishing periodic messages using the publishers objects.
In this section of the code we create the protobuf messages and fill them with content. Next, we iterate in a loop that publishes one message every 100ms to each topic. The method publish() sends a message to all the subscribers.
Subscriber
Download the subscriber.py file into the gz_transport_tutorial
folder and open it with your favorite editor:
Walkthrough
Just as before, we are importing the Node
class from the gz.transport13
library and the generated code for the StringMsg
and Vector3d
protobuf messages.
We need to register a function callback that will execute every time we receive a new topic update. The signature of the callback is always similar to the ones shown in this example with the only exception of the protobuf message type. You should create a function callback with the appropriate protobuf type depending on the type of the topic advertised. In our case, we know that topic /example_stringmsg_topic
will contain a Protobuf StringMsg
type and topic /example_vector3d_topic
a Vector3d
type.
After the node creation, the method subscribe()
allows you to subscribe to a given topic by specifying the message type, the topic name and a subscription callback function.
If you don't have any other tasks to do besides waiting for incoming messages, we create an infinite loop that checks for messages each 1ms that will block your current thread until you hit CTRL-C.
Updating PYTHONPATH
If you made the binary installation of Gazebo Transport, you can skip this step and go directly to the next section. Otherwise, if you built the package from source it is needed to update the PYTHONPATH in order for Python to recognize the library by doing the following:
- If you builded from source using
colcon
:export PYTHONPATH=$PYTHONPATH:<path to ws>/install/lib/python - If you builded from source using
cmake
:export PYTHONPATH=$PYTHONPATH:<path_install_prefix>/lib/python
Running the examples
Open two new terminals and directly run the Python scripts downloaded previously.
From terminal 1:
From terminal 2:
In your publisher terminal, you should expect an output similar to this one, showing when a message is being published:
In your subscriber terminal, you should expect an output similar to this one, showing that your subscriber is receiving the topic updates:
Threading in Gazebo Transport
The way Gazebo Transport is implemented, it creates several threads each time a node, publisher, subscriber, etc is created. While this allows us to have a better parallelization in processes, a downside is possible race conditions that might occur if the ownership/access of variables is shared across multiple threads. Even though Python has its GIL, all the available features are bindings created for its C++ implementation, in other words, the downsides mentioned before are still an issue to be careful about. We recommend to always use threading locks when working with object that are used in several places (publisher and subscribers).
We developed a couple of examples that demonstrate this particular issue. Take a look at a publisher and subscriber (whithin the same node) that have race conditions triggered in the data_race_without_mutex.py file. The proposed solution to this issue is to use the threading
library, you can see the same example with a mutex in the data_race_with_mutex.py file.
You can run any of those examples by just doing the following in a terminal:
or
Advertise Options
We can specify some options before we publish the messages. One such option is to specify the number of messages published per topic per second. It is optional to use but it can be handy in situations where we want to control the rate of messages published per topic.
We can declare the throttling option using the following code :
Walkthrough
In this section of code, we declare an AdvertiseMessageOptions object and use it to set the publishing rate (the member msgs_per_sec
), In our case, the rate specified is 1 msg/sec.
Next, we advertise the topic with message throttling enabled. To do it, we pass opts as an argument to the advertise() method.
Subscribe Options
A similar option is also available for the Subscriber node which enables it to control the rate of incoming messages from a specific topic. While subscribing to a topic, we can use this option to control the number of messages received per second from that particular topic.
We can declare the throttling option using the following code :
Walkthrough
In this section of code, we create a SubscribeOptions object and use it to set message rate (the member msgs_per_sec
). In our case, the message rate specified is 1 msg/sec. Then, we subscribe to the topic using the subscribe() method with opts passed as an argument to it.
Topic remapping
It's possible to set some global node options that will affect both publishers and subscribers. One of these options is topic remapping. A topic remap consists of a pair of topic names. The first name is the original topic name to be replaced. The second name is the new topic name to use instead. As an example, imagine that you recorded a collection of messages published over topic /foo
. Maybe in the future, you want to play back the log file but remapping the topic /foo
to /bar
. This way, all messages will be published over the /bar
topic without having to modify the publisher and create a new log.
We can declare the topic remapping option using the following code:
You can modify the publisher example to add this option.
From terminal 1:
From terminal 2 (requires Gazebo Tools):
And you should receive all the messages coming in terminal 2.
The command gz log playback
also supports the notion of topic remapping. Run gz log playback -h
in your terminal for further details (requires Gazebo Tools).
Service Requester
Download the requester.py file into the gz_transport_tutorial
folder and open it with your favorite editor:
Walkthrough
Just as before, we are importing the Node
class from the gz.transport13
library and the generated code for the StringMsg
protobuf message.
On these lines we are creating our Node object which will be used to create the service request and defining all the relevant variables in order to create a request, the service name, the timeout of the request, the request and response data types.
Here we are creating the service request to the /echo
service, storing the result and response of the request in some variables and printing them out.
Service Responser
Unfortunately, this feature is not available on Python at the moment. However, we can use a service responser created in C++ and make a request to it from a code in Python. Taking that into account, we will use the response.cc file as the service responser.
Running the examples
Open a new terminal and directly run the Python script downloaded previously. It is expected that the service responser is running in another terminal for this, you can refer to the previous tutorial Services.
From terminal 1:
In your terminal, you should expect an output similar to this one, showing the result and response from the request: