Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python Bindings for Publisher, Subscriber and Service Request features. #411

Merged
merged 39 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
6937a73
Creates base structure for bindings
Voldivh May 18, 2023
27fe57b
Adds bindings and python module
Voldivh May 30, 2023
7682d37
Adds more bindings and modifies the package structure
Voldivh Jun 1, 2023
21393d2
Make a wrapper for Node::Publisher instead of inheriting from it
azeey Jun 1, 2023
8e88079
Adds bindings and wrapper for subscription methods
Voldivh Jun 5, 2023
833b382
Use message types in `advertise` and `subscribe` signatures
azeey Jun 7, 2023
183be20
Use copy constructor of Publisher
azeey Jun 7, 2023
62b4052
Fix compiler warning, use more specific headers
azeey Jun 7, 2023
7f29e5b
Adds the request_raw bindings
Voldivh Jun 14, 2023
8d64f78
Adds integration test for python bindings
Voldivh Jun 19, 2023
9b281c1
Adds empty line at the end of the gitignore file
Voldivh Jun 20, 2023
03d5984
Adds empty line at the end of the gitignore file
Voldivh Jun 20, 2023
9478c5c
Merge branch 'main' into voldivh/pybind11_bindings
ahcorde Jun 21, 2023
acff6ea
Adds license and addresses code format
Voldivh Jun 22, 2023
05838d6
Adds requester test and fixes license year
Voldivh Jun 23, 2023
b34544c
Removes comment
Voldivh Jun 23, 2023
5402433
Adds the pybind11 dependency to the packages.apt file
Voldivh Jun 23, 2023
830ae5f
Adds other python dependencies
Voldivh Jun 23, 2023
d12a0e4
Adds other python dependencies
Voldivh Jun 23, 2023
9b45618
Adds documentation, removes unnecesary binding argument and skip bind…
Voldivh Jun 26, 2023
a9d660e
Corrects format on python files, create python type hints to methods,…
Voldivh Jun 26, 2023
d4a7119
Adds a new constructor to the node and create bindings for the NodeOp…
Voldivh Jun 27, 2023
684c02b
Adds bindings for Options, EnableStats and TopicStats of the Node class
Voldivh Jun 27, 2023
5756147
Adds a threading lock to the test to avoid data races
Voldivh Jun 27, 2023
2d3c294
Fixes style and add more documentation
Voldivh Jun 28, 2023
d408d94
Adds docstring to python wrapper
Voldivh Jun 29, 2023
77b76ec
Adds more test cases to pubSub
Voldivh Jun 30, 2023
f88ee99
Adds test for rest of bindings
Voldivh Jun 30, 2023
498dd05
Adds MessagePublisher and ServicePublisher bindings for info methods
Voldivh Jul 3, 2023
2f23947
Adds an example using a gz-sim world
Voldivh Jul 5, 2023
aafd3bd
Fixes different tests
Voldivh Jul 6, 2023
9552d5e
Merge branch 'gazebosim:gz-transport12' into voldivh/pybind11_bindings
Voldivh Jul 12, 2023
0e3a5b3
Adds data race examples and modifies docstrings
Voldivh Jul 12, 2023
99531f2
Merge branch 'voldivh/pybind11_bindings' of github.com:Voldivh/gz-tra…
Voldivh Jul 12, 2023
703c9a9
Modifies test name and adds comments on topic stats test
Voldivh Jul 14, 2023
1bfc4fd
Merge branch 'main' into voldivh/pybind11_bindings
Voldivh Jul 17, 2023
e1bca5e
Add python3-gz-msgs10 package dependency
azeey Jul 19, 2023
3bfb365
Adds file for python module generation on CI
Voldivh Jul 20, 2023
5371b41
Rename after_cmake.sh to after_make.sh
azeey Jul 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/ci/packages.apt
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,9 @@ libsqlite3-dev
libzmq3-dev
pkg-config
protobuf-compiler
python3-dev
python3-distutils
python3-psutil
python3-pybind11
python3-pytest
uuid-dev
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ build_*
# OS generated files
.DS_Store
*.swp

# Python generated files
*.pyc
49 changes: 49 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,22 @@ set(GZ_CMAKE_VER ${gz-cmake3_VERSION_MAJOR})
#============================================================================
# Configure the project
#============================================================================
set(skip_pybind11_default_value OFF)
option(SKIP_PYBIND11
"Skip generating Python bindings via pybind11"
${skip_pybind11_default_value})

# Python interfaces vars
include(CMakeDependentOption)
include(GzPython)
cmake_dependent_option(USE_SYSTEM_PATHS_FOR_PYTHON_INSTALLATION
"Install python modules in standard system paths in the system"
OFF "NOT SKIP_PYBIND11" OFF)

cmake_dependent_option(USE_DIST_PACKAGES_FOR_PYTHON
"Use dist-packages instead of site-package to install python modules"
OFF "NOT SKIP_PYBIND11" OFF)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

Expand Down Expand Up @@ -72,6 +88,7 @@ gz_find_package(CPPZMQ REQUIRED PRIVATE
#--------------------------------------
# Find uuid
if (MSVC)
set(skip_pybind11_default_value ON)
message (STATUS "UUID: Using Windows RPC UuidCreate function\n")
else()
gz_find_package(UUID REQUIRED)
Expand Down Expand Up @@ -115,6 +132,31 @@ gz_find_package(SQLite3
#============================================================================
# Configure the build
#============================================================================

########################################
# Python interfaces
if (NOT PYTHON3_FOUND)
azeey marked this conversation as resolved.
Show resolved Hide resolved
GZ_BUILD_WARNING("Python is missing: Python interfaces are disabled.")
message (STATUS "Searching for Python - not found.")
else()
message (STATUS "Searching for Python - found version ${Python3_VERSION}.")

if (SKIP_PYBIND11)
message(STATUS "SKIP_PYBIND11 set - disabling python bindings")
else()
set(PYBIND11_PYTHON_VERSION 3)
find_package(pybind11 2.4 QUIET)

if (${pybind11_FOUND})
find_package(Python3 ${GZ_PYTHON_VERSION} REQUIRED COMPONENTS Development)
message (STATUS "Searching for pybind11 - found version ${pybind11_VERSION}.")
else()
GZ_BUILD_WARNING("pybind11 is missing: Python interfaces are disabled.")
message (STATUS "Searching for pybind11 - not found.")
endif()
endif()
endif()

gz_configure_build(QUIT_IF_BUILD_ERRORS
COMPONENTS log parameters)

azeey marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -123,6 +165,13 @@ gz_configure_build(QUIT_IF_BUILD_ERRORS
#============================================================================
add_subdirectory(conf)

#============================================================================
# gz transport python bindings
#============================================================================
if (pybind11_FOUND AND NOT SKIP_PYBIND11)
add_subdirectory(python)
endif()

#============================================================================
# Create package information
#============================================================================
Expand Down
97 changes: 97 additions & 0 deletions python/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
if(WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug")
# pybind11 logic for setting up a debug build when both a debug and release
# python interpreter are present in the system seems to be pretty much broken.
# This works around the issue.
set(PYTHON_LIBRARIES "${PYTHON_DEBUG_LIBRARIES}")
endif()

if(USE_SYSTEM_PATHS_FOR_PYTHON_INSTALLATION)
if(${CMAKE_VERSION} VERSION_LESS "3.12.0")
execute_process(
COMMAND "${PYTHON_EXECUTABLE}" -c "if True:
from distutils import sysconfig as sc
print(sc.get_python_lib(plat_specific=True))"
OUTPUT_VARIABLE Python3_SITEARCH
OUTPUT_STRIP_TRAILING_WHITESPACE)
else()
# Get install variable from Python3 module
# Python3_SITEARCH is available from 3.12 on, workaround if needed:
find_package(Python3 COMPONENTS Interpreter)
endif()

if(USE_DIST_PACKAGES_FOR_PYTHON)
string(REPLACE "site-packages" "dist-packages" GZ_PYTHON_INSTALL_PATH ${Python3_SITEARCH})
else()
# custom cmake command is returning dist-packages
string(REPLACE "dist-packages" "site-packages" GZ_PYTHON_INSTALL_PATH ${Python3_SITEARCH})
endif()
else()
# If not a system installation, respect local paths
set(GZ_PYTHON_INSTALL_PATH ${GZ_LIB_INSTALL_DIR}/python/gz)
endif()

# Set the build location and install location for a CPython extension
function(configure_build_install_location _library_name)
# Install library for actual use
install(TARGETS ${_library_name}
DESTINATION "${GZ_PYTHON_INSTALL_PATH}/transport${PROJECT_VERSION_MAJOR}"
)
endfunction()

message(STATUS "Building pybind11 interfaces")
# The bindings require a python wrapper in order to be able to provide an API
# similar to the one used in C++. We are creating the bindings name as the
# following in order to avoid having to modify the import on wrapper on each
# version of gz-transport.
set(BINDINGS_MODULE_NAME "_transport")
azeey marked this conversation as resolved.
Show resolved Hide resolved
# Split from main extension and converted to pybind11
pybind11_add_module(${BINDINGS_MODULE_NAME} MODULE
src/transport/_gz_transport_pybind11.cc
)

target_link_libraries(${BINDINGS_MODULE_NAME} PRIVATE
${PROJECT_LIBRARY_TARGET_NAME}
)

target_compile_definitions(${BINDINGS_MODULE_NAME} PRIVATE
BINDINGS_MODULE_NAME=${BINDINGS_MODULE_NAME})

configure_build_install_location(${BINDINGS_MODULE_NAME})

install(FILES
src/__init__.py
DESTINATION "${GZ_PYTHON_INSTALL_PATH}/transport${PROJECT_VERSION_MAJOR}"
)

if (BUILD_TESTING AND NOT WIN32)
set(python_tests
pubSub_TEST
requester_TEST
options_TEST
)
execute_process(COMMAND "${Python3_EXECUTABLE}" -m pytest --version
OUTPUT_VARIABLE PYTEST_output
ERROR_VARIABLE PYTEST_error
RESULT_VARIABLE PYTEST_result)
if(${PYTEST_result} EQUAL 0)
set(pytest_FOUND TRUE)
else()
message(WARNING "Pytest package not available: ${PYTEST_error}")
endif()

foreach (test ${python_tests})
if (pytest_FOUND)
add_test(NAME ${test}.py COMMAND
"${Python3_EXECUTABLE}" -m pytest "${CMAKE_SOURCE_DIR}/python/test/${test}.py" --junitxml "${CMAKE_BINARY_DIR}/test_results/${test}.xml")
else()
add_test(NAME ${test}.py COMMAND
"${Python3_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/python/test/${test}.py")
endif()
set(_env_vars)
list(APPEND _env_vars "CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR}/bin")
list(APPEND _env_vars "PYTHONPATH=${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/python/:${CMAKE_BINARY_DIR}/lib:$ENV{PYTHONPATH}")
azeey marked this conversation as resolved.
Show resolved Hide resolved
list(APPEND _env_vars "LD_LIBRARY_PATH=${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}:$ENV{LD_LIBRARY_PATH}")
set_tests_properties(${test}.py PROPERTIES
ENVIRONMENT "${_env_vars}")
endforeach()
endif()
92 changes: 92 additions & 0 deletions python/examples/multi_lrauv_race.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#
# Copyright (C) 2023 Open Source Robotics Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
#
# In each iteration, for each vehicle, generate a random fin angle and thrust
# within reasonable limits, and send the command to the vehicle.
#
# Usage:
# $ python3 multi_lrauv_race
Voldivh marked this conversation as resolved.
Show resolved Hide resolved
#

from gz.msgs10.double_pb2 import Double
from gz.transport13 import Node

import random
import time


# Find joint limits from tethys model.sdf
def random_angle_within_limits(min=-0.261799, max=0.261799):
return random.uniform(min, max)


# Nominal speed is thruster 300 rpm ~ 31.4 radians per second ~ 6.14 Newtons
def random_thrust_within_limits(min=-6.14, max=6.14):
return random.uniform(min, max)


def main():
# Set up node
node = Node()

# Set up publishers
ns = ["tethys", "triton", "daphne"]
rudder_pubs = [
node.advertise(
"/model/" + name + "/joint/vertical_fins_joint/0/cmd_pos", Double
)
for name in ns
]
propeller_pubs = [
node.advertise("/model/" + name + "/joint/propeller_joint/cmd_thrust", Double)
for name in ns
]

artificial_speedup = 1

# Set up messages
rudder_msg = Double()
propeller_msg = Double()

try:
while True:
for rudder_pub, propelled_pub, name in zip(rudder_pubs, propeller_pubs, ns):
rudder_msg.data = random_angle_within_limits(-0.01, 0.01)
rudder_pub.publish(rudder_msg)
propeller_msg.data = random_thrust_within_limits(
-6.14 * artificial_speedup, 0
)
propelled_pub.publish(propeller_msg)
print(
"Commanding: "
+ name
+ " ruddder angle "
+ str(round(rudder_msg.data, 4))
+ " rad, thrust "
+ str(round(propeller_msg.data, 2))
+ " Newtons."
)
print(
"----------------------------------------------------------------------"
)
time.sleep(0.5)
except KeyboardInterrupt:
print("\nProcess terminated")


if __name__ == "__main__":
main()
54 changes: 54 additions & 0 deletions python/examples/publisher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Copyright (C) 2023 Open Source Robotics Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

from gz.msgs10.stringmsg_pb2 import StringMsg
ahcorde marked this conversation as resolved.
Show resolved Hide resolved
from gz.msgs10.vector3d_pb2 import Vector3d
from gz.transport13 import AdvertiseMessageOptions
from gz.transport13 import Node

import time

def main():
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)

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) or pub_vector3d.publish(vector3d_msg)):
break

print("Publishing 'Hello' on topic [{}]".format(stringmsg_topic))
print("Publishing a Vector3d on topic [{}]".format(vector3d_topic))
time.sleep(0.1)

except KeyboardInterrupt:
pass


if __name__ == "__main__":
main()
31 changes: 31 additions & 0 deletions python/examples/requester.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Copyright (C) 2023 Open Source Robotics Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

from gz.msgs10.stringmsg_pb2 import StringMsg
ahcorde marked this conversation as resolved.
Show resolved Hide resolved
from gz.transport13 import Node

def main():
node = Node()
service_name = "/echo"
request = StringMsg()
request.data = "Hello world"
response = StringMsg()
timeout = 5000

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

if __name__ == "__main__":
main()
Loading