srmainwaring / gz-python

Python bindings for gz-msgs and gz-transport
Other
17 stars 1 forks source link
gazebo gz-msgs gz-transport protobuf pybind11 python

Gazebo Python Bindings

This project provides Python bindings for gz-msgs and gz-transport.

ubuntu-build

Building with CMake

Install Gazebo

Follow the installation instructions for Gazebo Garden. This project depends directly on gz-msgs and gz-transport. These may be either available as system installs or in a source install in a local workspace folder which we assume is ~/gz_ws.

Install gz-python

Clone this repo into the workspace source directory:

cd ~/gz_ws/src
git clone https://github.com/srmainwaring/gz-python.git

Build with CMake

Currently our use of pybind11_protobuf requires the protobuf compiler to generate the Python implementation of the Python protobuf bindings. This is configured with the environment variable:

export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python

Set the environment variable determing the Gazebo version. The default is garden:

export GAZEBO_VERSION=garden

Then create a build directory, configure and make:

mkdir -p ~/gz_ws/src/gz-python/build
cd ~/gz_ws/src/gz-python/build
cmake ..
make

Update the Python environment

Update the PYTHONPATH to include the location of the extension modules and the generated Python protobuf bindings.

cd ~/gz_ws/src/gz-python
export PYTHONPATH=${PYTHONPATH}:$(pwd)/build/python

Usage

gz-msg bindings

The Python bindings for gz-msgs are the standard generated protobuf code for Python. For example gz-msgs/proto/gz/msgs/time.proto may be used as follows:

# example.py
from gz.msgs.time_pb2 import Time

msg = Time()
msg.sec = 15
msg.nsec = 21
print(msg)

gz-transport bindings

The Python bindings for gz-transport are contained in a module called transport. The object naming and usage for the most part follows the C++ interface, so the C++ Gazebo Tutorials are a good guide on how to use the library.

Publish:

from gz.msgs.stringmsg_pb2 import StringMsg

from gz.transport import AdvertiseMessageOptions
from gz.transport import Node

# Create a transport node
node = Node()

# Advertise a topic
topic = "/foo"
msg_type_name = StringMsg.DESCRIPTOR.full_name
pub_options = AdvertiseMessageOptions()
pub = node.advertise(topic, msg_type_name, pub_options)

# Publish a message
msg = StringMsg()
msg.data = "hello"
pub.publish(msg)

Subscribe:

import time
import typing

from gz.msgs.stringmsg_pb2 import StringMsg

from gz.transport import SubscribeOptions
from gz.transport import Node

def cb(msg: StringMsg) -> None:
    print("Msg: [{}] from Python".format(msg.data))

# Create a transport node
node = Node()

# Subscribe to a topic by registering a callback
topic = "/foo"
msg_type_name = StringMsg.DESCRIPTOR.full_name
sub_options = SubscribeOptions()

node.subscribe(topic, cb, msg_type_name, sub_options)

Examples

A number of examples in C++ and Python are provided. In the following we suppose that they are being run from the project directory ~/gz_ws/src/gz-python.


src/msg_example.cc is a copy of the gz-msgs tutorial example:

$ ./build/msg_example
Point1:
x: 1
y: 3
z: 5

Point2:
x: 2
y: 4
z: 6

src/publisher.cc and src/subscriber.cc is copied from the gz-transport messages tutorial example.

From terminal 1:

$ ./build/publisher
Publishing hello on topic [/foo]
Publishing hello on topic [/foo]
...

From terminal 2:

$ ./build/subscriber
Msg: hello
Msg: hello
...

src/rover_publisher.cc and src/rover_subscriber.cc comprise another publish / subscribe example that publishes the pose and twist of a rover moving in a circle with constant angular velocity.

From terminal 1:

$ ./build/rover_publisher
Publishing pose on topic [/pose], twist on topic [/twist]
Publishing pose on topic [/pose], twist on topic [/twist]
...

From terminal 2:

$ ./build/rover_subscriber
header {
  stamp {
    sec: 10
    nsec: 45483925
  }
}
name: "base_link"
id: 20
position {
  x: 2.7015115293406988
  y: 4.2073549240394827
}
orientation {
  z: 0.479425538604203
  w: 0.87758256189037276
}
header {
  stamp {
    sec: 10
    nsec: 45483925
  }
}
linear {
  x: -0.42073549240394825
  y: 0.27015115293406988
}
angular {
  z: 0.1
}

python/msgs_example.py is a Python example that uses the generated Python protobuf libraries for gz-msgs:

$ ./python/msgs_example.py
Gazebo Protobuf Example

proto api type: python
----------------------------------------
<class 'gz.msgs.time_pb2.Time'>
sec: 15
nsec: 21
...

python/publisher.py is a Python version of the C++ src/publisher.cc described above. You can listen to the messages using the C++ subscriber as before.

From terminal 1:

$ ./python/publisher.py 
Advertising gz.msgs.StringMsg on topic [/foo]
Publishing hello on topic [/foo]
Publishing hello on topic [/foo]
...

From terminal 2:

$ ./python/subscriber.py 
Subscribing to type gz.msgs.StringMsg on topic [/foo]
Msg: [hello] from Python
Msg: [hello] from Python
...

python/rover_publisher.py is a Python version of the C++ src/rover_publisher.cc example.

From terminal 1:

./python/rover_publisher.py
Advertising gz.msgs.Pose on topic [/pose]
Advertising gz.msgs.Twist on topic [/twist]
Publishing pose on topic [/pose], twist on topic [/twist]
Publishing pose on topic [/pose], twist on topic [/twist]
...

From terminal 2:

./python/rover_subscriber.py
Subscribing to type gz.msgs.Pose on topic [/pose]
Subscribing to type gz.msgs.Twist on topic [/twist]
header {
  stamp {
    sec: 2
    nsec: 511006000
  }
}
name: "base_link"
id: 5
position {
  x: 4.900332889206208
  y: 0.9933466539753061
}
orientation {
  z: 0.09983341664682815
  w: 0.9950041652780257
}

header {
  stamp {
    sec: 2
    nsec: 511006000
  }
}
linear {
  x: -0.09933466539753061
  y: 0.4900332889206208
}
angular {
  z: 0.1
}
...

python/gz_topic_list.py is a Python version of the command gz topic -l

$ ./python/gz_topic_list.py 
/foo

python/gz_topic_info.py is a Python version of the command gz topic -i -t /foo

$ ./python/gz_topic_info.py -t /foo
Publishers [Address, Message Type]:
  tcp://127.0.0.1:60328, gz.msgs.StringMsg

python/gz_topic_echo.py is a Python version of the command gz topic -e -t /foo

$ ./python/gz_topic_echo.py -t /foo
Subscribing to topic [/foo]
data: "hello"

data: "hello"

data: "hello"

python/gz_service_list.py is a Python version of the command gz service -l

$ ./python/gz_service_list.py 
/gazebo/resource_paths/add
/gazebo/resource_paths/get
/gazebo/worlds
/gui/camera/view_control
/gui/follow
...

python/gz_service_info.py is a Python version of the command gz service -i -s /gazebo/worlds

$ ./python/gz_service_info.py -s /gazebo/worlds
Service providers [Address, Request Message Type, Response Message Type]:
  tcp://127.0.0.1:63657, gz.msgs.Empty, gz.msgs.StringMsg_V

Building with Bazel

On macOS the project may also be built with Bazel. This is experimental and depends upon a modified version of the Bazel build rules for Gazebo in the gz-bazel project.

Installing Bazel

On macOS Bazel can be installed with brew:

brew install bazel

The Google protocol buffers compiler version 3.19 is also required and can be installed with:

brew install protobuf

Setting up the workspace

Make a directory to contain this package and all the Gazebo packages and dependencies:

mkdir ~/gz/
cd ~/gz/

Clone each of the packages using the bazel.repos file and vcstool. The forked version of gz-bazel contains modified build rules and repo references for macOS.

wget https://raw.githubusercontent.com/srmainwaring/gz-bazel/bazel-macos/example/bazel.repos
vcs import . < bazel.repos

Next, symlink the following files into the workspace directory:

cd ~/gz
ln -sf ./gz_bazel/example/WORKSPACE.example ./WORKSPACE
ln -sf ./gz_bazel/example/BUILD.example ./BUILD.bazel
ln -sf ./gz_bazel/example/bazelrc.example ./.bazelrc
ln -sf ./gz_python/gz-msgs9.BUILD ./gz-msgs9.BUILD

Finally, pybind11_protobuf requires a patch to the protobuf archive which must be available in the subdirectory ~/gz/external. It must be copied rather than symlinked:

cd ~/gz
mkdir external
cp ./gz_python/external/com_google_protobuf_build.patch external

Build with Bazel

Currently our use of pybind11_protobuf requires the Python implementation of the Python protobuf generator. This is configured with the environment variable:

export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python

Then build the examples with:

bazel build //gz_python:all
bazel build //gz_python/python:all

Note: on macOS not all Gazebo projects will build with Bazel, so the command:

bazel build //... 

will result in a number of errors.

Usage in Bazel builds

The Bazel build file gz-msgs9.BUILD defines targets for a selection of messages. For example the targets for proto/gz/msgs/time.proto are:

# proto_library
@gz-msgs9//:time_proto

# cpp_proto_library
@gz-msgs9//:time_cc_proto

# python_proto_library
@gz-msgs9//:time_py_pb2

To use the message bindings for C++ targets:

# BUILD.bazel

cc_binary(
  name = "main",
  srcs = ["main.cc"],
  deps = [
      "@gz-msgs9//:time_cc_proto",
  ],
)

To use the bindings for Python targets:

# BUILD.bazel

py_binary(
  name = "example",
  srcs = ["example.py"],
  data = [
    "@com_google_protobuf//:proto_api",
  ],
  deps = [
    "@gz-msgs9//:time_py_pb2",
  ],
)

Notes

Protobuf compiler version

The project depends on protoc version 3.19.1. You must ensure that the version of protoc installed by brew matches (otherwise the examples will segfault).

$ protoc --version
libprotoc 3.19.1

Protobuf generated Python libraries

The brew installation of protobuf defaults to use the C++ implementation of the generated Python protobuf library. When using this with pybind11_protobuf there are some cases when C++ wrapped protobuf objects returned from an extension module are not recognised as their Python equivalents. The Python implementation will work in these cases and to enable this set the following environment variable before building the project:

export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python

CI Testing

Repos used to build Gazebo on macOS with Bazel

The table summarises the dependencies (repo and branch) on the Gazebo libraries.

library repo branch build test
gz_python https://github.com/srmainwaring/gz-python main pass -
gz-bazel https://github.com/srmainwaring/gz-bazel bazel-macos pass pass
gz-math https://github.com/srmainwaring/gz-math bazel-macos/gz-math6 pass pass
gz-utils https://github.com/srmainwaring/gz-utils bazel-macos/gz-utils1 pass pass
gz-common https://github.com/srmainwaring/gz-common bazel-macos/gz-common4 pass pass
gz-msgs https://github.com/srmainwaring/gz-msgs bazel-macos/gz-msgs9 pass pass
sdformat https://github.com/gazebosim/sdformat bazel-sdf10 pass pass
gz-plugin https://github.com/srmainwaring/gz-plugin bazel-macos/gz-plugin1 pass pass
gz-transport https://github.com/srmainwaring/gz-transport bazel-macos/gz-transport12 pass 1 fail
gz-physics https://github.com/srmainwaring/gz-physics bazel-macos/gz-physics5 pass pass
gz-tools https://github.com/srmainwaring/gz-tools bazel-macos/gz-tools1 partial -
gz-rendering https://github.com/gazebosim/gz-rendering bazel-rendering4 fail fail
gz-gui https://github.com/gazebosim/gz-gui bazel-gui4 fail fail
gz-sensors https://github.com/gazebosim/gz-sensors bazel-sensors4 fail fail
gz-gazebo https://github.com/gazebosim/gz-gazebo bazel-gazebo4 fail fail