From da3094b03676efad9f3c871e93139b5e0a119c5b Mon Sep 17 00:00:00 2001 From: Voldivh Date: Tue, 3 Oct 2023 12:42:18 -0500 Subject: [PATCH 1/6] Adds the python tutorial for the bindings Signed-off-by: Voldivh --- python/examples/publisher.py | 6 +- python/examples/subscriber.py | 1 - .../src/transport/_gz_transport_pybind11.cc | 2 +- tutorials/01_intro.md | 4 +- tutorials/05_services.md | 2 +- tutorials/06_python_support.md | 385 ++++++++++++++++++ tutorials/{06_security.md => 07_security.md} | 2 +- tutorials/{07_relay.md => 08_relay.md} | 0 8 files changed, 393 insertions(+), 9 deletions(-) create mode 100644 tutorials/06_python_support.md rename tutorials/{06_security.md => 07_security.md} (98%) rename tutorials/{07_relay.md => 08_relay.md} (100%) diff --git a/python/examples/publisher.py b/python/examples/publisher.py index 0fdfa89eb..0c49653af 100644 --- a/python/examples/publisher.py +++ b/python/examples/publisher.py @@ -15,7 +15,6 @@ from gz.msgs10.stringmsg_pb2 import StringMsg from gz.msgs10.vector3d_pb2 import Vector3d -from gz.transport13 import AdvertiseMessageOptions from gz.transport13 import Node import time @@ -39,10 +38,11 @@ def main(): while True: count += 1 vector3d_msg.x = count - if not (pub_stringmsg.publish(stringmsg_msg) or pub_vector3d.publish(vector3d_msg)): + if not (pub_stringmsg.publish(stringmsg_msg)): break - print("Publishing 'Hello' on topic [{}]".format(stringmsg_topic)) + if not (pub_vector3d.publish(vector3d_msg)): + break print("Publishing a Vector3d on topic [{}]".format(vector3d_topic)) time.sleep(0.1) diff --git a/python/examples/subscriber.py b/python/examples/subscriber.py index f5f26b142..498991565 100644 --- a/python/examples/subscriber.py +++ b/python/examples/subscriber.py @@ -15,7 +15,6 @@ from gz.msgs10.stringmsg_pb2 import StringMsg from gz.msgs10.vector3d_pb2 import Vector3d -from gz.transport13 import SubscribeOptions from gz.transport13 import Node import time diff --git a/python/src/transport/_gz_transport_pybind11.cc b/python/src/transport/_gz_transport_pybind11.cc index 0f8ac9422..de5cbaa34 100644 --- a/python/src/transport/_gz_transport_pybind11.cc +++ b/python/src/transport/_gz_transport_pybind11.cc @@ -186,7 +186,7 @@ PYBIND11_MODULE(BINDINGS_MODULE_NAME, m) { py::class_( m, "TopicStatistics", - "This class encapsulates statistics for a single topic..") + "This class encapsulates statistics for a single topic.") .def(py::init<>()); auto node = py::class_(m, "Node", diff --git a/tutorials/01_intro.md b/tutorials/01_intro.md index e87bd73c1..f7dc71ae9 100644 --- a/tutorials/01_intro.md +++ b/tutorials/01_intro.md @@ -20,5 +20,5 @@ combination of custom code and [ZeroMQ] (http://zeromq.org/). ## What programming language can I use with Gazebo Transport? -C++ is the native implementation and so far the only way to use the library. -We hope to offer different wrappers for the most popular languages in the future. +C++ is the native implementation and the only language that have available all library features. +Python implementation is a wrapper around C++ methods using pybind11. It does not support all features like C++, but, contains the main features such as publication, subscription and service request. diff --git a/tutorials/05_services.md b/tutorials/05_services.md index a411195cd..cdcf648cf 100644 --- a/tutorials/05_services.md +++ b/tutorials/05_services.md @@ -1,6 +1,6 @@ \page services Services -Next Tutorial: \ref security +Next Tutorial: \ref python Previous Tutorial: \ref messages ## Overview diff --git a/tutorials/06_python_support.md b/tutorials/06_python_support.md new file mode 100644 index 000000000..f38badaf7 --- /dev/null +++ b/tutorials/06_python_support.md @@ -0,0 +1,385 @@ +\page python Python Support + +Next Tutorial: \ref security +Previous Tutorial: \ref services + +## Overview + +In this tutorial, we are going to show the functionalities implemented in python. This features are brought up to python by creating bindings from the C++ implementation using pybind11. It is important to note that not all of C++ features are available yet in python, on this tutorial we will go over the most relevant features we currently have on python. 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 are going to 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 +cd ~/gz_transport_tutorial +``` + +## Publisher + +Download the [publisher.py](https://github.com/gazebosim/gz-transport/blob/gz-transport13/python/examples/publisher.py) file within the `gz_transport_tutorial` +folder and open it with your favorite editor: + +\snippet python/examples/publisher.py complete + +### Walkthrough + +```{.py} + from gz.msgs10.stringmsg_pb2 import StringMsg + from gz.msgs10.vector3d_pb2 import Vector3d + 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 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() + stringmsg_topic = "/example_stringmsg_topic" + vector3d_topic = "/example_vector3d_topic" + pub_stringmsg = node.advertise(stringmsg_topic, StringMsg) + pub_vector3d = node.advertise(vector3d_topic, Vector3d) +``` + +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. + +```{.py} + vector3d_msg = Vector3d() + vector3d_msg.x = 10 + vector3d_msg.y = 15 + vector3d_msg.z = 20 + stringmsg_msg = StringMsg() + stringmsg_msg.data = "Hello" + + try: + count = 0 + while True: + count += 1 + vector3d_msg.x = count + if not (pub_stringmsg.publish(stringmsg_msg)): + break + print("Publishing 'Hello' on topic [{}]".format(stringmsg_topic)) + if not (pub_vector3d.publish(vector3d_msg)): + break + print("Publishing a Vector3d on topic [{}]".format(vector3d_topic)) + time.sleep(0.1) + + except KeyboardInterrupt: + pass +``` + +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](https://github.com/gazebosim/gz-transport/blob/gz-transport13/python/examples/subscriber.py) +file into the `gz_transport_tutorial` folder and open it with your favorite editor: + +\snippet python/examples/subscriber.py complete + +### Walkthrough + +```{.py} + from gz.msgs10.stringmsg_pb2 import StringMsg + from gz.msgs10.vector3d_pb2 import Vector3d + 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. + +```{.py} + def stringmsg_cb(msg: StringMsg): + print("Received StringMsg: [{}]".format(msg.data)) + + def vector3_cb(msg: Vector3d): + print("Received Vector3: [x: {}, y: {}, z: {}]".format(msg.x, msg.y, msg.z)) +``` +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. + +```{.py} + # create a transport node + node = Node() + topic_stringmsg = "/example_stringmsg_topic" + topic_vector3d = "/example_vector3d_topic" + # subscribe to a topic by registering a callback + if node.subscribe(StringMsg, topic_stringmsg, stringmsg_cb): + print("Subscribing to type {} on topic [{}]".format( + StringMsg, topic_stringmsg)) + else: + print("Error subscribing to topic [{}]".format(topic_stringmsg)) + return + # subscribe to a topic by registering a callback + if node.subscribe(Vector3d, topic_vector3d, vector3_cb): + print("Subscribing to type {} on topic [{}]".format( + Vector3d, topic_vector3d)) + else: + print("Error subscribing to topic [{}]".format(topic_vector3d)) + return +``` + +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. + +```{.py} + # wait for shutdown + try: + while True: + time.sleep(0.001) + except KeyboardInterrupt: + 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*. + +## 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: + +```{.sh} +export PYTHONPATH=$PYTHONPATH:/install/lib/python +``` + +## Running the examples + +Open two new terminals and directly run the python scripts downloaded previously. + +From terminal 1: + +```{.sh} +python3 ./publisher.py +``` + +From terminal 2: + +```{.sh} +python3 ./subscriber.py +``` + +In your publisher terminal, you should expect an output similar to this one, +showing when a message is being published: + +```{.sh} +$ ./publisher.py +Publishing 'Hello' on topic [/example_stringmsg_topic] +Publishing a Vector3d on topic [/example_vector3d_topic] +Publishing 'Hello' on topic [/example_stringmsg_topic] +Publishing a Vector3d on topic [/example_vector3d_topic] +``` +In your subscriber terminal, you should expect an output similar to this one, +showing that your subscriber is receiving the topic updates: + +```{.sh} +$ ./subscriber.py +Received StringMsg: [Hello] +Received Vector3: [x: 2.0, y: 15.0, z: 20.0] +Received StringMsg: [Hello] +Received Vector3: [x: 3.0, y: 15.0, z: 20.0] +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. + +You can run any of those examples by just doing the following in a terminal: +```{.sh} +python3 ./data_race_without_mutex.py +``` +or + +```{.sh} +python3 ./data_race_with_mutex.py +``` + +## 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 like when we want to control the rate +of messages published per topic. + +We can declare the throttling option using the following code : + +```{.py} + from gz.msgs10.stringmsg_pb2 import StringMsg + from gz.transport13 import Node, AdvertiseMessageOptions + + # Create a transport node and advertise a topic with throttling enabled. + node = Node() + topic_name = "/foo" + + # Setting the throttling option + opts = AdvertiseMessageOptions() + opts.msgs_per_sec = 1 + pub = node.advertise(topic_name, StringMsg, opts); + if (!pub): + print("Error advertising topic" + topic_name) + return False +``` + +### Walkthrough + +```{.py} + # Setting the throttling option + opts = AdvertiseMessageOptions() + 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. + +```{.py} + pub = node.advertise(topic_name, StringMsg, opts); +``` + +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 : + +```{.py} + from gz.msgs10.stringmsg_pb2 import StringMsg + from gz.transport13 import Node, SubscribeOptions + + def stringmsg_cb(msg: StringMsg): + print("Received StringMsg: [{}]".format(msg.data)) + + # Create a transport node and subscribe to a topic with throttling enabled. + node = Node() + topic_name = "/foo" + opts = SubscribeOptions() + opts.msgs_per_sec = 1 + node.subscribe(StringMsg, topic_name, stringmsg_cb, opts) +``` + +### Walkthrough + +```{.py} + opts = SubscribeOptions() + opts.msgs_per_sec = 1 + 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 +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: + +```{.py} + from gz.transport13 import Node, NodeOptions + + # Create a transport node and remap a topic. + nodeOpts = NodeOptions() + nodeOptions.add_topic_remap("/foo", "/bar"); + node = Node(nodeOptions); +``` + +You can modify the publisher example to add this option. + +From terminal 1: + +```{.sh} +python3 ./publisher.py + +``` + +From terminal 2 (requires Gazebo Tools): + +```{.sh} +gz topic --echo -t /bar +``` + +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](https://github.com/gazebosim/gz-transport/blob/gz-transport13/python/examples/requester.py) +file into the `gz_transport_tutorial` folder and open it with your favorite editor: + +\snippet python/examples/requester.py complete + +### Walkthrough + +```{.py} + from gz.msgs10.stringmsg_pb2 import StringMsg + 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. + +```{.py} + node = Node() + service_name = "/echo" + request = StringMsg() + request.data = "Hello world" + response = StringMsg() + 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. + +```{.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. + +## 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. + +From terminal 1: + +```{.sh} +python3 ./requester.py +``` + +In your terminal, you should expect an output similar to this one, +showing the result and response from the request: + +```{.sh} +$ ./requester.py +Result: True +Response: Hello world +``` diff --git a/tutorials/06_security.md b/tutorials/07_security.md similarity index 98% rename from tutorials/06_security.md rename to tutorials/07_security.md index 69bad0210..6ed4829fe 100644 --- a/tutorials/06_security.md +++ b/tutorials/07_security.md @@ -1,7 +1,7 @@ \page security Security Next Tutorial: \ref relay -Previous Tutorial: \ref services +Previous Tutorial: \ref python ## Overview diff --git a/tutorials/07_relay.md b/tutorials/08_relay.md similarity index 100% rename from tutorials/07_relay.md rename to tutorials/08_relay.md From 80212ae1dbe949bf56c76d19cc2ce3da17881ded Mon Sep 17 00:00:00 2001 From: Voldivh Date: Wed, 4 Oct 2023 10:13:26 -0500 Subject: [PATCH 2/6] Address review comments Signed-off-by: Voldivh --- python/examples/publisher.py | 4 ++-- tutorials/01_intro.md | 4 ++-- tutorials/06_python_support.md | 13 +++++++------ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/python/examples/publisher.py b/python/examples/publisher.py index 0c49653af..db6c9a6fc 100644 --- a/python/examples/publisher.py +++ b/python/examples/publisher.py @@ -38,10 +38,10 @@ def main(): while True: count += 1 vector3d_msg.x = count - if not (pub_stringmsg.publish(stringmsg_msg)): + if not pub_stringmsg.publish(stringmsg_msg): break print("Publishing 'Hello' on topic [{}]".format(stringmsg_topic)) - if not (pub_vector3d.publish(vector3d_msg)): + if not pub_vector3d.publish(vector3d_msg): break print("Publishing a Vector3d on topic [{}]".format(vector3d_topic)) time.sleep(0.1) diff --git a/tutorials/01_intro.md b/tutorials/01_intro.md index f7dc71ae9..0088e0f66 100644 --- a/tutorials/01_intro.md +++ b/tutorials/01_intro.md @@ -20,5 +20,5 @@ combination of custom code and [ZeroMQ] (http://zeromq.org/). ## What programming language can I use with Gazebo Transport? -C++ is the native implementation and the only language that have available all library features. -Python implementation is a wrapper around C++ methods using pybind11. It does not support all features like C++, but, contains the main features such as publication, subscription and service request. +C++ is the native implementation and the only language that has all available library features. +Python implementation is a wrapper around C++ methods using `pybind11`. It does not support all features like C++, but contains the main features such as publication, subscription and service request. diff --git a/tutorials/06_python_support.md b/tutorials/06_python_support.md index f38badaf7..0ec2f39ed 100644 --- a/tutorials/06_python_support.md +++ b/tutorials/06_python_support.md @@ -5,9 +5,9 @@ Previous Tutorial: \ref services ## Overview -In this tutorial, we are going to show the functionalities implemented in python. This features are brought up to python by creating bindings from the C++ implementation using pybind11. It is important to note that not all of C++ features are available yet in python, on this tutorial we will go over the most relevant features we currently have on python. 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. +In this tutorial, we are going to show the functionalities implemented in python. This features are brought up to python by creating bindings from the C++ implementation using pybind11. It is important to note that not all of C++ features are available yet in Python, on this tutorial we will go over the most relevant features we currently have on Python. 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 are going to communicate via messages. One node will be a publisher that generates the information, +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. @@ -31,7 +31,7 @@ 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. @@ -46,7 +46,7 @@ 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 +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} @@ -102,6 +102,7 @@ Just as before, we are importing the `Node` class from the `gz.transport13` libr def vector3_cb(msg: Vector3d): print("Received Vector3: [x: {}, y: {}, z: {}]".format(msg.x, msg.y, msg.z)) ``` + 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. @@ -147,7 +148,7 @@ 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: ```{.sh} export PYTHONPATH=$PYTHONPATH:/install/lib/python @@ -200,6 +201,7 @@ You can run any of those examples by just doing the following in a terminal: ```{.sh} python3 ./data_race_without_mutex.py ``` + or ```{.sh} @@ -249,7 +251,6 @@ In this section of code, we declare an *AdvertiseMessageOptions* object and use 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 From d1de32d7309c28328976f8112005ce93ae12f115 Mon Sep 17 00:00:00 2001 From: Voldivh Date: Thu, 5 Oct 2023 11:14:08 -0500 Subject: [PATCH 3/6] Adds the required snippet tags to the examples Signed-off-by: Voldivh --- python/examples/publisher.py | 2 ++ python/examples/requester.py | 3 +++ python/examples/subscriber.py | 3 +++ 3 files changed, 8 insertions(+) diff --git a/python/examples/publisher.py b/python/examples/publisher.py index db6c9a6fc..ae1b77d05 100644 --- a/python/examples/publisher.py +++ b/python/examples/publisher.py @@ -13,6 +13,7 @@ # limitations under the License. # +#! [complete] from gz.msgs10.stringmsg_pb2 import StringMsg from gz.msgs10.vector3d_pb2 import Vector3d from gz.transport13 import Node @@ -49,6 +50,7 @@ def main(): except KeyboardInterrupt: pass +#! [complete] if __name__ == "__main__": main() diff --git a/python/examples/requester.py b/python/examples/requester.py index 9da72b394..398173c25 100644 --- a/python/examples/requester.py +++ b/python/examples/requester.py @@ -13,6 +13,7 @@ # limitations under the License. # +#! [complete] from gz.msgs10.stringmsg_pb2 import StringMsg from gz.transport13 import Node @@ -27,5 +28,7 @@ def main(): result, response = node.request(service_name, request, StringMsg, StringMsg, timeout) print("Result:", result, "\nResponse:", response.data) +#! [complete] + if __name__ == "__main__": main() diff --git a/python/examples/subscriber.py b/python/examples/subscriber.py index 498991565..7a0054873 100644 --- a/python/examples/subscriber.py +++ b/python/examples/subscriber.py @@ -13,6 +13,7 @@ # limitations under the License. # +#! [complete] from gz.msgs10.stringmsg_pb2 import StringMsg from gz.msgs10.vector3d_pb2 import Vector3d from gz.transport13 import Node @@ -55,5 +56,7 @@ def main(): pass print("Done") +#! [complete] + if __name__ == "__main__": main() From b774eb6fd857fe56bd53c94af64b0a2257d30aab Mon Sep 17 00:00:00 2001 From: Voldivh Date: Mon, 16 Oct 2023 08:50:33 -0500 Subject: [PATCH 4/6] Shortens a couple of lines Signed-off-by: Voldivh --- tutorials/06_python_support.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/06_python_support.md b/tutorials/06_python_support.md index 0ec2f39ed..2a0bf6b01 100644 --- a/tutorials/06_python_support.md +++ b/tutorials/06_python_support.md @@ -5,7 +5,7 @@ Previous Tutorial: \ref services ## Overview -In this tutorial, we are going to show the functionalities implemented in python. This features are brought up to python by creating bindings from the C++ implementation using pybind11. It is important to note that not all of C++ features are available yet in Python, on this tutorial we will go over the most relevant features we currently have on Python. 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. +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 From e407bf5269612a7b3b8372ecd5f5332772d41d1c Mon Sep 17 00:00:00 2001 From: Voldivh Date: Mon, 16 Oct 2023 10:02:29 -0500 Subject: [PATCH 5/6] Uses capital P on Python Signed-off-by: Voldivh --- tutorials/06_python_support.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tutorials/06_python_support.md b/tutorials/06_python_support.md index 2a0bf6b01..f754d61ac 100644 --- a/tutorials/06_python_support.md +++ b/tutorials/06_python_support.md @@ -156,7 +156,7 @@ export PYTHONPATH=$PYTHONPATH:/install/lib/python ## Running the examples -Open two new terminals and directly run the python scripts downloaded previously. +Open two new terminals and directly run the Python scripts downloaded previously. From terminal 1: @@ -193,7 +193,7 @@ 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). +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. @@ -364,11 +364,11 @@ Here we are creating the service request to the `/echo` service, storing the res ## 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: From 46e050b78b9fb49f28ddbe012d1ea4cb8b16ff60 Mon Sep 17 00:00:00 2001 From: Voldivh Date: Tue, 24 Oct 2023 10:52:50 -0500 Subject: [PATCH 6/6] Fixes some grammar Signed-off-by: Voldivh --- tutorials/06_python_support.md | 104 ++++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 29 deletions(-) diff --git a/tutorials/06_python_support.md b/tutorials/06_python_support.md index f754d61ac..81ebd2603 100644 --- a/tutorials/06_python_support.md +++ b/tutorials/06_python_support.md @@ -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 @@ -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() @@ -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() @@ -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 @@ -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): @@ -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 @@ -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 @@ -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:/install/lib/python ``` +2. If you builded from source using `cmake`: +```{.sh} +export PYTHONPATH=$PYTHONPATH:/lib/python +``` ## Running the examples @@ -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} @@ -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 : @@ -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); @@ -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 @@ -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() @@ -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: