!IMPORTANT!
Note: If you come from ROSCON UK and are aiming to try this, I need a few days to clean up the repo and make it ready to use as you saw it in the presentation.
ROS 2 package providing rclcpp bindings via cppyy and examples on how to use cppyy in ROS2.
- Tired of writing python wrappers for your C++ code?
- Missing features from C++ APIs that you'd like to call in Python?
- Do you like to prototype and test in Python but you use a lot of C++ code?
cppyy can help you! Cppyy is a Python-C++ bindings library that provides automatic, runtime-based access to C++ code from Python using reflection and just-in-time compilation. It enables seamless interoperability between the two languages, allowing Python to call C++ functions and manipulate C++ objects directly.
This repository aims to expose useful ROS2 C++ (and related) APIs via automatic wrapping with cppyy.
For example you will be able to:
- Transparently use
rclcpp's- Node
- Publisher
- Subscriber
- Timer
- Messages (without converting Python<>C++, always working on the C++ representation!)
- init/spin/shutdown
- Loaned messages (TODO!)
To automatically replace your rclpy with rclcpp classes/methods just place at the top of your Python file:
import rclcppyy; rclcppyy.enable_cpp_acceleration()
# Rest of your code doing import rclpy, from sensor_msgs.msg import Image... etcTo get an idea of how working with cppyy (without the quality of life features of rclcppyy) the code looks like (excerpt from an example):
import cppyy
# include/import your stuff and then...
if not cppyy.gbl.rclcpp.ok():
cppyy.gbl.rclcpp.init(len(sys.argv), sys.argv)
# Some code in a class...
self.node = cppyy.gbl.rclcpp.Node("node_exmaple")
self.publisher = self.node.create_publisher[cppyy.gbl.std_msgs.msg.String](
"pub_topic", 10)
# Define the callback wrapper with proper Python.h include
cppyy.cppdef("""
#include <Python.h>
#include <functional>
static std::function<void()> create_timer_callback(PyObject* self) {
return [self]() {
if (self && PyObject_HasAttrString(self, "timer_callback")) {
PyObject_CallMethod(self, "timer_callback", nullptr);
}
};
}
""")
callback = cppyy.gbl.create_timer_callback(self)
self.timer = self.node.create_wall_timer(
cppyy.gbl.std.chrono.nanoseconds(10000),
callback)
self.start_time = cppyy.gbl.std.chrono.steady_clock.now()-
Benchmarks (ran on a Intel® Core™ Ultra 7 165H × 22 on "Performance" mode on Ubuntu 24.04)
-
Running a publisher and a subscriber (small
std_msgs/String) at 1khz
rclpyuses 15~% CPU for the publisher, and 18~% CPU for the subscriberrclcppyyuses 4~% CPU for the publisher, and 4~% CPU for the subscriber
-
Running a publisher and a subscriber (small
std_msgs/String) at 10khz
rclpyuses 86~% CPU for the publisher, and 88~% CPU for the subscriberrclcppyyuses 26~% CPU for the publisher, and 22~% CPU for the subscriber
-
-
The
publisher_member_function.pyWriting a simple publisher and subscriber (Python) tutorial usingrclcppyyas backend. scripts/ros_tutorials/publisher_member_function.py. -
Note that the
rclpypublisher benchmark bench_pub_rclpy.py can be switched to therclcppyybackend by uncommenting theenable_cpp_accelerationline at the top.
Easiest way to test by yourself is using a Pixi workspace. TODO: make a repo with the Pixi workspace ready to use, with this repo as a git submodule.
For now one can take this pixi.toml:
[workspace]
authors = ["Sam Pfeiffer <sammypfeiffer@gmail.com>"]
channels = ["conda-forge", "robostack-jazzy"]
name = "rclcppyy_ws"
platforms = ["linux-64"]
version = "0.1.0"
[tasks]
[dependencies]
ros-jazzy-ros-base = ">=0.11.0,<0.12"
opencv = ">=4.11.0,<5"
[activation]
# Comment out this line on first build
scripts = ["fix_cppyy_api_path.sh"]
# To silence warning: non-portable path to file '"cpycppyy/API.h"'; specified path differs in case from file name on disk [-Wnonportable-include-path]
# From patching the wrong path due to capitalisation from the current cppyy packaged version
env = { EXTRA_CLING_ARGS = "-Wno-nonportable-include-path" }
[pypi-dependencies]
cppyy = ">=3.5.0, <4"Workaround needed: Comment out the scripts = ["fix_cppyy_api_path.sh"] line on the first build as it will fail otherwise. There's an issue with cppyy packaging and the capitalisation of the cpycppyy directory.
And do:
# If you haven't installed pixi:
curl -fsSL https://pixi.sh/install.sh | sh
source ~/.bashrc
# Clone this repo
mkdir -p rclcppyy_ws/src
cd rclcppyy_ws/src
git clone https://github.com/awesomebytes/rclcppyy
# workaround...
cp rclcppyy/fix_cppyy_api_path.sh ..
cd ..
# Copy the `pixi.toml` here and (apply the workaround mentioned above)
pixi install
# Undo the workaround done before, e.g. uncomment the line
pixi shell
# You'll be in a shell with the environment fully ready after a few secondsOnce in the pixi workspace or in a system with all dependencies...
cd /path/to/workspace
colcon build --packages-select rclcppyy
source install/setup.bash# Default is fastrtps, which has random latency issues, and big messages don't pass through
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
export ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST
# One on each shell probably
ros2 run rclcppyy bench_pub_rclpy.py 10000
ros2 run rclcppyy bench_sub_rclpy.py
ros2 run rclcppyy bench_pub_rclcppyy_monkeypatched.py 10000
ros2 run rclcppyy bench_sub_rclcppyy_monkeypatched.py
# Monitor with
# top -c -p $(pgrep -d, -f bench_)
# Or the tutorial example
ros2 run rclcppyy publisher_member_function.py[x] Benchmark pub/sub
[x] Get rclpy tutorials code to run with rclcppyy backend.
[x] Monkeypatch/substitute rclpy with rclcppyy and make your Python nodes use less CPU!
[x] Monkeypatch/substitute rclpy messages for rclcpp messages (so to avoid conversions).
[ ] (WIP) Generate stubs to get IDE autocompletion.
[ ] (WIP) Demo images (these big images ones should be done with loaned or zero-cost copy messages).
[x] Demo use python testing packages with C++.
[x] Demo use python CLI generator with C++.
[ ] Demo use C++ Markers classes from Python.
[ ] Demo use zero-copy torch.
[x] (WIP) Demo use C++ rosbag reader (to C++ messages).
[x] Demo pointclouds.
[ ] Demo Nav2.
[ ] Demo Moveit2.
[ ] Demo ROS control.
[ ] Separate into different packages the base rclcppyy and other demos/reusable pieces.
- Bring down the bringup of rclcppyy time (currently 2.5s~) by figuring out how to build a
.pcm+.sodictionary that is pre-compiled (or at least compiled just once per machine)
