This tutorial is created by Rosbotics Ambassador 010 Ernest
Rosbotics Ambassador Program https://www.theconstruct.ai/rosbotics-ambassador/)
What we are going to learn:
- How to set up the package architecture to accommodate C++ and Python in the same package
- How to configure this package (by modifying
package.xml
andCMakeLists.txt
) - How to compile and run Python and C++ nodes from this package
List of resources used in this post:
- Use the rosject: https://app.theconstructsim.com/l/5e01d324/
- Question from the Robotics StackExchange that is answered in this post: https://robotics.stackexchange.com/questions/88149/ros2-c-and-python-in-same-package-is-it-possible
- Similar tutorial that this is based on: https://roboticsbackend.com/ros2-package-for-both-python-and-cpp-nodes/#ROS2_Package_architecture_with_both_Python_and_Cpp_nodes_–_final
Overview
- Is it possible to have Python and C++ node in the same package? Yes! In this tutorial I’ll show you how to create and configure your Python and Cpp nodes in the same package.
- You should ideally be familiar with how to create a standard C++ package because the method I’ll use is essentially creating a standard C++ package with additional setup and configurations to include Python libraries and nodes.
- I’ll be using The Construct’s ROS development studio and doing it in a new ROS2 Humble workspace.
Setup the package architecture
Create a standard C++ package
With dependencies on rclpy and rclcpp:
With dependencies on rclpy
:
cd ~/ros2_ws/src/
ros2 pkg create cpp_py_pkg –build-type ament_cmake –dependencies rclpy rclcpp
Run the “tree
.” command to see the folder structure. If you don’t have the command installed, you can install it using:
sudo apt-get update
sudo apt-get install -y tree
You should be able to see this structure which you are already familiar with:
cpp_py_pkg/
├── CMakeLists.txt
├── include
│ └── cpp_py_pkg
├── package.xml
└── src
Add a C++ node and header
cd cpp_py_pkg/
touch src/cpp_node.cpp
touch include/cpp_py_pkg/cpp_header.hpp
In order to compile the package later, we need at least a main function in the C++ node. For this tutorial for simplicity, we can just add this minimal code to the cpp_node.cpp
file:
#include “rclcpp/rclcpp.hpp”// Include your header file to use it#include “cpp_py_pkg/cpp_header.hpp”int main(int argc, char **argv){// Initiate ROS communicationsrclcpp::init(argc, argv);// Instantiate the nodeauto node = std::make_shared<rclcpp::Node>(“my_node_name”);// Make the node spinrclcpp::spin(node);// Shutdown ROS communicationsrclcpp::shutdown();return 0;}
Add a Python node and module to import
For Python, we need to create additional folders first:
mkdir cpp_py_pkg
touch cpp_py_pkg/__init__.py
mkdir scripts
touch cpp_py_pkg/module_to_import.py
touch scripts/py_node.py
You have to add a shebang line first thing in the py_node.py
file otherwise you will get an error when trying to run the node:
#!/usr/bin/env python3import rclpyfrom rclpy.node import Node# Import a specific function/class from your module# from cpp_py_pkg.module_to_import import …def main(args=None):# Initiate ROS communicationsrclpy.init(args=args)# Instantiate the nodenode = Node(‘my_node_name’)# Make the node spinrclpy.spin(node)# Shutdown ROS communicationsrclpy.shutdown()if __name__ == ‘__main__’:main()
Final package architecture
Run the “tree .” command to see the folder structure. I have added additional comments and files to make it clearer:
cpp_py_pkg/
# –> package info, configuration, and compilation
├── CMakeLists.txt
├── package.xml
# Python stuff
# –> empty init file & any python library or module files we want to import
├── cpp_py_pkg
│ ├── __init__.py
│ └── module_to_import.py
│ └── another_module_to_import.py
# –> python executables/nodes
├── scripts
│ └── py_node.py
# Cpp stuff
# –> cpp header files
├── include
│ └── cpp_py_pkg
│ └── cpp_header.hpp
│ └── another_cpp_header.hpp
# –> cpp executables/nodes
└── src
└── cpp_node.cpp
The CMakeLists.txt
and package.xml
will be shared by Python and C++, which is what we will edit in the next section to configure the package for both Python and C++.
Configure the package
package.xml
Add a buildtool_depend
tag for ament_cmake_python
:
<buildtool_depend>ament_cmake_python</buildtool_depend>
So your package.xml
should look like this:
<?xml version=”1.0″?>
<?xml-model href=”http://download.ros.org/schema/package_format3.xsd” schematypens=”http://www.w3.org/2001/XMLSchema”?>
<package format=”3″>
<name>cpp_py_pkg</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email=”user@todo.todo”>user</maintainer>
<license>TODO: License declaration</license><buildtool_depend>ament_cmake</buildtool_depend>
<buildtool_depend>ament_cmake_python</buildtool_depend><depend>rclpy</depend>
<depend>rclcpp</depend><test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend><export>
<build_type>ament_cmake</build_type>
</export>
</package>
cd ~/ros2_ws/
colcon build –packages-select py_pkg
CMakeLists.txt
Add this external dependency:
find_package(ament_cmake_python REQUIRED)
Add this for C++:
# Include Cpp “include” directory
include_directories(include)# Create Cpp executable and link with dependencies
add_executable(cpp_executable src/cpp_node.cpp)
ament_target_dependencies(cpp_executable rclcpp)# Install Cpp executables in the ros2_ws/install/cpp_py_pkg/lib/cpp_py_pkg/ folder
install(TARGETS
cpp_executable
DESTINATION lib/${PROJECT_NAME}
)
Add this for Python:
# Install Python modules
ament_python_install_package(${PROJECT_NAME})# Install Python executables in the ros2_ws/install/cpp_py_pkg/lib/cpp_py_pkg/ folder
install(PROGRAMS
scripts/py_node.py
DESTINATION lib/${PROJECT_NAME}
)
So your CMakeLists.txt
should look like this:
source ~/.bashrc # . ~/.bashrc
So your CMakeLists.txt should look like this:
cmake_minimum_required(VERSION 3.8)
project(cpp_py_pkg)if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES “Clang”)
add_compile_options(-Wall -Wextra -Wpedantic)
endif()# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclpy REQUIRED)
find_package(rclcpp REQUIRED)
find_package(ament_cmake_python REQUIRED)# Include Cpp “include” directory
include_directories(include)# Create Cpp executable and link with dependencies
add_executable(cpp_executable src/cpp_node.cpp)
ament_target_dependencies(cpp_executable rclcpp)# Install Cpp executables in the ros2_ws/install/cpp_py_pkg/lib/cpp_py_pkg/ folder
install(TARGETS
cpp_executable
DESTINATION lib/${PROJECT_NAME}
)# Install Python modules
ament_python_install_package(${PROJECT_NAME})# Install Python executables in the ros2_ws/install/cpp_py_pkg/lib/cpp_py_pkg/ folder
install(PROGRAMS
scripts/py_node.py
DESTINATION lib/${PROJECT_NAME}
)if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()ament_package()
Compile package, then run C++ node and Python node
cd ~/ros2_ws/
colcon build –packages-select cpp_py_pkg
Source ROS2 environment and start C++ node in one terminal:
source ~/ros2_ws/install/setup.bash
ros2 run cpp_py_pkg cpp_executable
Source ROS2 environment and start Python node in another terminal:
source ~/ros2_ws/install/setup.bash
ros2 run cpp_py_pkg py_node.py
That’s it! If there are no errors when you run the nodes then it means that it was successful (remember that our nodes do not do anything except spin).
0 Comments