diff --git a/_data/navigation.yml b/_data/navigation.yml index 6600d2a4..4f24722f 100644 --- a/_data/navigation.yml +++ b/_data/navigation.yml @@ -98,6 +98,8 @@ wiki: url: /wiki/common-platforms/ros2-navigation-for-clearpath-husky/ - title: Hello Robot Stretch RE1 url: /wiki/common-platforms/hello-robot + - title: Building ROS2 Custom Packages + url: /wiki/common-platforms/ros2-custom-packages/ - title: Sensing url: /wiki/sensing/ children: @@ -340,6 +342,8 @@ wiki: url: /wiki/tools/code-editors-Introduction-to-vs-code-and-vim/ - title: Qtcreator UI development with ROS url: /wiki/tools/Qtcreator-ros/ + - title: ROS2 Humble Intra-Process Communication Recorder + url: /wiki/tools/ros2-humble-ipc-recorder/ - title: Datasets url: /wiki/datasets/ children: diff --git a/wiki/common-platforms/ros/ros2-custom-package.md b/wiki/common-platforms/ros/ros2-custom-package.md new file mode 100644 index 00000000..6a9b5efd --- /dev/null +++ b/wiki/common-platforms/ros/ros2-custom-package.md @@ -0,0 +1,480 @@ +--- +date: 2025-05-03 +title: Building ROS2 Custom Packages +--- + +When working on a larger ROS 2 project, you'll often need to add new packages to an existing workspace. This tutorial provides a step‑by‑step guide on how to create and integrate a new ROS 2 package (targeting **ROS 2 Humble**) into an existing workspace. We focus on using **colcon** as the build tool and cover both Python‑based packages (using **ament_python**) and C++ packages (using **ament_cmake**). You will learn how to set up the package manifest and build files, build the workspace with colcon, verify that the new package is recognized by the ROS 2 environment, and debug common issues that can arise. By the end of this tutorial, you'll be confident in adding new ROS 2 packages to your workspace and troubleshooting integration problems. + +**Note:** The primary example uses a Python package, but we include notes on integrating C++ packages. We'll also discuss a real‑world debugging scenario (a rosbag2 composable node issue we encountered when developing our [ROS2 Humble Intra‑Process Communication Recorder](/wiki/tools/ros2-humble-ipc-recorder.md)) to illustrate how to tackle workspace integration challenges in practice. + +## Table of Contents +- [Introduction](#introduction) +- [Background: ROS 2 Workspaces and Build Systems](#background-ros-2-workspaces-and-build-systems) +- [Step 1: Creating a New ROS 2 Package in an Existing Workspace](#step-1-creating-a-new-ros-2-package-in-an-existing-workspace) + - [Package Manifest (package.xml)](#package-manifest-packagexml) + - [Python Package Setup (ament_python)](#python-package-setup-ament_python) + - [C++ Package Setup (ament_cmake)](#c-package-setup-ament_cmake) +- [Step 2: Building the Workspace with Colcon](#step-2-building-the-workspace-with-colcon) +- [Step 3: Sourcing the Workspace and Validating Integration](#step-3-sourcing-the-workspace-and-validating-integration) +- [Common Pitfalls and Debugging Tips](#common-pitfalls-and-debugging-tips) + - [Case Study: Rosbag2 Composable Node Integration Issue](#case-study-rosbag2-composable-node-integration-issue) +- [Summary](#summary) +- [See Also](#see-also) +- [Further Reading](#further-reading) +- [References](#references) + +## Introduction + +Adding a new package to a ROS 2 workspace involves more than just writing code - you must properly configure build files and manifests so that the package can be built and recognized by the ROS 2 ecosystem. In this tutorial, we walk through creating a new ROS 2 package inside an existing workspace and ensuring it's correctly integrated. We will use **ROS 2 Humble** for examples. Our focus is on using **colcon** (the standard ROS 2 build tool) and the ament build system. We will create a simple example package (in Python) and discuss what each of the key files (`package.xml`, `setup.py`, and if applicable, `CMakeLists.txt`) should contain. Then we'll build the workspace, demonstrate how to verify the package's presence (so that other packages or tools can see it), and troubleshoot common mistakes. + +Whether you are creating a new node, library, or other ROS 2 component, following these steps will help avoid issues like “package not found” errors or build system mix‑ups. We also highlight a real debugging scenario involving a rosbag2 composable node to show how to generalize problem‑solving strategies for package integration issues. + +## Background: ROS 2 Workspaces and Build Systems + +Before diving in, it's important to understand how ROS 2 organizes code and builds packages: + +- **ROS 2 Workspace:** A workspace is a directory (often named something like `ros2_ws`) that contains a `src/` subdirectory. All ROS 2 packages live under `src/`. You can have multiple packages in one workspace and build them together. Colcon will create separate `build/`, `install/`, and `log/` directories alongside `src/` when you build the workspace. An existing workspace likely already has these; adding a new package means putting it in the `src` folder and rebuilding. +- **Colcon Build Tool:** ROS 2 uses **colcon** instead of ROS 1's catkin tools. Colcon builds all packages in a workspace (or selected packages) and installs their files into the `install/` directory. Colcon is an evolution of ROS build tools like `catkin_make` / `ament_tools`, and it supports both CMake (C/C++ code) and Python package build types. +- **Ament Build System:** ROS 2 packages use *ament* (either `ament_cmake` or `ament_python`) as the build system. **ament_cmake** is used for C++ (or mixed C++/Python) packages and relies on CMakeLists files and CMake macros, while **ament_python** is used for pure Python packages and relies on Python's `setup.py` for installation. These build types are specified in the package manifest. By contrast, ROS 1 used Catkin; mixing ROS 1 Catkin packages into a ROS 2 workspace is not straightforward and generally not recommended without special bridging tools. +- **Package Manifest (`package.xml`):** Every ROS 2 package has a `package.xml` file (format 2 is standard for ROS 2) which declares the package's name, version, dependencies, and build type. This file is critical for colcon to identify how to build the package and what other packages it needs. The manifest also ensures the package gets indexed so ROS 2 tools can find it. +- **Ament Index vs Catkin Index:** ROS 2 uses an *ament resource index* to locate packages and their resources. When a package installs, it registers itself by creating a marker file in `install/share/ament_index/resource_index/packages/`. ROS 1 (Catkin) relied on a different mechanism (crawling directories or `ROS_PACKAGE_PATH`). If a ROS 2 package is not correctly registered in the ament index, tools like `ros2 run` or `ros2 pkg list` will not see it. Misconfiguring a ROS 2 package as a Catkin (ROS 1) package can cause it to install under a Catkin‑specific index (or not be indexed at all), leading to “package not found” issues. Always use the appropriate ROS 2 build type so that the package is indexed in ROS 2's ament resource index, not in a Catkin index. + +With these concepts in mind, let's proceed to creating and building a new package. + +## Step 1: Creating a New ROS 2 Package in an Existing Workspace + +First, navigate to the `src` directory of your existing workspace (for example, `~/ros2_ws/src`). We will create a new package named `my_package`. ROS 2 provides a handy command-line tool to bootstrap a package: + +```bash +# Navigate to the workspace's source directory +$ cd ~/ros2_ws/src + +# Create a new package named my_package, using ament_python as build type and add a dependency on rclpy (for ROS 2 Python client library) +$ ros2 pkg create my_package --build-type ament_python --dependencies rclpy +```` + +The `ros2 pkg create` command will create a new folder `my_package/` in the `src` directory. Inside, you should see a structure like: + +```plaintext +my_package/ +├── package.xml +├── setup.py +├── setup.cfg +├── resource/ +│ └── my_package +├── my_package/ # Python module directory +│ └── __init__.py +└── test/ # (optional) testing files +``` + +Colcon's package creation template populates these files with boilerplate. For a Python package, it includes an empty Python module (`my_package/__init__.py`), an empty resource marker file, and some sample test files. If you used the `--node-name` option, it might also create a sample Python node script (e.g. `my_node.py`) and some boilerplate code in it. + +> **Note:** If you wanted a C++ package instead, you would specify `--build-type ament_cmake`. The structure would then include a `CMakeLists.txt`, `src/` folder (with a sample .cpp if a node name was given), and an `include//` directory for headers, instead of the Python-specific files. We'll cover C++ differences shortly. + +Next, we need to examine and possibly edit the key files to ensure our package is properly configured. + +### Package Manifest (`package.xml`) + +The `package.xml` is the manifest that describes your package. Open `my_package/package.xml` in a text editor. You should see tags for ``, ``, ``, maintainers, licenses, and dependencies. ROS 2 package manifests are format 2 XML. Key points to check or modify: + +* **Name, Version, Description:** Make sure `my_package` is set (the template does this). Update `` from the placeholder “TODO: ...” to a meaningful description of your package. Set the license tag to your chosen license (e.g. `Apache-2.0` or other OSI-approved license). +* **Maintainer:** Ensure there's at least one `Your Name` tag. Fill it with your info if the template left a generic placeholder. +* **Build Type:** The template should include something like `ament_python` inside an `` section for a Python package, or `ament_cmake` for a C++ package. This tells colcon which build tool to use for this package. Verify this is correct. A wrong build type (for example, mistakenly using `ament_cmake` for a purely Python package or vice versa) can cause build issues. For instance, using `cmake` or `catkin` as the build type for a ROS 2 package is a common misconfiguration - colcon either won't know how to build it or will treat it as a plain CMake project without proper registration, leading to the package not appearing in ROS 2's index. +* **Dependencies:** The `ros2 pkg create` command we ran already added `rclpy` as a dependency in the manifest (it should appear as `rclpy` and possibly in `` if needed). Add any other dependencies your package needs. For example, if your Python code will import messages from `geometry_msgs`, add: + + ```xml + geometry_msgs + ``` + + Use `` for compile-time dependencies (mostly for C++ code or generating interfaces) and `` for run dependencies (needed at runtime). You can also use `` which implies both build and exec dependency in format 2. +* **Dependency Versioning:** It's usually not necessary to specify versions for dependencies unless you have a specific requirement. The presence of the tag is enough for colcon/rosdep to ensure the dependency is present. +* **Export Section:** Ensure the `` tag contains the build\_type. For ament\_python it will be: + + ```xml + + ament_python + + ``` + + The template handles this. This section can also include other export information (for example, plugin definitions for pluginlib, but that's advanced usage). + +After editing, your `package.xml` might look like this (minimal example for our Python package): + +```xml + + + my_package + 0.0.0 + A simple example package for ROS 2 (Python). + Your Name + Apache-2.0 + + + ament_python + + + rclpy + geometry_msgs + + + ament_python + + +``` + +Notice we used `ament_python` to indicate we need the ament\_python build tool. We also listed `rclpy` and `geometry_msgs` as dependencies (so that our package will be able to import `rclpy` and messages at runtime). Adjust these depends according to your package's needs. + +For a C++ package, the manifest would be similar but use `ament_cmake` for build tool and build type, and you'd list any C++ library/package dependencies under `` (or specifically `` and `` as appropriate). + +**Common Manifest Pitfall:** Forgetting to list a dependency in `package.xml` can lead to build failures or (worse) successful build but runtime errors. Colcon relies on the manifest to order package builds. If package A depends on B but you didn't declare it, colcon might try to build A before B (leading to include or import errors), or `rosdep` won't know to install B. Always update `package.xml` with all dependencies (including things like message packages, libraries, or ROS 2 core packages your code uses). + +### Python Package Setup (ament\_python) + +Since our example package is Python-based, we need to ensure the Python packaging files are correctly set up. There are typically two important files for an `ament_python` package: `setup.py` and `setup.cfg`, plus the Python module code itself. + +#### `setup.py` + +Open `my_package/setup.py`. This is a standard Python `setuptools` setup script, adapted for ROS 2. It defines how to install your package and any executable scripts. The template created by `ros2 pkg create` likely contains something similar to: + +```python +from setuptools import setup + +package_name = 'my_package' + +setup( + name=package_name, + version='0.0.0', + packages=[package_name], + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), # Ensure package is indexed + ('share/' + package_name, ['package.xml']), # Install package.xml + ], + install_requires=['setuptools'], + zip_safe=True, + author='Your Name', + author_email='[email protected]', + maintainer='Your Name', + maintainer_email='[email protected]', + description='A simple example package for ROS2 (Python)', + license='Apache-2.0', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + # 'executable_name = module.path:main_function', + ], + }, +) +``` + +Let's break down the important parts of `setup.py`: + +* **`packages=[package_name]`:** This tells setuptools to look for a Python package (directory with `__init__.py`) named `my_package` and install it. The template ensures your `my_package/` directory (with `__init__.py`) is included. +* **`data_files`:** This is crucial in ROS 2 packaging. Here we install two things: + + * A marker file for the ament index: The empty file in `resource/my_package` gets copied into `share/ament_index/resource_index/packages/` with the package name. This “marks” the package as a ROS 2 package in the ament resource index so that ROS 2 tools know it's present. (Colcon was historically adding this automatically for Python packages, but it's required to do it explicitly moving forward.) + * The `package.xml`: Installing the manifest to `share/my_package/` is standard so that tools can inspect package metadata at runtime if needed. +* **`entry_points`:** This is where we define console scripts (executables). If you want to make a Python node directly runnable with `ros2 run`, you should add an entry here. For example, if you have a Python file `my_package/my_node.py` with a function `main()`, you can add: + + ```python + entry_points={ + 'console_scripts': [ + 'my_node = my_package.my_node:main', + ], + } + ``` + + This will install a command-line script named `my_node` (in ROS 2 environments, it will be namespaced under the package, but `ros2 run` knows to look for it). Then running `ros2 run my_package my_node` will execute the `main()` function in `my_node.py`. In our example template, we haven't created a `my_node.py` yet, but you can create it under `my_package/` and use this mechanism. +* **Metadata fields** like `author`, `maintainer`, `description`, `license` - these should match or complement your package.xml. The template likely filled them with placeholders or the data you provided via command options. Fill them in accordingly (in our snippet above we put example values). + +After configuring `setup.py`, if you create a Python node script, e.g., `my_package/my_node.py`, ensure it has a `main()` function (or whatever entry point you specified). For example, a simple node might be: + +```python +# my_package/my_node.py +import rclpy +from rclpy.node import Node + +class MyNode(Node): + def __init__(self): + super().__init__('my_node') + self.get_logger().info("Hello from my_node!") + +def main(): + rclpy.init() + node = MyNode() + rclpy.spin(node) + rclpy.shutdown() +``` + +This is just a basic example; the important part is that `main()` will be called when you do `ros2 run my_package my_node` (after building and sourcing). The console\_scripts entry point connects the dots. + +#### `setup.cfg` + +The `setup.cfg` file is often a one-liner needed for ROS 2 Python packages that have console scripts. It typically contains: + +```ini +[build_scripts] +executable = /usr/bin/env python3 +``` + +This ensures the installed scripts use the correct Python interpreter. The template likely included this file already. You usually don't need to modify it except to verify it exists in the package. Its presence allows ROS 2 to find the executables via `ros2 run`. + +#### Resource Folder and Marker + +As mentioned, the `resource/` directory contains a file named exactly as your package (`my_package` with no extension). It's an empty file. During installation (via `setup.py` data\_files), this gets copied to the ament index. Ensure this file exists and is named correctly. The template should have created it for you. If it's missing, create the folder `resource` and inside it an empty file `my_package`. This small detail is what allows ROS 2 tools to identify that your package is installed. If omitted, you might encounter warnings or issues with package recognition (e.g., a warning that the package doesn't explicitly install a marker, which will be required in the future). + +**Recap for Python package:** After these checks, our Python package has: + +* Proper `package.xml` with `ament_python` and dependencies. +* A `setup.py` that installs the marker and `package.xml`, and sets up any console script entry points. +* A `setup.cfg` for ensuring correct script shebang. +* Python module code (and possibly a script with a `main()` function). + +This is sufficient for colcon to build and install the package. + +### C++ Package Setup (ament\_cmake) + +If your new package is C++ or you need to integrate C++ code (or libraries) in the workspace, the process is similar but involves a CMake build. Suppose we created `my_cpp_pkg` with `--build-type ament_cmake`. Key files would be `CMakeLists.txt` (instead of setup.py) and the `package.xml` would list `ament_cmake` as the build type. + +For a minimal C++ package: + +* **CMakeLists.txt:** This file defines how to build the C++ code. A simple template might contain: + + ```cmake + cmake_minimum_required(VERSION 3.8) + project(my_cpp_pkg) + + find_package(ament_cmake REQUIRED) + find_package(rclcpp REQUIRED) # for example, if using rclcpp + + add_executable(my_node src/my_node.cpp) + ament_target_dependencies(my_node rclcpp std_msgs) # list your dependencies + + install(TARGETS my_node + DESTINATION lib/${PROJECT_NAME}) + + ament_package() + ``` + + This CMakeLists does a few important things: + + * Calls `find_package(ament_cmake REQUIRED)` and any other dependencies (like `rclcpp`, message packages, etc.). This makes sure the CMake knows about those packages' include paths and libraries. + * Uses `add_executable` to compile a node from source (if you have a `src/my_node.cpp`). + * Uses `ament_target_dependencies` to link the target against ROS 2 libraries (here `rclcpp` and `std_msgs` as an example). + * Installs the compiled executable to the correct location (`lib/my_cpp_pkg`). This is crucial: if you don't install your targets or other artifacts, they won't be placed into the `install/` space and ROS 2 won't be able to use them. + * Calls `ament_package()` at the end. This macro does a lot, including adding the package to the ament index (it generates that same marker file for C++ packages automatically) and exporting CMake config files for downstream packages. **Forgetting to call `ament_package()` is a common mistake** - without it, your package might build but won't be findable by other packages or fully registered. + +* **Source code and headers:** Place your `.cpp` files under `src/` and headers (if any) under `include/my_cpp_pkg/`. The template from `ros2 pkg create` likely made an empty `src/` and `include/` directory. If you used `--node-name`, it may have created a simple `my_node.cpp` that prints “Hello World” to stdout. Ensure your `CMakeLists.txt` references the correct file names. + +* **package.xml:** Similar to the Python example, but with `ament_cmake` and `ament_cmake` in the export section. Also list dependencies like `rclcpp` or `std_msgs` in the manifest (`rclcpp` etc.), matching what you find\_package in CMake. + +After setting up, a minimal `package.xml` for C++ might be: + +```xml + + my_cpp_pkg + 0.0.0 + Example ROS2 C++ package + Your Name + Apache-2.0 + ament_cmake + rclcpp + std_msgs + + ament_cmake + + +``` + +With these in place, your C++ package will compile and integrate. One additional note: if your package is a library or plugin (not just an executable), you would use `add_library(...)` in CMake and also install the library, plus possibly export plugin information. For example, for a *composable node* (which is a plugin), you'd create a library and use `rclcpp_components_register_nodes` macro and install a plugin description XML. That's more advanced, but keep in mind that integration requires installing those resources too. + +**Common C++ Pitfall:** Not installing targets or forgetting `ament_package()`. If you build a library or node and don't install it, it stays in the build directory and won't be available at runtime in the install space. Similarly, without `ament_package()`, other packages won't be able to `find_package(my_cpp_pkg)` because no package index entry or CMake config gets generated. Always follow the pattern above. + +## Step 2: Building the Workspace with Colcon + +After creating the package and configuring its files, the next step is to build the workspace. Since we are adding to an existing workspace, make sure any existing packages are also in a good state (their manifests are correct, etc.). + +Before building, it's generally good to run `rosdep` to install any system dependencies for all packages in your workspace: + +```bash +# From the workspace root (e.g., ~/ros2_ws) +$ rosdep install --from-paths src --ignore-src --rosdistro humble -y +``` + +This finds all `` and `` that are system packages and attempts to install them (for example, if you depend on OpenCV or other system libraries). If your new package only depends on core ROS packages that are already installed, rosdep will likely say all requirements are already satisfied. + +Now, build the workspace: + +```bash +# Ensure you are at the root of the workspace (the directory that contains src/) +$ cd ~/ros2_ws + +# Build the entire workspace +$ colcon build +``` + +Colcon will locate all packages in `src/` (including the new `my_package` and any others) and build them in the correct order. You should see output logging each package's build process. In our case, it should find `my_package` (ament\_python) and process its setup.py, and produce an install for it under `install/my_package`. If you also have `my_cpp_pkg`, colcon will compile it via CMake and place its artifacts under `install/my_cpp_pkg`. + +If the workspace is large, you can choose to build only the new package to save time: + +```bash +$ colcon build --packages-select my_package +``` + +This will build `my_package` and nothing else (except its dependencies if not built yet). In an existing workspace scenario, `--packages-select` is handy if you know only your new package and maybe a few others changed. Note that if your new package depends on another package in the workspace that hasn't been built yet, you should either build that dependency first or let colcon build it by not excluding it. + +A successful build will end with a summary like: + +``` +Summary: 1 package finished [X.Y seconds] +``` + +for a single package build, or listing multiple if you built the whole workspace. If there are errors, read them carefully: + +* **Compilation errors (for C++):** Check your code and CMakeLists. Common issues include missing includes or forgetting to link to a library (manifest vs CMake mismatch). +* **Installation or setup errors (for Python):** If colcon complains about missing files or not finding `setup.py`, ensure you ran colcon from the workspace root, not inside the package. Running `colcon build` from the `src/` directory can confuse it (it may not find packages properly). Always run from the root (where `build/` and `install/` will be created). +* **Dependency not found:** If you see an error like “Could not find package configuration file provided by X”, you likely have a missing dependency or forgot to source the underlay (see Pitfalls section). For example, if your package depends on something in ROS 2 that's not installed, or another package in the workspace that failed to build, colcon will stop. Installing the dependency via apt or adding it to the workspace and building it should resolve this. + +After a successful build, colcon will have populated the `install/` directory with subdirectories for each package (unless you used `--merge-install`, which puts all files in a single prefix). For each package, key things in `install/` are: + +* `install/my_package/lib/` - contains executables or libraries (for Python packages, console entry scripts go here). +* `install/my_package/share/` - contains the package.xml and potentially ament index resource file, and any launch files or other resources if you installed them. +* For Python, `install/my_package/lib/python3.X/site-packages/my_package` - the Python module code gets installed here so it's accessible via Python path. + +## Step 3: Sourcing the Workspace and Validating Integration + +Once built, you need to **source the workspace's setup script** to add the new package to your environment. Open a new terminal (or source in the current one) and execute: + +```bash +$ source ~/ros2_ws/install/setup.bash +``` + +Make sure you have also sourced the ROS 2 installation (e.g., `/opt/ros/humble/setup.bash`) either earlier or in your `~/.bashrc`. If your ROS 2 environment wasn't sourced, do that first, then the workspace. Sourcing `install/setup.bash` will overlay the workspace on top of the ROS distribution. This sets up environment variables so that ROS 2 commands are aware of the new package and its contents. (In Windows, you'd call the `.bat` file; in zsh or other shells there are analogous scripts.) + +Now, let's validate that the new package is recognized and accessible: + +* **List the package:** Run `ros2 pkg list | grep my_package`. You should see `my_package` in the output. ROS 2's package listing uses the ament index to find packages. If it doesn't show up, something is wrong with the installation or indexing (we'll troubleshoot that soon). + +* **Run executables:** If your package has a runtime executable (entry point or C++ node), try running it. For our Python example, if we added the console script for `my_node`, do: + + ```bash + $ ros2 run my_package my_node + ``` + + This should start the node and you should see its log output (e.g., “Hello from my\_node!” from the example code). If ROS 2 complains “Package ‘my\_package' not found” or “Executable ‘my\_node' not found”, then the integration isn't complete: + + * “Package not found” indicates the environment is not aware of the package (likely you didn't source the setup.bash of the workspace, or the package failed to install properly). + * “Executable not found” indicates the package is known, but the console script or binary isn't found. This could mean you didn't set up the entry point (for Python) or didn't install the target (for C++). Double-check `setup.py` entry\_points or CMake install directives. + +* **Check environment variables:** For Python packages, `PYTHONPATH` (or the more modern approach of using the installed site-packages) is handled by the setup script. After sourcing, you can run `python3 -c "import my_package"` in the terminal to see if the module imports. It should succeed (doing nothing if the module is empty). If it fails, the package isn't on the Python path, implying the setup script wasn't sourced correctly or installation failed. + +* **Ament index check:** You can manually verify that the marker file is present. Look for `install/share/ament_index/resource_index/packages/my_package`. If that file is missing, ROS 2 will not recognize the package even if everything else is there. If you find it missing, it means the installation step in setup.py didn't happen or was incorrect. (Earlier colcon versions implicitly created it for python, but that's changing.) The fix is to add the proper `data_files` entry as shown in setup.py above. + +* **Using the package from another package:** If you have another package in the workspace that depends on `my_package`, try building that as a further test. Colcon should be able to find `my_package` via `find_package` (CMake) or as a dependency (package.xml). If it can't, then something is off with how `my_package` was registered (again, likely missing `ament_package()` or marker). + +Everything up to this point ensures that the package is correctly built and integrated. Next, we'll cover common pitfalls and how to debug them when things don't go as expected. + +## Common Pitfalls and Debugging Tips + +Even when following the general procedure, it's easy to run into issues. Here are some common problems and how to address them: + +* **Forgetting to Source the Workspace:** After building, if you forget to source the new `install/setup.bash`, ROS 2 commands won't know about your new package. This leads to errors like `Package 'my_package' not found` when you try to run or launch it. The fix is simple: source the workspace overlay. If you open new terminals frequently, consider adding `source ~/ros2_ws/install/setup.bash` to your `~/.bashrc` (after sourcing the base ROS setup) for convenience. +* **Running Colcon in the Wrong Directory:** Ensure you run `colcon build` from the root of the workspace (the directory that contains `src/`), *not* from within `src/` or inside a package. Running colcon from the wrong location can result in it not finding any packages or only building one and not installing things correctly. One user error was running `colcon build` inside the `src` folder, causing the package to not be found after build. Always `cd` to the workspace root before building. +* **Missing Dependencies:** If colcon fails with an error about missing package configurations, e.g.: + + ``` + CMake Error at CMakeLists.txt: find_package(some_dep REQUIRED) ... not found + ``` + + or a Python import error during runtime, you likely didn't install or declare a dependency. Use `rosdep` to install system dependencies. Check that every package or module your code uses is listed in `package.xml`. For C++ dependencies, also ensure you have the corresponding `find_package` in CMake. In one case, a user's C++ component had an undefined symbol at runtime - it turned out they hadn't linked against a needed library in CMake, which is solved by adding it to `ament_target_dependencies`. +* **Misconfigured Package Type (Catkin vs Ament):** As noted, ROS 2 does not use Catkin. If you accidentally use a Catkin-style package (e.g., copying a ROS1 package into ROS2 workspace without conversion), colcon might identify it as `type: ros.catkin`. By default, ROS 2 installations don't even include the Catkin build tool, so you'll get errors about `Findcatkin.cmake` not found. The solution is to migrate the package to ROS 2: + + * Update the `package.xml` to format 2 and change `catkin` to the appropriate ament dependency. + * If it's pure CMake without ROS, you might set `cmake` and manage CMakeLists manually, but then ensure you install a marker file to register it (similar to what ament\_cmake/ament\_python do) so ROS 2 sees it. + * Generally, it's best to use `ament_cmake` or `ament_python`. Only use `cmake` build type for special cases, and be aware you must handle the indexing and installation. + * If you truly need to build a ROS1 catkin package inside a ROS2 workspace, there are colcon extensions and workarounds, but that's advanced (see **catment** in ROS 2 design docs, which allows mixing, but requires additional setup). +* **Package Builds but Not Recognized (Missing Ament Index Resource):** If `ros2 pkg list` doesn't show your package even after sourcing, check for the presence of the marker file as mentioned. For Python packages, a common oversight is not including the `data_files` section in `setup.py` to install the resource file. Colcon currently might warn: + + ``` + WARNING: Package 'your_package' doesn't explicitly install a marker in the package index... + ``` + + This warns that in the future it won't auto-create it. The fix is to include the snippet in setup.py as we showed. For C++ packages, forgetting `ament_package()` can lead to a missing marker; ensure it's in CMakeLists. +* **Console Script Not Found:** If you can see your package with `ros2 pkg list` but `ros2 run my_package my_script` says the script is not found, then likely the `entry_points` in setup.py wasn't set or the setup.py didn't execute. Check that the console\_scripts entry matches the name you're using and that you rebuilt after adding it. For C++, if an executable is not found, ensure the target was installed in CMake. +* **Mixing Release and Debug or Multiple Overlays:** Sometimes, especially with C++ code or when mixing source builds of core packages with binaries, you can have library path issues. For example, if you built a custom version of a core library in your workspace, you must source that workspace *before* using it, or ROS 2 might still use the system-installed version, causing ABI mismatches. A typical scenario: you build a newer `rclcpp` or `rosbag2` from source in your overlay but forget to source it, then your nodes might load the wrong .so file. Always ensure you're using the intended version of each package by sourcing order or not mixing duplicate packages. +* **Cleaning and Rebuilding:** If you make changes to package.xml or setup.py and things still seem off, try cleaning the workspace. You can remove the `build/`, `install/`, and `log/` directories (or use `colcon build --clean` if available) and rebuild fresh. This ensures no stale artifacts are causing confusion. +* **Using `colcon list`:** A handy command is `colcon list` (in the workspace root). It will list all packages colcon sees and their build types. Use this to verify your new package is being detected and that its type is correct (it will show `ament_python` or `ament_cmake`). If your package doesn't appear in `colcon list`, then either the directory is not in the `src` folder, or the `package.xml` is malformed (colcon couldn't parse it). Fix the manifest or location as needed. + +### Case Study: Rosbag2 Composable Node Integration Issue + +Consider a real example: integrating ROS 2's rosbag2 (the recording and playback tool) in a workspace to use its components. Rosbag2 provides composable nodes (C++ components) for the recorder and player. Suppose you want to customize rosbag2 or use it alongside your packages, so you include rosbag2 source in your workspace or build a package that depends on it. + +We encountered a problem where after building rosbag2 from source in our workspace, trying to load the rosbag2 recorder as a component in a launch file failed. The error was along the lines of *“Failed to load component… class not found”* when using `ros2 component load` or a composable launch. This was puzzling because the rosbag2 packages were built and present. + +After investigation, a few issues were identified: + +* The **plugin description file** (which lists the component plugin for rosbag2 recorder/player) was not being found. In rosbag2, these are usually installed in the `share/` directory of the package and registered in the ament index under `resources` for pluginlib. If the installation or index registration wasn't correct, the component loader can't discover the class. +* There was a **version mismatch**: the user had rosbag2 installed via binaries (apt) and also built from source. If the environment was not carefully managed, the running system might have been mixing the two - for example, trying to load the component from the wrong library (.so). The ROS 2 environment might have found the binary version's plugin description, but the source-built library or vice versa, causing an undefined symbol error or missing class error. +* It turned out that the user's new workspace was not fully overriding the system installation because they hadn't sourced the local workspace or they only built some parts of rosbag2 but not others (like maybe the `rosbag2_composition` package which provides the loading functionality). + +**Resolution:** The solution in that case was to ensure: + +* All relevant rosbag2 packages were built in the workspace (including any plugin or composition-related ones). +* The workspace `install/setup.bash` was properly sourced *and* that no conflicting sourcing of the system installation came afterward to override it. +* Verify that the plugin XML (for rosbag2 components, this might be installed as `share/rosbag2_composition/rosbag2_composition.xml` or similar) is present in the install space and that the `ament_index/resource_index` has an entry for it (pluginlib uses the ament index to find the plugin xml by package name). +* Use `ros2 pkg prefix rosbag2_composition` to see which path ROS 2 is using for that package - it should point to your workspace install, not `/opt/ros/humble`. If it doesn't, then your overlay didn't override the binary (perhaps you didn't build that package, or the overlay order is wrong). +* In the end, rebuilding with all components and carefully sourcing fixed the issue. The rosbag2 recorder could then be loaded as a component successfully. + +**General lesson:** When integrating a complex package like rosbag2: + +* Make sure to include all necessary sub-packages and plugins. +* Avoid mixing source and binary versions of the same package in the runtime environment; it's usually all or nothing to prevent conflicts. +* If a component fails to load with an undefined symbol, check that you've linked all dependent libraries and that the component was built against the correct version of those libraries (no ABI incompatibility). Undefined symbols often mean a missing dependency in the CMake or a version mismatch. +* Leverage ROS 2 introspection tools: `ros2 pkg prefix`, `ros2 pkg xml`, or just inspecting the `install` directory to ensure files are in place. + +By analyzing the rosbag2 issue, we see that most integration problems boil down to either build configuration errors (not building or installing something correctly) or environment setup mistakes (not sourcing or mixing installs). Systematically checking each of these aspects will resolve the majority of “my new package doesn't work” scenarios. + +## Summary + +Building a new ROS 2 package in an existing workspace involves careful setup of the package's manifest and build files, using colcon to compile and install it, and then making sure the environment is updated to include it. In this guide, we created an example package `my_package` using Python (ament\_python) and highlighted how to configure `package.xml`, `setup.py`, and related files. We also touched on how a C++ (ament\_cmake) package setup differs, including the need for `CMakeLists.txt` and proper CMake macros. + +Key takeaways: + +* Always specify the correct `` in package.xml (`ament_python` for Python, `ament_cmake` for C++). Incorrect types can lead to your package not being recognized by ROS 2. +* Ensure all dependencies are declared in package.xml and that for Python packages you explicitly install the ament index resource marker (so the package is discoverable by ROS 2 tools). +* Use `colcon build` from the workspace root and source the workspace's setup script after building. Without sourcing, your new package remains invisible to ROS 2. +* Verify integration by listing and running the package's nodes. If something is not found, retrace your steps: check installation paths, entry points, and environment sourcing. +* Common issues like missing dependencies, not installing executables, or mixing build systems can be debugged by inspecting build logs and the install directory. Leverage community Q\&A and official docs when stuck on specific errors - often the solution is a small fix in the manifest or CMake/setup file. +* The rosbag2 case study illustrated that even when everything builds, runtime issues can occur if the environment is inconsistent. Always ensure your workspace overlay is properly applied and avoid conflicting installations when possible. + +By following this tutorial and the best practices outlined, you should be able to confidently add new packages to your ROS 2 Humble workspace and have them work seamlessly with existing packages. + +## See Also: + +- [ROS Introduction](/wiki/common-platforms/ros/ros-intro) +- [ROS 2 Humble IPC Recorder](/wiki/tools/ros2-humble-ipc-recorder) +- [Building an iOS App for ROS2 Integration – A Step-by-Step Guide](/wiki/common-platforms/ros2_ios_app_with_swift) + +## Further Reading + +- [ROS 2 Tutorial - Writing a Simple Publisher and Subscriber (Python)](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Py-Publisher-And-Subscriber.html) + - A step‑by‑step, official Humble‑branch tutorial that walks you through creating your first Python node pair using rclpy. It clarifies publisher/subscriber APIs, QoS defaults, and the directory layout that ROS 2 expects, making it an ideal foundation before tackling more advanced, high‑bandwidth recording scenarios. +- [ROS 2 Tutorial - Writing a Simple Publisher and Subscriber (C++)](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Cpp-Publisher-And-Subscriber.html) + - The C++ counterpart to the Python guide, this page teaches how to write minimal rclcpp publisher and subscriber nodes, build them with ament_cmake, and test them with ros2 run. +- [ROS2 Create ROS Package with Colcon](https://www.youtube.com/watch?v=4zGUDisw4UI) + - A concise YouTube demo that shows the entire ros2 pkg create -> colcon build workflow, highlighting where package.xml, CMakeLists.txt, or setup.py live. +- [ROS2 Build Packages with Colcon](https://www.youtube.com/watch?v=KLvUMtYI_Ag) + - This follow‑up video dives deeper into colcon build flags, selective‑build options, and troubleshooting failed builds. +- [ROS2 Publisher and Subscriber Package C++](https://www.youtube.com/watch?v=rGsyQHwWObA) + - Demonstrates building a small C++ package from scratch that publishes and subscribes to custom messages, then tests it live. The example reinforces how topic names, message types, and QoS settings must line up. +- [CMake Tutorial for Absolute Beginners - From GCC to CMake including Make and Ninja](https://www.youtube.com/watch?v=NGPo7mz1oa4) + - A beginner‑friendly introduction to modern CMake, Make, and Ninja. If you need to tweak the IPC recorder’s CMakeLists.txt or add custom compile flags for performance, this video quickly gets you comfortable with the build‑system fundamentals ROS 2 relies on. + + +## References + +\[1] Open Robotics, “*Creating a ROS 2 Package (Humble)*,” *ROS 2 Tutorials*, 2022. Accessed: May 3 2025. \[Online]. Available: [https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.html](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.html) + +\[2] Open Robotics, “*Using colcon to build packages*,” *ROS 2 Documentation (Foxy)*, May 12 2020. Accessed: May 3 2025. \[Online]. Available: [https://docs.ros.org/en/foxy/Tutorials/Colcon-Tutorial.html](https://docs.ros.org/en/foxy/Tutorials/Colcon-Tutorial.html) + + diff --git a/wiki/tools/ros2-humble-ipc-recorder.md b/wiki/tools/ros2-humble-ipc-recorder.md new file mode 100644 index 00000000..2c4a4e75 --- /dev/null +++ b/wiki/tools/ros2-humble-ipc-recorder.md @@ -0,0 +1,363 @@ +--- +date: 2025-05-04 +title: ROS2 Humble Intra-Process Communication Bag Recorder +--- + +High-bandwidth sensors like cameras and LiDAR can easily overwhelm a ROS 2 system when recording data. Intra-process communication (IPC) in ROS 2 offers a solution by allowing zero-copy message passing between nodes in the same process, dramatically reducing CPU and memory overhead. This wiki entry introduces the [**ROS 2 Humble Intra-Process Communication Bag Recorder**](https://github.com/MRSD-Wheelchair/humble_ipc_rosbag) – a tool that enables efficient recording of data-heavy topics (e.g. high-resolution images and point clouds) on ROS 2 Humble. We’ll explain how ROS 2’s intra-process mechanism and composable nodes work, why the default `ros2 bag record` in Humble doesn’t use IPC, and how this custom recorder backports “zero-copy” recording to Humble. By the end, you’ll understand when and how to use this tool to reliably capture multiple camera streams or other large-data topics without dropping messages. + +**Note:** This tool is specifically for **ROS 2 Humble Hawksbill (2022)**. Newer ROS 2 releases (starting with Jazzy) already include intra-process support in rosbag2, so Humble users benefit most from this solution. + +## Table of Contents + +* [Introduction](#introduction) +* [Background: Intra-Process Communication and Composable Nodes](#background-intra-process-communication-and-composable-nodes) +* [The Problem: Recording High-Bandwidth Data in ROS 2 Humble](#the-problem-recording-high-bandwidth-data-in-ros-2-humble) +* [Humble IPC Bag Recorder: Overview and Benefits](#humble-ipc-bag-recorder-overview-and-benefits) +* [Installation and Setup](#installation-and-setup) +* [Usage Guide: Intra-Process Rosbag Recording](#usage-guide-intra-process-rosbag-recording) + + * [Launching the Recorder via Launch Files](#launching-the-recorder-via-launch-files) + * [Configuring Topics and Parameters](#configuring-topics-and-parameters) + * [Starting and Stopping the Recording](#starting-and-stopping-the-recording) + * [Validating the Recorded Bag](#validating-the-recorded-bag) +* [Example Use Case: Recording Multiple Cameras](#example-use-case-recording-multiple-cameras) +* [Summary](#summary) +* [See Also](#see-also) +* [Further Reading](#further-reading) +* [References](#references) + +## Introduction + +Robotic systems often need to log data from high-resolution cameras, 3D LiDARs, or depth sensors for debugging and analysis. However, recording these high-bandwidth topics in ROS 2 can strain the system. By default, ROS 2 nodes run in separate processes, meaning messages are passed via the middleware (DDS) between publisher and subscriber. For large messages (images, point clouds, etc.), this inter-process communication incurs significant serialization and copying overhead. As data rates climb into the hundreds of MB/s, a standard rosbag2 recorder may start dropping messages or consuming excessive CPU. + +**Intra-Process Communication (IPC)** is a ROS 2 feature that bypasses the middleware when publisher and subscriber are in the same process. Instead of serializing data, ROS 2 can transfer a pointer to the message, enabling *zero-copy* or near zero-copy delivery. This drastically reduces CPU load and memory usage during message passing. In fact, node composition has been shown to save \~28% CPU and 33% RAM by avoiding duplicate message copies. For very large messages (on the order of 1 MB or more), intra-process messaging is essentially a necessity – such data flows are *“virtually impossible without node composition.”* + +ROS 2 provides the concept of **Composable Nodes** to leverage IPC. By combining multiple nodes into a single process (a *composition*), messages exchanged between those nodes use the efficient intra-process mechanism. In ROS 1, a similar idea existed as nodelets; ROS 2 improves on this with a more robust design. Using composable nodes, we can place a data-producing node (e.g. a camera driver) and a data-consuming node (e.g. a recorder) in the same process, eliminating the expensive inter-process message copy. This wiki entry focuses on applying these techniques to rosbag recording on ROS 2 Humble. + +We will first cover the background of ROS 2 IPC and composable nodes. Next, we’ll discuss why the default `ros2 bag record` in Humble does *not* utilize IPC (and the limitations that imposes). We then introduce the **Humble IPC Bag Recorder** tool which backports intra-process recording to Humble. Detailed usage instructions – from installation to runtime operation – are provided, including example launch files and commands. An example scenario of recording multiple camera streams demonstrates the benefits. Finally, we include a summary, related links, further reading, and references for deeper insight. + +## Background: Intra-Process Communication and Composable Nodes + +To understand the solution, it’s important to grasp how ROS 2 implements intra-process communication and what composable nodes are: + +* **ROS 2 Intra-Process Communication (IPC):** In ROS 2, when a publisher and subscriber are in the same process and use compatible QoS settings, the middleware can be bypassed. The message data is stored in an in-memory buffer and a pointer or reference is shared with the subscriber, rather than serializing the data through DDS. This “zero-copy” transport means the data isn’t duplicated for the subscriber; both publisher and subscriber access the same memory (until it’s no longer needed). The result is much lower latency and CPU usage for message passing, since we avoid copy and serialization costs. In effect, IPC treats message exchange like a direct function call or shared memory queue inside one process, rather than sending over network interfaces. ROS 2’s IPC was significantly improved starting in Dashing and Eloquent (after early versions had limitations). With modern ROS 2, IPC supports `std::unique_ptr` message types to achieve zero-copy transfer in most cases. The trade-off is that publisher and subscriber must run in one process – which leads to the need for composed nodes. + +* **Composable Nodes (Node Composition):** ROS 2 allows multiple nodes to be launched into a single OS process using the rclcpp composition API. A *Composable Node* is a node that can be loaded at runtime into a container process (an `rclcpp_components` container) instead of running as a standalone process. By composing nodes together, intra-process communication becomes possible among them. Composition is ROS 2’s answer to ROS 1’s nodelets, but it’s more flexible – any node that is written as a component (basically, exports itself as a plugin) can be dynamically loaded into a container. The ROS 2 launch system supports this via the `ComposableNodeContainer` and `ComposableNode` descriptions in Python launch files. When launching a component, you can pass an argument `extra_arguments=[{'use_intra_process_comms': True}]` to ensure intra-process communication is enabled for that node’s subscriptions. Composed nodes still appear as separate logical nodes (with their own names, topics, etc.), but physically they share the process. This eliminates the need to send messages via DDS for intra-process delivery. Composable nodes are especially beneficial for high-frequency or high-bandwidth data pipelines – a recent study showed composition **dramatically improves performance** for large messages like images and point clouds. It reduces latency and prevents redundant copies, which is crucial on resource-constrained systems or when running dozens of nodes. + +In summary, intra-process communication in ROS 2 provides a way to avoid costly data copying by delivering messages within the same process, and composable nodes are the mechanism to colocate publishers and subscribers so that IPC can happen. By using these features, one can create a “zero-copy” pipeline for data-intensive tasks. Next, we look at how this applies to rosbag recording and why ROS 2 Humble’s default bagging tool doesn’t take advantage of it by default. + +## The Problem: Recording High-Bandwidth Data in ROS 2 Humble + +Recording topics in ROS 2 is done with the rosbag2 tool (`ros2 bag`). In ROS 2 Humble (and earlier releases), `ros2 bag record` runs as a standalone process that subscribes to the chosen topics and writes messages to storage (SQLite3 or MCAP). Because the recorder runs in its own process separate from the publishers, all data is transferred via *inter-process* communication. For each message published by, say, a camera driver, a copy must be sent through DDS to the recorder process. This introduces significant overhead for large messages: + +* **CPU Overhead:** Each message may be serialized and copied in the publisher, then deserialized in the recorder, consuming CPU time. At high publish rates (e.g. 30 FPS video at 1080p), the serialization workload can saturate a core. The recorder itself must keep up with decoding and writing the data. +* **Memory Bandwidth:** Instead of sharing memory, the data travels through the loopback network or shared memory transport of DDS, effectively duplicating the data in RAM. Large images (several MB each) or point cloud scans will quickly chew through memory bandwidth when duplicated for recording. +* **Dropped Messages:** If the recorder cannot keep up with the incoming data stream, it will start to drop messages. Users often observe that after some seconds, frames begin to drop – the recorder falls behind as buffers overflow. In ROS 1, `rosbag` had tuning options like queue size, buffer size, and chunk size; ROS 2 rosbag2 has a `--max-cache-size` parameter to buffer more data, but ultimately if the throughput exceeds what inter-process transport+disk can handle, data will be lost. +* **Latency:** The additional hops through the middleware add latency. For live monitoring purposes (like visualizing a camera while recording), inter-process communication can introduce noticeable lag or jitter. + +The net effect is that on ROS 2 Humble, recording high-bandwidth topics can be inefficient. As an example, consider two 640×480 cameras at 30 Hz: one user reported smooth viewing in RViz but as soon as they started a rosbag2 recording, frames began dropping after \~20 seconds, even though CPU and disk usage appeared under limits. The default recorder simply couldn’t sustain the required throughput with inter-process message handling. + +**Why doesn’t ROS 2 Humble use IPC for rosbag2?** The capability to use intra-process communication in rosbag2 was not yet integrated into Humble’s stable release. The rosbag2 recorder and player in Humble run only as standalone processes. While ROS 2 did have the IPC mechanism available, the rosbag2 tool hadn’t been refactored to take advantage of it. In late 2021 and 2022, ROS 2 developers started addressing this via feature requests to make rosbag2’s Recorder and Player into *components* that could be launched in-process. This work landed in ROS 2’s development branch after Humble’s release. By the time of **ROS 2 Iron** (mid-2023) and the **ROS 2 Jazzy (2024)** release, rosbag2 gained the ability to run as a composable node. In Jazzy’s changelog, it’s noted that *“Player and Recorder are now exposed as rclcpp components”*, enabling zero-copy recording and reducing CPU load, and helping avoid data loss for high-bandwidth streams. In other words, the feature we need is available in newer ROS 2 distributions (and in nightly Rolling builds), but it’s absent from Humble by default. + +For users sticking with the Humble Hawksbill LTS, this posed a challenge: how to efficiently record high-volume data without upgrading the entire ROS 2 distribution. This is where the **Humble IPC Bag Recorder** comes in – it fills that gap by bringing intra-process recording to Humble. + +## Humble IPC Bag Recorder: Overview and Benefits + +The **ROS2 Humble Intra-Process Communication Bag Recorder** (or *Humble IPC Bag Recorder* for short) is a custom tool that enables rosbag2 recording with intra-process communication on ROS 2 Humble. In essence, this tool provides a **composable recorder node** that you can run in the same process as your publishers, achieving the zero-copy data transfer that the stock Humble recorder lacks. + +**Purpose:** This package was created as a backport of the rosbag2 composable recorder functionality. It allows Humble users to record topics without the inter-process overhead, by manually composing the recorder with the target publishing nodes. The motivation came from real-world needs to log camera and LiDAR data at full rate on Humble systems without losing data. Rather than waiting for the next ROS 2 release or attempting a risky partial upgrade, developers can integrate this recorder into their existing Humble workflows. + +**Key Features and Advantages:** + +* **Intra-Process (Zero-Copy) Recording:** The recorder node subscribes to topics via intra-process communication when co-located with the publishers. This minimizes CPU usage and virtually eliminates the extra memory copy for each message. High-frequency image streams can be recorded with far less overhead, reducing the chance of drops. +* **Backported Capability:** Provides in Humble what ROS 2 Iron/Jazzy offer out-of-the-box. Under the hood, it uses ROS 2’s rclcpp composition API similarly to how it’s done in newer releases, but packaged for Humble compatibility. (One early implementation of this idea was released as a standalone package `rosbag2_composable_recorder` for Galactic/Humble – this tool builds on that concept, tailored for Humble Hawksbill). +* **Same Bag Format:** The tool still writes standard rosbag2 files (SQLite3 by default, or MCAP if configured). The output is a normal `.db3` (or `.mcap`) bag that can be played back with `ros2 bag play` or analyzed with existing tools. The difference lies in how data arrives to the recorder, not in the file format. +* **Flexible Topic Selection:** It supports recording all topics or a specified list of topics, just like `ros2 bag`. You can configure which topics to record via parameters or launch arguments. This means you can target only the high-bandwidth topics with the IPC recorder if desired, and let the rest use standard methods. +* **Composability:** The recorder is designed to be launched as a component in a container. You have control over how to integrate it with your system. For example, you might launch multiple camera drivers and the recorder together in one process, or start your drivers first and then load the recorder into that process on-demand. Launch files are provided (and can be customized) to simplify these use cases. +* **Reduced Dropped Messages:** By avoiding the DDS hop, the recorder can keep up with the publishers more easily. Disk write speed remains a factor (you can still saturate the disk if you try to record *too much* data), but the elimination of serialization delays buys significant headroom. In tests with this approach, recording stability is greatly improved – e.g., users have reported the recorder keeping up with dual 30 Hz camera streams that previously showed periodic frame drops. +* **When to Use vs. Standard Recorder:** Use the Humble IPC recorder **when you have high-bandwidth or high-frequency topics** that the standard `ros2 bag` struggles with. If you’re recording video streams, depth images, large point clouds, or any scenario where CPU usage for bagging is high, this tool is likely beneficial. On the other hand, for low-bandwidth topics (odometry, small sensor readings, etc.) or quick debugging sessions, the default recorder might be sufficient and simpler (since it doesn’t require setting up a special launch). In summary, for serious data logging on Humble – especially multi-camera rigs or multi-sensor fusion – this tool can make the difference between a successful bag and a bag with gaps in data. + +It’s worth noting that this solution is a **stopgap specific to Humble**. If you upgrade to ROS 2 Jazzy or later, you can achieve the same intra-process recording by using the built-in rosbag2 components (without this external package). In fact, the package’s maintainers suggest not using it on ROS 2 distributions where the feature is already native. But for those on Humble, the IPC bag recorder is a game-changer for data collection tasks. + +## Installation and Setup + +To get started with the Humble IPC Bag Recorder, you will need to build it from source in your Humble workspace. It’s assumed you have an existing ROS 2 Humble installation and a workspace for building packages (e.g. `~/ros2_ws` with a `src/` directory). + +**Requirements:** + +* ROS 2 Humble Hawksbill (Ubuntu 22.04 or equivalent platform with ROS 2 Humble). +* Developer tools to build ROS 2 packages (C++ compiler, colcon, etc.). +* The `rosbag2` core packages and `rclcpp_components` should be installed (if you have ROS 2 binaries installed, you have these). The recorder will link against rosbag2 libraries. +* This package’s source code, available via git. + +**Installation Steps:** + +1. **Clone the Repository:** Obtain the source code for the humble IPC recorder. (Replace `` with the actual repository if needed): + + ```bash + mkdir -p ~/ros2_ws/src + cd ~/ros2_ws/src + git clone https://github.com//humble_ipc_rosbag.git + ``` + + This will create a package (e.g. named `humble_ipc_rosbag` or similar) in your workspace. It contains the recorder node implementation and example launch files. + +2. **Install Dependencies:** Ensure all necessary ROS dependencies are available. You can use rosdep to install any missing ones: + + ```bash + cd ~/ros2_ws + rosdep install --from-paths src --ignore-src -r -y + ``` + + This will check the package.xml for dependencies like `rclcpp`, `rosbag2`, etc. On a standard Humble installation, you likely already have them. If not, rosdep will use apt to install what's needed. + +3. **Build the Package:** Use colcon to build your workspace: + + ```bash + colcon build --symlink-install + ``` + + (Add `--parallel-workers N` if you want to limit parallel jobs, or `--cmake-args -DCMAKE_BUILD_TYPE=Release` for an optimized build.) If the build succeeds, you should have the recorder node plugin available in the workspace. + +4. **Source the Setup:** Before using the recorder, source the new workspace overlay: + + ```bash + source ~/ros2_ws/install/setup.bash + ``` + + This makes ROS 2 aware of the new package and its executables/launch files. + +After these steps, the Humble IPC recorder is ready to use. The package provides launch files to help you run the recorder node in various ways, as described below. + +## Usage Guide: Intra-Process Rosbag Recording + +Using the IPC bag recorder involves launching the recorder node in the same process as the target publisher nodes. There are a couple of ways to do this, ranging from one-shot launch files that start everything together, to loading the recorder into an already running container. We’ll go through the provided methods and general tips for usage. + +### Launching the Recorder via Launch Files + +The package comes with two example launch files – let’s call them `simple.launch.py` and `load.launch.py` – to facilitate common use cases. + +* **`simple.launch.py`:** This launch file is designed to start up both your sensor nodes *and* the recorder in one go, within a single process. It’s particularly geared towards launching one or more ZED camera nodes alongside the recorder. You can adapt it to other camera types or nodes if they support composition. The launch will create a container (an instance of `rclcpp_components` container) and then load each camera driver component and the recorder component into it. + + For example, if you have a ZED camera, you might use: + + ```bash + ros2 launch humble_ipc_rosbag simple.launch.py \ + cam_names:=[zed_front] cam_models:=[zed2] cam_serials:=[] + ``` + + In this example, `cam_names`, `cam_models`, and `cam_serials` are arguments the launch file expects (specifically for ZED cameras) to identify and configure the camera nodes. Under the hood, `simple.launch.py` will: + + 1. Start a composable container (if not already running). + 2. Load the ZED camera driver component (with `use_intra_process_comms=True`) into that container. + 3. Load the IPC recorder component into the same container, configured to immediately start recording all topics (or specific topics you set). + + The result is that the ZED camera’s topics (e.g. `/zed_front/zed_node/left/image_raw`, `/.../point_cloud/cloud_registered`) are being published in the same process where the recorder is subscribed. All image frames and point cloud messages are delivered via intra-process to the recorder, greatly reducing the load. This is a convenient way to launch if you want to begin recording as soon as the sensors start. Once you Ctrl-C this launch, recording stops and the bag file will be finalized. + +* **`load.launch.py`:** This launch is used to **add** the recorder into an *already running* container process. It’s useful in scenarios where you might want to start the sensors first (perhaps letting them warm up or stabilize) and then begin recording at a specific moment. With `load.launch.py`, the assumption is you have a container running elsewhere (for example, maybe you launched a multi-camera container using another launch file or command). You will pass the name of that container into `load.launch.py`, which will then load the recorder node into that existing process. + + Typical usage might look like: + + ```bash + # In one terminal, start your sensor container (e.g., using the vendor's launch file for multiple cameras) + ros2 launch zed_multi_camera zed_multi_camera.launch.py + + # In another terminal, list running component containers to get the container name + ros2 component list + # Suppose it outputs something like "/zed_multi_camera/zed_container" + + # Now launch the recorder into that container + ros2 launch humble_ipc_rosbag load.launch.py container_name:=/zed_multi_camera/zed_container + ``` + + The `load.launch.py` will find the container by name and use the ROS 2 composition service to load the recorder component into it. Once loaded, the recorder (if configured to start immediately) will begin subscribing and recording the topics from the sensors in that process. + + One advantage of this method is control: you can start and stop the recorder without shutting down the sensors. For instance, to stop recording, you could unload the recorder component: + + ```bash + # Find the component ID of the recorder in the container + ros2 component list + # (this will show something like "/zed_multi_camera/zed_container : : humble_ipc_rosbag/Recorder") + ros2 component unload /zed_multi_camera/zed_container + ``` + + Unloading the component stops the recording (and closes the bag file properly). You could later load it again to start a new bag. *(Alternatively, you could leave the recorder running but use its service interface to start/stop recording; more on that shortly.)* + +Both provided launch files can be customized. If you are using a different camera or sensor, you can edit them or write a new launch file. The key is that you include: + +* A `ComposableNodeContainer` (if one isn’t already running). +* Your publisher nodes as `ComposableNode` entries (with intra-process enabled). +* The recorder as a `ComposableNode` (with appropriate parameters). + +**Example (conceptual):** If we wanted to compose an Intel RealSense camera node (if it supports composition) with the recorder, a launch might look like: + +```python +from launch import LaunchDescription +from launch_ros.actions import ComposableNodeContainer, LoadComposableNodes +from launch_ros.descriptions import ComposableNode + +def generate_launch_description(): + container = ComposableNodeContainer( + name='camera_container', + namespace='', + package='rclcpp_components', + executable='component_container', + output='screen' + ) + realsense_node = ComposableNode( + package='realsense2_camera', + plugin='realsense2_camera::RealSenseNodeFactory', + name='realsense', + namespace='camera', + parameters=[{'enable_pointcloud': True}], + extra_arguments=[{'use_intra_process_comms': True}] + ) + recorder_node = ComposableNode( + package='humble_ipc_rosbag', + plugin='humble_ipc_rosbag::RecorderNode', # hypothetical class name + name='ipc_recorder', + parameters=[{'record_all_topics': True}], + extra_arguments=[{'use_intra_process_comms': True}] + ) + load_components = LoadComposableNodes( + target_container=container, + composable_node_descriptions=[realsense_node, recorder_node] + ) + return LaunchDescription([container, load_components]) +``` + +In this hypothetical snippet, we launch a container, then load a RealSense camera component and the IPC recorder component into it. All topics published by the RealSense driver (e.g. `/camera/color/image_raw`, `/camera/depth/points`) would be recorded via intra-process. This illustrates how one could set up their own launch if needed. + +### Configuring Topics and Parameters + +By default, the recorder can be set to record all topics or a specified list. The configuration is done through ROS 2 parameters on the recorder node. Some important parameters include: + +* **`record_all` / `all_topics`:** Boolean to indicate whether to record all available topics. If `True`, the recorder will subscribe to every topic it discovers (except perhaps some excluded internal topics). If `False`, you should provide a list of specific topics. +* **`topics`:** An explicit list of topic names to record. Use this if you only want to record certain topics. For example, you might list `/camera1/image_raw`, `/camera2/image_raw` and ignore everything else. +* **`bag_file` / `bag_name`:** The output file name or prefix for the bag. If not set, a default name (like `rosbag2_`) will be used. You can set this to organize your bag files (e.g. `experiment1.bag`). +* **`storage_id`:** Storage plugin to use (`sqlite3` by default, or `mcap` for the newer format). Humble defaults to SQLite3; you can use MCAP for better performance on large recordings. +* **`max_cache_size`:** Size in bytes of the cache before writing to disk. By default, rosbag2 may cache 100 MB or so. Increasing this can help smooth bursts of data (at the cost of memory). Setting it to 0 means no caching (write every message immediately). For high-speed recording, a cache is usually beneficial, so leaving this as default or even raising it might help. +* **`start_paused` / `start_recording_immediately`:** Some implementations allow starting in a paused state, requiring a service call or user input to actually begin recording. In our Humble recorder, the launch files by default set the recorder to start right away (to mimic `ros2 bag record` behavior). If you prefer to manually trigger start, you could configure this parameter and then call a ROS service or use a provided script to start. +* **`topics_ignore` / `exclude`:** If available, you could specify topics to exclude (this is analogous to `ros2 bag record --exclude` option in newer versions). For example, you might exclude `/tf` or other high-frequency but unneeded topics. + +These parameters can be set via a YAML config file passed to the launch, or directly in the launch file as shown in the example. The package likely includes a default `params.yaml` with documentation on each setting. For instance, you might find in `config/params.yaml`: + +```yaml +humble_ipc_rosbag: + ros__parameters: + record_all_topics: true + bag_file: "" + storage_id: "sqlite3" + max_cache_size: 100000000 # 100 MB + # topics: ["/camera1/image_raw", "/camera2/image_raw"] # example if not record_all +``` + +**Selecting Topics to Record:** If you only care about certain topics (to save space or bandwidth), set `record_all_topics: false` and list those topics. Just ensure those topics will be published in the same process. If you accidentally omit a needed topic, the recorder won’t subscribe to it. Conversely, if `record_all_topics` is true, the recorder will latch onto every topic it sees – which could include some you don’t need. In a multi-camera container that might be fine (you likely want all camera topics). But if you have other nodes in that container (e.g. some intermediate processing nodes), you might record internal topics unnecessarily. Tailor the config to your use case. + +### Starting and Stopping the Recording + +When using the provided launch files, recording begins as described (immediately on launch for `simple.launch.py`, or immediately on loading via `load.launch.py` unless configured otherwise). Stopping the recording can be done in a few ways: + +* **Graceful Shutdown:** If you launched everything together, hitting Ctrl-C will stop the launch, which in turn will shutdown the recorder node. Rosbag2 recorder is designed to close the bag file properly on shutdown (writing metadata, etc.), so the resulting bag should be usable. Always ensure the process actually terminates so that the bag is finalized (if a node hangs for some reason, you might need to Ctrl-C twice or kill, which could leave an incomplete bag). + +* **Unloading the Component:** As shown earlier, if you loaded the recorder into an existing container, you can unload it without killing the whole process. The `ros2 component unload` command is the clean way to remove the recorder. This triggers its shutdown routines just like Ctrl-C would. After unloading, the container and other nodes keep running, so you could later load the recorder again to start a new recording session. When unloading, you refer to the container and the component ID (which you can get via `ros2 component list`). For example: + + ```bash + ros2 component unload /camera_container 1 + ``` + + (if the recorder was listed with ID 1 in that container). + +* **Service Calls (if supported):** The IPC recorder node may provide services like `/start_recording` and `/stop_recording` (this was the case in earlier implementations). If available, you could start the recorder in a paused state (parameter `start_paused: true` or similar) and then call: + + ```bash + ros2 service call /start_recording std_srvs/srv/Trigger "{}" + ``` + + to begin recording, and later call `/stop_recording` to stop without unloading. This approach requires that the node advertise those services. Check the package documentation or running `ros2 interface list | grep record` to see if such services exist. If they do, this can be a very flexible way to control recording programmatically (for instance, start recording only when a certain event happens, via a ROS service call from another node). + +* **Keyboard Controls:** Newer rosbag2 versions have keyboard controls (space to pause, etc.) – in this Humble tool, since it’s running as a component, you typically won’t have an interactive console in the same way. So rely on the above methods for control. + +Regardless of how you stop, once the recorder node stops, the bag file is closed. It will typically be created in the directory from where you launched the command (unless you specified an output path). + +### Validating the Recorded Bag + +After you have recorded data using the IPC recorder, you should verify that everything was captured as expected. Since the output is a standard rosbag2, you can use the usual ROS 2 bag utilities for validation: + +* **Inspect with ros2 bag info:** This will show you what topics were recorded, the number of messages, frequency, duration, etc. For example: + + ```bash + ros2 bag info experiment1 + ``` + + You should see all the topics you intended to record. Pay special attention to the message count and frequency. If you recorded a camera at 30 Hz for 60 seconds, you expect around 1800 messages. If the count is significantly lower, it could indicate dropped frames. Ideally, with the IPC recorder, the count will match the publish count (aside from perhaps the first few frames during startup or last few at shutdown). + +* **Play back or visualize:** You can do a quick playback test: + + ```bash + ros2 bag play experiment1 + ``` + + Perhaps run rqt or RViz to visualize the images/points from the bag to ensure they look okay. This is more of a sanity check that the data is not corrupted and that timestamps are correct. Because intra-process affects only how data gets to the recorder, the recorded data itself should be identical to what a normal recorder would have stored (only with fewer drops). The bag file structure is the same, so playback should work normally. + +* **Check metadata and performance:** The `ros2 bag info` will also tell you the storage format and any splitting of bag files. If your recording was long or large, rosbag2 might split the bag into multiple files (e.g., `experiment1_0.db3`, `experiment1_1.db3`, etc.). Ensure all expected splits are present. You can also open the metadata YAML (`experiment1/metadata.yaml`) to see if end\_time matches your recording end, etc. + +If you encounter an empty bag or missing data: + +* Ensure that the recorder actually started (if using services to start, maybe it never got triggered). +* Double-check that the topics you wanted were in the same container. If a topic was published in a different process than the recorder, the recorder would not see it at all (since in our usage we likely disabled discovery from outside to focus on intra-process, or simply never loaded a subscription for it). The symptom would be 0 messages recorded for that topic or it not appearing at all. +* Look at the console output during recording. The recorder might log warnings if it cannot subscribe to a topic or if something goes wrong. For instance, if you accidentally had two recorder nodes or a leftover one, you might see errors about failing to write. +* If using the `topics` parameter list, verify the names exactly match (including namespaces) of the publishers. + +In practice, once set up, the Humble IPC recorder tends to “just work” and you’ll immediately notice the lower CPU usage. It’s good to confirm that by observing system metrics: CPU should be much lower compared to the standard recorder scenario, since no expensive serialization is happening in the recording path. This improved efficiency directly translates to more reliable data capture. + +## Example Use Case: Recording Multiple Cameras + +To illustrate the value of the Humble IPC Bag Recorder, let’s consider a concrete scenario: **recording multiple high-resolution cameras simultaneously on ROS 2 Humble**. Suppose a robot is equipped with two 1080p RGB cameras (say, Intel RealSense or webcams) publishing at 30 frames per second, and also a depth camera or LiDAR producing large point clouds. Recording all of these streams is challenging: + +In a traditional setup (using `ros2 bag record` in a separate process), each image (which could be \~1920×1080×3 bytes ≈ 6 MB per frame uncompressed) has to be sent to the recorder process. At 30 FPS, that’s \~180 MB/s per camera of raw data. Two cameras double that to 360 MB/s, not even counting point clouds. Even if images are compressed, the data rate is still significant. The CPU would spend a lot of time just handling these images – packaging them into DDS messages and copying them across processes. It’s no surprise that frames would drop: the recorder simply can’t keep up, especially on modest hardware (e.g., a single-board computer on a robot). + +Now, using the IPC recorder approach: + +* We launch both camera drivers and the recorder in one process using composition. Each camera’s driver publishes images, which the recorder subscribes to via a shared memory pointer. There’s **no serialization** of the image data for transport. Instead, the image memory is handed off to the recorder directly inside the process. +* The CPU usage plummets compared to before. Instead of two processes each handling 6 MB × 30 FPS of copy, those copies are eliminated. The system mainly needs to handle the disk writing. As long as the disk (or SSD) can sustain the write throughput (which 360 MB/s might still be high but perhaps using compression or downsampling can mitigate), the recorder will not be the bottleneck. +* The benefit scales with number of cameras: if you had 4 cameras, the savings are even more dramatic. In fact, a ROS 2 developer mentioned being able to record data from **8 HD cameras simultaneously (over 2.6 GB/s)** by splitting loads and presumably using techniques like this in a distributed fashion. While 2.6 GB/s is extreme, the principle stands – removing inter-process overhead is essential to hit those levels. + +With our two-camera example, after switching to the Humble IPC recorder, you’d likely observe that the frame drops disappear or are vastly reduced. The recorded bag will have close to 30 Hz for each camera consistently. The **latency** from capture to disk is also reduced, meaning if you were timestamping or triggering events based on frame capture, the recorded timestamps more accurately reflect the real timing (no big delays due to queuing in DDS). In mission-critical logging (e.g. recording all sensor data during a test run of an autonomous vehicle or robot), this fidelity is crucial. + +Another example: the **Stereolabs ZED cameras** are stereo + depth cameras that produce multiple high-bandwidth topics (left/right images, depth map or point cloud, etc.). Stereolabs provides a composition-based launch for multiple ZEDs specifically to leverage IPC. By adding the IPC recorder into that same process (as our provided launch does), you can record all ZED outputs with minimal overhead. This approach *“achieves zero-copy data transfer for efficient data exchange”* and *“enhances performance and reduces resource consumption by minimizing data copying”*. Essentially, the heavy data published by each camera does not bounce between processes, making multi-camera recording on one machine feasible. + +In summary, the IPC recorder shines in use cases with **multiple high-data-rate sources**. Whether it’s an array of cameras, a combination of cameras and LiDAR, or any sensor setup pushing lots of bytes, running the recorder in-process ensures that recording no longer becomes the weakest link. You can fully exercise your sensors at max resolution and frame rate, confident that the logger can keep up (within the limits of your disk write speed). This allows robotics developers to gather richer datasets on ROS 2 Humble, which otherwise might have required upgrading to a newer ROS 2 or using custom hacky solutions. + +## Summary + +Recording large volumes of data in ROS 2 Humble is greatly improved by leveraging intra-process communication. **ROS 2’s IPC and node composition** allow publishers and subscribers to exchange messages without going through the middleware, avoiding redundant copies and serialization. The default rosbag2 in Humble, however, doesn’t utilize this, which can lead to dropped messages and high overhead for high-bandwidth topics. + +The **Humble IPC Bag Recorder** addresses this gap by providing a composable recorder node for ROS 2 Humble. By running the recorder in the same process as the data producers, it achieves near zero-copy recording. This results in significantly lower CPU usage and more reliable logging of topics like HD images and point clouds. We discussed how to set up and use this tool: you clone and build the package in a Humble workspace, then either launch your sensors and recorder together in one container or load the recorder into an existing container when needed. You can configure it to record all or specific topics, and control the start/stop as needed. The recorded bag files are in the standard format and can be used like any other. + +**When to use this tool:** If you’re running ROS 2 Humble and need to record high-bandwidth data streams, the IPC recorder is likely the right choice. It’s particularly useful for multi-sensor systems (multiple cameras, etc.) where the standard recorder can’t keep up. It essentially brings Humble up to feature-parity (for recording) with newer ROS 2 versions, without requiring an upgrade. On the other hand, if you have already moved to ROS 2 Iron or beyond, you can use the built-in rosbag2 component capability instead (and this tool isn’t necessary). For low-data topics, the default recorder might suffice, but using the IPC recorder has little downside besides the extra setup. + +By adopting intra-process communication for rosbag recording, ROS 2 users can capture datasets that were previously very difficult to obtain on Humble. This empowers better testing, debugging, and data analysis for robots with rich sensor suites. In robotics, data is king – and with the right tools, you won’t have to compromise on collecting it. The Humble IPC Bag Recorder is one such tool, enabling robust data recording on ROS 2 Humble Hawksbill. + +## See Also + +- [Building ROS2 Custom Packages](/wiki/common-platforms/ros/ros2-custom-package) +- [ROS Introduction](/wiki/common-platforms/ros/ros-intro) +- [Building an iOS App for ROS2 Integration – A Step-by-Step Guide](/wiki/common-platforms/ros2_ios_app_with_swift) + +## Further Reading + +- [ROS 2 composition and ZED ROS 2 Wrapper](https://www.stereolabs.com/docs/ros2/ros2-composition) + - Stereolabs’ documentation page explains how to run multiple ZED camera components inside a single ComposableNodeContainer with use_intra_process_comms=True. +- [rosbg2_composable_recorder](https://github.com/berndpfrommer/rosbag2\_composable\_recorder) + - An earlier, experimental project that turns the standard rosbag2 Recorder into a composable node. Reviewing its design and issues offers additional insight into the trade‑offs of zero‑copy bagging, complementing the Humble‑specific IPC recorder covered here. + +## References + +\[1] Yu-Hsin Chan and Haoyang He, “*humble_ipc_rosbag*,” GitHub repository, 2025. Accessed: May 4, 2025. [Online]. Available: [https://github.com/MRSD-Wheelchair/humble_ipc_rosbag](https://github.com/MRSD-Wheelchair/humble_ipc_rosbag) + +\[2] Open Robotics, “*Intra-Process Communications in ROS 2*,” *ROS 2 Design Articles*, 2020. Accessed: May 4, 2025. \[Online]. Available: [https://design.ros2.org/articles/intraprocess_communications.html](https://design.ros2.org/articles/intraprocess_communications.html) + +\[3] Open Robotics, “*ROS 2 Jazzy Jalisco Release Highlights: rosbag2 Components*,” *ROS 2 Documentation*, 2024. Accessed: May 4, 2025. \[Online]. Available: [https://docs.ros.org/en/jazzy/Releases/Release-Jazzy-Jalisco.html#rosbag2](https://docs.ros.org/en/jazzy/Releases/Release-Jazzy-Jalisco.html#rosbag2) + +\[4] ROS 2 Tutorial, “*Recording and Playing Back Data (Humble)*,” *docs.ros.org*, 2022. Accessed: May 4, 2025. \[Online]. Available: [https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Recording-And-Playing-Back-Data/Recording-And-Playing-Back-Data.html](https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Recording-And-Playing-Back-Data/Recording-And-Playing-Back-Data.html)