Skip to content

Commit

Permalink
Fixes some grammar
Browse files Browse the repository at this point in the history
Signed-off-by: Voldivh <[email protected]>
  • Loading branch information
Voldivh committed Oct 24, 2023
1 parent e407bf5 commit 46e050b
Showing 1 changed file with 75 additions and 29 deletions.
104 changes: 75 additions & 29 deletions tutorials/06_python_support.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@ Previous Tutorial: \ref services

## Overview

In this tutorial, we are going to show the functionalities implemented in Python. This 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](https://github.com/gazebosim/gz-transport/blob/gz-transport13/python/src/__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.
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](https://github.com/gazebosim/gz-transport/blob/gz-transport13/python/src/__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.

```{.sh}
mkdir ~/gz_transport_tutorial
Expand All @@ -31,10 +37,13 @@ folder and open it with your favorite editor:
from gz.transport13 import Node
```

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 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.
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.

```{.py}
node = Node()
Expand All @@ -46,8 +55,8 @@ for our messages. We are going to publish two types of messages: `StringMsg` and

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.
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.

```{.py}
vector3d_msg = Vector3d()
Expand Down Expand Up @@ -75,8 +84,8 @@ advertised, we can start publishing periodic messages using the publishers objec
```

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.
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

Expand All @@ -93,7 +102,8 @@ file into the `gz_transport_tutorial` folder and open it with your favorite edit
from gz.transport13 import Node
```

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.
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.

```{.py}
def stringmsg_cb(msg: StringMsg):
Expand All @@ -108,7 +118,8 @@ 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.
`/example_stringmsg_topic` will contain a Protobuf `StringMsg` type and topic
`/example_vector3d_topic` a `Vector3d` type.

```{.py}
# create a transport node
Expand All @@ -132,7 +143,8 @@ depending on the type of the topic advertised. In our case, we know that topic
```

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.
given topic by specifying the message type, the topic name and a subscription
callback function.

```{.py}
# wait for shutdown
Expand All @@ -143,16 +155,25 @@ given topic by specifying the message type, the topic name and a subscription ca
pass
```

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*.
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 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:

1. If you builded from source using `colcon`:
```{.sh}
export PYTHONPATH=$PYTHONPATH:<path to ws>/install/lib/python
```
2. If you builded from source using `cmake`:
```{.sh}
export PYTHONPATH=$PYTHONPATH:<path_install_prefix>/lib/python
```

## Running the examples

Expand Down Expand Up @@ -193,9 +214,21 @@ Received StringMsg: [Hello]
Received Vector3: [x: 4.0, y: 15.0, z: 20.0]
```
## 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 paralization in processes, a downside is possible race conditions that might occur if the ownership/access of variables is shared across multiple processes. Even though Python have it's [GIL](https://wiki.python.org/moin/GlobalInterpreterLock), all the available features are bindings created for it's C++ implementation, in other words, downsides commented before are still an issue to be careful. 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](https://github.com/gazebosim/gz-transport/blob/gz-transport13/python/examples/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](https://github.com/gazebosim/gz-transport/blob/gz-transport13/python/examples/data_race_with_mutex.py) file.
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](https://wiki.python.org/moin/GlobalInterpreterLock),
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](https://github.com/gazebosim/gz-transport/blob/gz-transport13/python/examples/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](https://github.com/gazebosim/gz-transport/blob/gz-transport13/python/examples/data_race_with_mutex.py) file.

You can run any of those examples by just doing the following in a terminal:
```{.sh}
Expand All @@ -212,7 +245,7 @@ python3 ./data_race_with_mutex.py

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 like when we want to control the rate
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 :
Expand Down Expand Up @@ -242,7 +275,9 @@ We can declare the throttling option using the following code :
opts.msgs_per_sec = 1
```

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.
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.

```{.py}
pub = node.advertise(topic_name, StringMsg, opts);
Expand Down Expand Up @@ -283,7 +318,9 @@ We can declare the throttling option using the following code :
node.subscribe(StringMsg, topic_name, stringmsg_cb, opts)
```

In this section of code, we create a *SubscribeOptions* object and use it 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
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
Expand Down Expand Up @@ -342,7 +379,8 @@ file into the `gz_transport_tutorial` folder and open it with your favorite edit
from gz.transport13 import Node
```

Just as before, we are importing the `Node` class from the `gz.transport13` library and the generated code for the `StringMsg` protobuf message.
Just as before, we are importing the `Node` class from the `gz.transport13`
library and the generated code for the `StringMsg` protobuf message.

```{.py}
node = Node()
Expand All @@ -353,22 +391,30 @@ Just as before, we are importing the `Node` class from the `gz.transport13` libr
timeout = 5000
```

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.
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.

```{.py}
result, response = node.request(service_name, request, StringMsg, StringMsg, timeout)
print("Result:", result, "\nResponse:", response.data)
```

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.
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](https://github.com/gazebosim/gz-transport/blob/gz-transport13/example/responser.cc) file as the 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](https://github.com/gazebosim/gz-transport/blob/gz-transport13/example/responser.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 \ref services.
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 \ref services.

From terminal 1:

Expand Down

0 comments on commit 46e050b

Please sign in to comment.