当我们想到使用 C 或 C++ 进行开发时,我们立即将其与工业系统和嵌入式设备的编程联系起来。确实,该语言在该领域得到广泛使用,这不可避免地包括其在机器人领域的应用。

在这种情况下,毫无疑问,ROS(机器人操作系统) 是最著名的框架,并且可能是使用最广泛的框架之一。它能够将不同的硬件组件集成到一个系统中,使它们能够无缝地协同工作。这使得机器人开发更加高效,因为它允许在各种机器人应用的项目中重用每个组件的软件。

这就是 Conan 团队欣喜地推出 Conan 与 ROS 集成的原因。我们团队的许多成员都来自工业工程领域,我们对这个领域充满热情,因此能够通过 Conan 为机器人领域做出贡献让我们非常高兴。

我们知道rosdep 是 ROS 生态系统中一个众所周知的工具,它有助于将依赖项安装到系统中。但是,在系统级别管理某些 C 和 C++ 依赖项并不总是最佳方法,我们相信 Conan 提供了一些优势,并且可以作为替代方案引入给希望受益于 Conan 特性和其开源包集合的开发人员。

在这篇文章中,我们将讨论构成 ROS 开发的不同组件,它们的工作原理,以及 Conan 如何集成到这些项目中以管理第三方库。

ROS2 包和工作区的快速入门

让我们从一个简短的概述开始,以帮助那些不太熟悉 ROS2 生态系统的人。

ROS 将代码组织成包,每个包都包含一个package.xml 文件,其中包含一些元数据、可执行文件、库或其他资源(如启动文件)。这些包通常在一个**工作区**中管理,允许开发人员构建和运行他们的机器人应用程序。工作流程的关键组件是

  • **CMake 用于构建**: ROS2 严重依赖 CMake 作为其 C/C++ 包的构建系统,使用 CMakeLists.txt 文件定义构建逻辑。

  • **Ament 构建工具**: Ament 构建工具是一个 CMake 宏和函数框架,它标准化并简化了包级别的任务,例如链接依赖项、安装和导出工件或与 ROS 特定工具(如rosidl)集成。

  • **Colcon**: 它是协调工作区内多个包构建的主要构建工具。它能够检查包及其依赖项,并按正确的顺序启动构建。它还可以将额外的包覆盖到它们现有的工作区之上,而不会破坏核心系统。

构建 ROS 包的小示例

对于不熟悉 ROS 的读者,我们想展示如何创建和构建 ROS 包以了解该过程。我们将在后面的示例中展示 Conan 集成。

我们需要一个**安装了 ROS2 Humble 版本的 Linux 环境**。如果您在其他系统上运行,或者仅仅为了方便起见,您还可以使用此 Dockerfile 构建和运行命令

Dockerfile

FROM osrf/ros:humble-desktop
RUN apt-get update && apt-get install -y \
curl \
python3-pip \
git \
ros-humble-nav2-msgs \
&& rm -rf /var/lib/apt/lists/*
RUN pip3 install --upgrade pip && pip3 install conan
RUN conan profile detect
CMD ["bash"]

您可以使用docker build -t conanio/ros-humble .构建并运行 Docker 镜像,并使用docker run -it conanio/ros-humble运行它

首先,我们创建一个工作区navigation_ws文件夹,设置 ROS 安装的环境,并创建一个包

$ mkdir /home/navigation_ws && cd /home/navigation_ws
$ source /opt/ros/humble/setup.bash
$ ros2 pkg create --build-type ament_cmake --node-name navigator navigation_package

这些是工作区中应该包含的文件。如本文所述,您可以检查它们以了解内容。

navigation_ws/
  navigation_package/
    CMakeLists.txt
    package.xml
    src/
      navigator.cpp

现在我们可以调用colcon来执行构建

$ colcon build --packages-select navigation_package
Starting >>> navigation_package
Finished <<< navigation_package [1.21s]

Summary: 1 package finished [1.60s]

最后,在执行二进制文件之前,我们必须为可执行文件设置环境(以便它能够找到可能具有的任何共享库),然后我们执行它

$ source install/setup.bash
$ ros2 run navigation_package navigator
hello world navigation_package package

将 Conan 依赖项集成到 ROS 示例中

假设我们想使用 Conan 包含一个外部库。在这种情况下,我们将创建一个导航节点,该节点从yaml文件中发送位置目标到我们的移动机器人。

此示例的代码可以在https://github.com/conan-io/examples2/tree/main/examples/tools/ros/rosenv/navigation_ws找到

navigation_ws/navigation_package/locations.yaml

locations:
  - name: "Kitchen"
    x: 3.5
    y: 2.0
  - name: "Living Room"
    x: 1.0
    y: -1.0
  - name: "Bedroom"
    x: -2.0
    y: 1.5

我们将使用来自 Conan Center 的yaml-cpp。为此,我们需要在项目的 CMakeLists.txt 旁边包含一个 conanfile.txt 文件

navigation_ws/navigation_package/conanfile.txt

[requires]
yaml-cpp/0.8.0

[generators]
CMakeDeps
CMakeToolchain
ROSEnv

如您所见,我们在[requires]部分列出了我们的依赖项,并且我们还添加了所需的生成器,这些生成器将包中文件的信息(库、头文件、可执行文件等)“翻译”成适合构建系统或使用者环境的文件格式。

  • **CMake 生成器**将生成文件,以便可以使用find_package()yaml-cpp包包含到我们的项目中。

  • **ROSEnv 生成器**将创建一个包含执行构建所需的环境变量的 shell 脚本。

CMakeLists.txt 中,我们需要包含**ROS 客户端库**(rclcpprclcpp_action)、ROS nav2_msgs和来自 Conan 的yaml-cpp。这是使用每个依赖项的find_package命令完成的,如下所示

navigation_ws/navigation_package/CMakeLists.txt

cmake_minimum_required(VERSION 3.8)
project(navigation_package)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# ROS dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_action REQUIRED)
find_package(nav2_msgs REQUIRED)

# Conan dependencies
find_package(yaml-cpp REQUIRED)

add_executable(navigator src/navigator.cpp)

target_compile_features(navigator PUBLIC c_std_99 cxx_std_17)  # Require C99 and C++17
ament_target_dependencies(navigator rclcpp rclcpp_action nav2_msgs yaml-cpp)

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

ament_package()

注意:
如果我们必须将 Conan 包作为传递依赖项传播给依赖于此包的其他 ROS 包(如果navigation_package是库)

其他 ROS 包navigation_packageyaml-cpp Conan 包

我们可以使用 ament 助手ament_export_dependencies()导出 Conan 目标,就像我们对普通 ROS 包所做的那样。您可以在我们的文档中阅读更多相关信息:https://docs.conan.org.cn/2/integrations/ros.html

为了执行新的构建,首先我们需要清理工作区

$ rm -rf build install log

现在我们像这样安装yaml-cpp Conan 包

$ conan install navigation_package/conanfile.txt --build=missing --output-folder=install/conan
======== Input profiles ========
Profile host:
[settings]
arch=x86_64
build_type=Release
compiler=gcc
compiler.cppstd=gnu17
compiler.libcxx=libstdc++11
compiler.version=11
os=Linux
...
======== Computing dependency graph ========
yaml-cpp/0.8.0: Not found in local cache, looking in remotes...
yaml-cpp/0.8.0: Checking remote: conancenter
yaml-cpp/0.8.0: Downloaded recipe revision 720ad361689101a838b2c703a49e9c26
Graph root
    conanfile.txt: /navigation_ws/navigation_package/conanfile.txt
Requirements
    yaml-cpp/0.8.0#720ad361689101a838b2c703a49e9c26 - Downloaded (conancenter)

======== Computing necessary packages ========
yaml-cpp/0.8.0: Main binary package '8631cf963dbbb4d7a378a64a6fd1dc57558bc2fe' missing
...
yaml-cpp/0.8.0: Found compatible package '13be611585c95453f1cbbd053cea04b3e64470ca': compiler.cppstd=17
Requirements
    yaml-cpp/0.8.0#720ad361689101a838b2c703a49e9c26:13be611585c95453f1cbbd053cea04b3e64470ca#971e8e22b118a337b31131ab432a3d5b - Download (conancenter)

======== Installing packages ========

-------- Downloading 1 package --------
yaml-cpp/0.8.0: Retrieving package 13be611585c95453f1cbbd053cea04b3e64470ca from remote 'conancenter'
yaml-cpp/0.8.0: Package installed 13be611585c95453f1cbbd053cea04b3e64470ca
yaml-cpp/0.8.0: Downloaded package revision 971e8e22b118a337b31131ab432a3d5b

======== Finalizing install (deploy, generators) ========
conanfile.txt: Writing generators to /navigation_ws/install/conan
...
conanfile.txt: Generated ROSEnv Conan file: conanrosenv.sh
Use 'source /navigation_ws/install/conan/conanrosenv.sh' to set the ROSEnv Conan before 'colcon build'
conanfile.txt: Generating aggregated env files
conanfile.txt: Generated aggregated env files: ['conanbuild.sh', 'conanrun.sh']
Install finished successfully

使用此安装命令,Conan 执行了一些操作

  1. Conan Center(OSS 包贡献的中央存储库)中**搜索**适合您的配置的包。
  2. 将包**下载**到您计算机上的本地 Conan 缓存中。
  3. install/conan 文件夹中**生成**ROS 项目所需的环境和 CMake 文件。

最后,让我们将节点的代码添加到 navigator.cpp 文件中

navigation_ws/my_package/src/navigator.cpp

#include <string>
#include <vector>

#include <rclcpp/rclcpp.hpp>
#include <nav2_msgs/action/navigate_to_pose.hpp>
#include <rclcpp_action/rclcpp_action.hpp>

#include <yaml-cpp/yaml.h>

using NavigateToPose = nav2_msgs::action::NavigateToPose;


class YamlNavigationNode : public rclcpp::Node {
public:
    YamlNavigationNode(const std::string &yaml_file_path) : Node("yaml_navigation_node") {
        // Create action client
        action_client_ = rclcpp_action::create_client<NavigateToPose>(this, "navigate_to_pose");

        // Read locations from YAML file
        RCLCPP_INFO(this->get_logger(), "Reading locations from YAML...");
        if (!loadLocations(yaml_file_path)) {
            RCLCPP_ERROR(this->get_logger(), "Failed to load locations.");
            return;
        }

        sendAllGoals();
    }

private:
    struct Location {
        std::string name;
        double x;
        double y;
    };

    std::vector<Location> locations_;
    rclcpp_action::Client<NavigateToPose>::SharedPtr action_client_;

    bool loadLocations(const std::string &file_path) {
        try {
            YAML::Node yaml_file = YAML::LoadFile(file_path);
            for (const auto &node : yaml_file["locations"]) {
                Location location;
                location.name = node["name"].as<std::string>();
                location.x = node["x"].as<double>();
                location.y = node["y"].as<double>();
                locations_.emplace_back(location);
            }
            return true;
        } catch (const std::exception &e) {
            RCLCPP_ERROR(this->get_logger(), "Error parsing YAML: %s", e.what());
            return false;
        }
    }

    void sendAllGoals() {
        for (const auto &location : locations_) {
            RCLCPP_INFO(this->get_logger(), "Sending goal to %s: (%.2f, %.2f)", location.name.c_str(), location.x, location.y);
            auto goal_msg = NavigateToPose::Goal();
            goal_msg.pose.header.frame_id = "map";
            goal_msg.pose.header.stamp = this->now();
            goal_msg.pose.pose.position.x = location.x;
            goal_msg.pose.pose.position.y = location.y;
            goal_msg.pose.pose.orientation.w = 1.0;
            action_client_->async_send_goal(goal_msg);
        }
        RCLCPP_INFO(this->get_logger(), "All goals have been sent.");
    }
};

int main(int argc, char **argv) {
    rclcpp::init(argc, argv);
    if (argc < 2) {
        RCLCPP_ERROR(rclcpp::get_logger("yaml_navigation_node"), "Usage: yaml_navigation_node <yaml_file_path>");
        return 1;
    }
    std::string yaml_file_path = argv[1];
    std::make_shared<YamlNavigationNode>(yaml_file_path);
    rclcpp::shutdown();
    return 0;
}

该节点将从 YAML 文件中读取位置,并将它们作为导航目标发送给我们的机器人。

我们可以像往常一样构建我们的 ROS 包,只需在之前源conanrosenv.sh文件即可。这将设置环境,以便 CMake 可以找到 Conan 生成的文件。

$ source install/conan/conanrosenv.sh
$ colcon build --packages-select navigation_package
Starting >>> navigation_package
Finished <<< navigation_package [16.3s]

Summary: 1 package finished [16.7s]

现在是时候运行我们的可执行文件了

$ source install/setup.bash
$ ros2 run navigation_package navigator navigation_package/locations.yaml
[INFO] [1732633293.207085200] [yaml_navigation_node]: Reading locations from YAML...
[INFO] [1732633293.208468700] [yaml_navigation_node]: Sending Kitchen goal (3.50, 2.00) to robot
[INFO] [1732633293.208949200] [yaml_navigation_node]: Sending Living Room goal (1.00, -1.00) to robot
[INFO] [1732633293.209244600] [yaml_navigation_node]: Sending Bedroom goal (-2.00, 1.50) to robot
[INFO] [1732633293.209548300] [yaml_navigation_node]: All goals have been sent.

结论

Conan 的新 ROS 集成提供了一种简洁的方式将 Conan 包合并到 ROS 包中。只需包含一个 conanfile,您就可以从 Conan Center 或您自己的私有服务器安装所需的库。

此外,与系统包管理器相比,像 Conan 这样的包管理器在 ROS 包的开发过程中提供了关键优势。使用 Conan,您可以**安装不同版本或类型的包,而不会干扰其他项目的依赖项**。您甚至可以**将自己的 Conan 包作为依赖项引入,而不会破坏开发工作流程**。

通过此集成,我们希望进一步改进 ROS 包的开发。您可以在我们的文档中找到有关此集成的更多信息:https://docs.conan.org.cn/2/integrations/ros.html

如果您有任何反馈,请在Conan 存储库中提交问题,以帮助我们改进开发体验。谢谢!