Different Files in Python vs. C++ ROS 2 Packages: Why the Split?

Python and C++ are both frequently used for creating robotic software in the ROS 2 (Robot Operating System) space. Nonetheless, there are notable differences in the ROS 2 package structure between these two languages. These distinctions may seem needless or confusing at first. For example, why does a Python package use a setup.py, but a C++ ROS 2 package needs a CMakeLists.txt?

A closer examination of each language’s fundamental characteristics, build systems, and roles within the ROS 2 ecosystem is necessary to comprehend the causes of these variations.

Part 1: The Anatomy of a C++ ROS 2 Package Structure (The Compiled World)

C++ is a compiled language, which means your source code must be converted into machine-readable code before execution. This step is done by a compiler. The structure of a C++ ROS 2 package is built around this compilation process, with multiple files and directories that support the build system. Here’s an overview of the key components and their roles.

The Jargon: Key Terms in C++ ROS 2

  • Source Code: The .cpp files containing your logic and function implementations.
  • Header Files: Files with .hpp or .h extensions containing declarations and class definitions, acting as the “public API” of your code.
  • Compiler: A program (such as g++) that compiles your source code into object files.
  • Linker: A program that connects the compiled object files and required libraries to generate an executable.
  • Build System (CMake): Manages the compile and link process. You do not manually type in g++ commands; instead, the instructions are written in CMakeLists.txt.
  • Target: An entity (executable or library) that the build system will generate.

File Breakdown

  1. package.xml
    • Purpose: This file serves as the “ID card” of the package. It defines essential metadata such as the package name, version, author, and dependencies.
    • Key Line: <build_type>ament_cmake</build_type>. This line tells ROS 2 to use CMake for building the package.
  2. CMakeLists.txt
    • Purpose: This is the heart of the build system, where all compilation and linking instructions are specified.
    • Key Commands:
      • find_package(rclcpp REQUIRED): Tells CMake to locate the rclcpp library, essential for ROS 2 C++ development.
      • add_executable(my_node src/my_node.cpp): This command creates an executable target named my_node from the source file src/my_node.cpp.
      • ament_target_dependencies(my_node rclcpp): Links the executable to the necessary ROS 2 libraries.
      • install(TARGETS my_node DESTINATION lib/${PROJECT_NAME}): Ensures that the generated executable is placed in the appropriate directory for ROS to run it.
  3. src/: This directory contains the source files (.cpp) for the node.
  4. include/package_name/: This directory holds header files (.hpp), which provide the interface for other parts of the code to interact with the node.

Writing and Building a C++ Node

A C++ ROS 2 node typically involves creating a class that inherits from the rclcpp::Node class. Below is an example C++ node that publishes messages to a topic.

cpp

#include "rclcpp/rclcpp.hpp"

#include "std_msgs/msg/string.hpp"

class MyCppNode : public rclcpp::Node

{

public:

    MyCppNode() : Node("cpp_talker_node")

    {

        publisher_ = this->create_publisher<std_msgs::msg::String>("my_topic", 10);

        timer_ = this->create_wall_timer(

            std::chrono::seconds(1),

            std::bind(&MyCppNode::timer_callback, this));

        RCLCPP_INFO(this->get_logger(), "C++ Talker Node has been started.");

    }

private:

    void timer_callback()

    {

        auto msg = std_msgs::msg::String();

        msg.data = "Hello from C++ Node!";

        publisher_->publish(msg);

    }

    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;

    rclcpp::TimerBase::SharedPtr timer_;

};

int main(int argc, char * argv[])

{

    rclcpp::init(argc, argv);

    rclcpp::spin(std::make_shared<MyCppNode>());

    rclcpp::shutdown();

    return 0;

}

Next, modify CMakeLists.txt to instruct the build system on how to compile the node:

cmake

find_package(rclcpp REQUIRED)

find_package(std_msgs REQUIRED)

add_executable(cpp_talker src/my_cpp_node.cpp)

ament_target_dependencies(cpp_talker rclcpp std_msgs)

install(TARGETS cpp_talker DESTINATION lib/${PROJECT_NAME})

Finally, build the package using colcon build.

Part 2: The Anatomy of a Python Package (The Interpreted World)

Python, in contrast to C++, is an interpreted language. This means that Python code is executed directly by the Python interpreter without needing to compile it beforehand. As such, the structure of a Python ROS 2 package is simpler and designed to facilitate direct execution of the Python scripts.

The Jargon: Key Terms in Python ROS 2

  • Interpreter: The program (python3) that reads and executes Python code.
  • Script: A .py file that contains executable code.
  • Module: A Python file designed to be imported by other scripts.
  • Package: A directory containing Python modules, along with an init.py file to mark it as a package.
  • Entry Point: A reference in setup.py that designates a function to be executed when the script is run.

File Breakdown

  1. package.xml
    • Purpose: Just like in C++, it serves as the “ID card” of the Python package.
    • Key Line: <build_type>ament_python</build_type>. This line tells ROS 2 to use the Python-specific build and install process.
  2. setup.py
    • Purpose: This file contains the installation instructions for the Python package. There is no compilation involved here.

Key Section:

python

entry_points={

    'console_scripts': [

        'py_talker = my_python_package.my_py_node:main',

    ],

}
  •  This section tells ROS 2 to create a script named py_talker, and when executed, it will call the main function in my_py_node.py.
  1. setup.cfg: A simple configuration file to tell the build tools where to find the Python files declared in setup.py.
  2. package_name/: This directory holds the Python package’s modules and other Python files (.py).

Writing and “Building” a Python Node

Here’s an example of a basic Python node that publishes a message to a topic:

python

import rclpy

from rclpy.node import Node

from std_msgs.msg import String

class MyPyNode(Node):

    def __init__(self):

        super().__init__('py_talker_node')

        self.publisher_ = self.create_publisher(String, 'my_topic', 10)

        self.timer_ = self.create_timer(1.0, self.timer_callback)

        self.get_logger().info("Python Talker Node has been started.")

    def timer_callback(self):

        msg = String()

        msg.data = "Hello from Python Node!"

        self.publisher_.publish(msg)

def main(args=None):

    rclpy.init(args=args)

    node = MyPyNode()

    rclpy.spin(node)

    node.destroy_node()

    rclpy.shutdown()

if __name__ == '__main__':

    main()

In setup.py, define the entry point:

python

entry_points={

    'console_scripts': [

        'py_talker = my_python_package.my_py_node:main',

    ],

}

Finally, build the package with colcon build, which will create an executable wrapper.

Part 3: Common Tasks – Libraries and Launching

Adding a Library

  • C++:
    • Create a new file my_lib.hpp and my_lib.cpp.

In CMakeLists.txt, add:

cmake

add_library(my_cpp_lib src/my_lib.cpp)

target_link_libraries(cpp_talker my_cpp_lib)

  • Include it in your node file with #include “package_name/my_lib.hpp”.
  • Python:
    • Create a new file my_lib.py in the package directory.
    • In your node file, import it with:
python

from my_python_package.my_lib import MyClass

Launching Both Nodes

Both C++ and Python nodes can be launched using the same launch file. This Python-based launch file will start both nodes:

python

from launch import LaunchDescription

from launch_ros.actions import Node

def generate_launch_description():

    return LaunchDescription([

        Node(

            package='my_cpp_package',

            executable='cpp_talker',

            output='screen'

        ),

        Node(

            package='my_python_package',

            executable='py_talker',

            output='screen'

        ),

    ])

Run it using ros2 launch my_cpp_package my_launch_file.launch.py.

Part 4: The Mixed Package (C++ and Python Together)

Sometimes, you need both the performance of C++ and the flexibility of Python in the same ROS 2 package. In such cases, follow these steps:

  • Build Type: Use ament_cmake for the primary build type in package.xml.
  • File Structure: Keep C++ files in src/ and include/, and Python files in a separate directory, such as scripts/ or python/.
  • CMakeLists.txt: Build C++ executables and libraries as usual. For Python, simply copy the scripts:
cmake

install(DIRECTORY scripts/ DESTINATION lib/${PROJECT_NAME})
  • Dependencies: Ensure rclcpp and rclpy are both listed as dependencies in package.xml.

Conclusion

The split between C++ and Python ROS 2 packages is largely a result of the fundamental differences between compiled and interpreted languages. C++ packages require complex build systems due to the need for compilation, while Python packages are simpler, focusing more on script execution. By understanding these structural differences, you can more effectively navigate the world of ROS 2, making smart decisions on when to use C++ for performance or Python for rapid development.

For a deeper dive into official ROS 2 package guidelines, refer to the Open Robotics ROS 2 tutorials on GitHub —a trusted and actively maintained resource used by professionals.

At Robotisim, we guide you step-by-step in applying these tools to real robots, with dedicated lessons on ROS 2 C++ and Python workflows.

Leave a Comment

Your email address will not be published. Required fields are marked *

Review Your Cart
0
Add Coupon Code
Subtotal

 
Scroll to Top