ROS2 - C++工程创建和工程解析

news2025/1/17 21:53:03

ROS2 系列文章目录

文章目录

  • ROS2 系列文章目录
  • 前言
  • 1. 安装构建工具colcon
    • 1.1 简介
    • 1.2 安装colcon
  • 2. 创建工作空间
    • 2.1 创建一个工作目录
  • 3 创建一个C++软件包
  • 4. 编写发布者节点
    • 4.1 使用wget获取一个模板
    • 4.2 代码解读
    • 4.2 添加依赖项
    • 4.3 修改CmakeLists.txt
  • 5. 编写订阅者节点
    • 5.1 获取代码
    • 5.2 代码解读
    • 5.2 添加依赖项
    • 5.3 修改CMakeLists.txt
  • 6. 编译和运行
    • 6.1 检查是否缺少依赖项
    • 6.2 编译
    • 6.3 编译成功
    • 6.4 加载setup.sh
    • 6.5 运行
  • 7. 参考链接


前言

本文主要介绍如何创建、编写、编译、运行ROS2的C++工程。
开发环境是基于Ubuntu的Linux虚拟机。

本文中很多的学习资源来源于ROS2官网


1. 安装构建工具colcon

1.1 简介

colcon是对 ROS 构建工具catkin_make、catkin_make_isolated、catkin_tools和ament_tools的迭代。

据我初步了解ROS1使用的是catkin_make、catkin_make_isolated、catkin_tools。而ROS2使用的是ament_tools,这带来一些问题就是ROS1和ROS2都属于ROS但是却使用不同的构建工具进行编译。

后面ROS想要做一个通用的构建工具,同时这个工具和特定的所支持的构建系统命名无关,然后就有了现在的colcon。

对于嵌入式开发者来说,可以理解为其是一个在Cmake之上的工具。它能够调用cmake去管理和编译c++功能,也能调用其他类似于Cmake之类的构建系统去进行别的构建。

同时它支持了例如创建ROS2的C++工程以及python工程等之类的功能。

在本文中它的主要作用是创建c++工程以及执行编译。

1.2 安装colcon

sudo apt install python3-colcon-common-extensions

2. 创建工作空间

ROS工作空间是一个具有特定结构的目录,通常有一个“src”子目录,用于存放ROS软件包源代码。

这个工作空间大家可以理解为就是ROS2 约定好的一个工作目录,例如它有一个build目录,是用于存放编译过程产生的信息的。

那么为啥要约定这么一个目录呢?我个人认为有以下几点。

  1. 按照这种方式去创建目录,更方便我们后续的开发,毕竟是官方提供的建议肯定是有一定依据的。
  2. 大家想一想如果我们随意按照自己喜欢的风格去创建目录结构,那么使用colcon编译过程中它怎么知道把这些信息放在哪里?如果再自己一点点的去配置不是会很麻烦吗。

2.1 创建一个工作目录

mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src

在创建一个C++的软件包前,我们需要先手动创建一个工作空间,用于存放ROS2的软件包。通过执行上方的指令我们创建出来一个ros2_ws的工作空间,其中/ros2_ws/src 是用于存放软件包的。

额外说明
当我们通过colcon进行编译后,将会出现新的文件夹
(可以等编译之后查看)

例如我们通过

git clone https://github.com/ros2/examples src/examples -b rolling

将一个examples存储库克隆到~/ros2_ws/src 目录下。

.
└── src
    └── examples
        ├── CONTRIBUTING.md
        ├── LICENSE
        ├── rclcpp
        ├── rclpy
        └── README.md

现在src目录中ROS2的示例代码目录结构。
在工作空间的根目录下,运行

colcon build --symlink-install

构建完成后将会看到如下的目录结构

.
├── build
├── install
├── log
└── src

“build”目录将是存储中间文件的地方。对于每个包,将在其中创建一个子文件夹,例如在该子文件夹中调用 CMake
“install”目录是每个软件包将被安装到的位置。默认情况下,每个软件包将被安装到一个单独的子目录中。
"log" 目录包含关于每次 colcon 调用的各种日志信息。

3 创建一个C++软件包

创建完工作空间 ros2_w 后,我们需要在ros2_w/src/目录下去创建我们自己的C++包

ros2 pkg create --build-type ament_cmake --license Apache-2.0 cpp_pubsub

进入到ros2_w/src/ 目录,然后执行上述指令。 cpp_pubsub是包名, ament_cmake代表我们创建的是一个使用C++工程

在这里插入图片描述

在这里插入图片描述

4. 编写发布者节点

当按照上面的操作成功创建出软件包后。

我们需要进入ros2_ws/src/cpp_pubsub/src 目录,这是CMake包中包含可执行文件的源文件所在的目录。
所以我们写的源代码要放到该目录

4.1 使用wget获取一个模板

wget -O publisher_lambda_function.cpp https://raw.githubusercontent.com/ros2/examples/rolling/rclcpp/topics/minimal_publisher/lambda.cpp

进入到ros2_ws/src/cpp_pubsub/src 目录,运行上述指令,将从github上拉去一个publisher的demo,对应文件名为publisher_lambda_function.cpp

如果拉不到可以直接创建一个publisher_lambda_function.cpp的文件,并将下面的代码拷贝进去
源代码如下所示:

#include <chrono>
#include <memory>
#include <string>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

/* This example creates a subclass of Node and uses a fancy C++11 lambda
* function to shorten the callback syntax, at the expense of making the
* code somewhat more difficult to understand at first glance. */

class MinimalPublisher : public rclcpp::Node
{
public:
  MinimalPublisher()
  : Node("minimal_publisher"), count_(0)
  {
    publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
    auto timer_callback =
      [this]() -> void {
        auto message = std_msgs::msg::String();
        message.data = "Hello, world! " + std::to_string(this->count_++);
        RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
        this->publisher_->publish(message);
      };
    timer_ = this->create_wall_timer(500ms, timer_callback);
  }

private:
  rclcpp::TimerBase::SharedPtr timer_;
  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
  size_t count_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalPublisher>());
  rclcpp::shutdown();
  return 0;
}

4.2 代码解读

第一段

#include <chrono>
#include <memory>
#include <string>
//上面包含了C++库的标准头文件
#include "rclcpp/rclcpp.hpp"   //包含了ROS2系统中最常见的部分接口
#include "std_msgs/msg/string.hpp" //包含了用于发布消息的内置消息类型
using namespace std::chrono_literals;

chrono_literals它引入了 chrono 库中的字面量后缀,使得我们能够直接使用时间字面量(比如 1s, 500ms, 2min 等)
来指定时间常量,而不需要显式地使用
std::chrono::seconds,
std::chrono::milliseconds 等类型
第二段

一个继承自rclcpp::Node的MinimalPublisher新节点类。
后续代码中的this 都是指代该新节点
class MinimalPublisher : public rclcpp::Node
MinimalPublisher()
  : Node("minimal_publisher"), count_(0)
在构建MinimalPublisher对象时,会创建一个名为minimal_publisher的节点,
并将count_变量初始化为0
 publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
创建一个String消息类型的topic,并初始化其队列大小为10
auto timer_callback =
     [this]() -> void {
       auto message = std_msgs::msg::String();
       message.data = "Hello, world! " + std::to_string(this->count_++);
       RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
       this->publisher_->publish(message);
     };
   timer_ = this->create_wall_timer(500ms, timer_callback);
 }
 
声明一个名为timer_callback 的 lambda 函数。
它执行当前对象 this 的按引用捕获,不接受输入参数并返回 void。
timer_callback 函数创建一个 String 类型的新消息,使用所需的字符串设置其数据并发布它。
RCLCPP_INFO 宏确保将每条发布的消息打印到控制台。

最后初始化timer_,使其每500ms调用一次上面的回调函数
private:
 rclcpp::TimerBase::SharedPtr timer_;
 rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
 size_t count_;
这些是一些私有变量字段的声明

第三段

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalPublisher>());
  rclcpp::shutdown();
  return 0;
}

main函数中则是负责初始化让节点运行,并最终关闭ROS2的环境

rclcpp::spin 是一个函数,它将节点传递给它并让节点保持运行,直到程序被显式地关闭
(如用户按下 Ctrl+C)退出。

spin 函数在 ROS 2 中用来不断执行节点中的回调函数并维持节点生命周期,
通常用于订阅回调、定时器回调等。个人觉得spin里面的实现应该是类似于一个while(1){ }

4.2 添加依赖项

返回到 ros2_ws/src/cpp_pubsub 目录
打开package.xmlament_cmake构建工具依赖项后添加一个新的行,并粘贴与我哦们节点的包含语句相对应的以下依赖

<depend>rclcpp</depend>
<depend>std_msgs</depend>

这声明了在构建和执行其代码时,该软件包需要rclcppstd_msgs

4.3 修改CmakeLists.txt

  1. 打开 CMakeLists.txt 文件。在现有的依赖项 find_package(ament_cmake REQUIRED) 下面,添加以下行:

    find_package(rclcpp REQUIRED)
    find_package(std_msgs REQUIRED)
    
  2. 之后添加可执行文件,并将其命名为talker,这样我们就可以使用ros2 run运行节点了

    add_executable(talker src/publisher_lambda_function.cpp)
    ament_target_dependencies(talker rclcpp std_msgs)
    
  3. 最后,添加install(TARGETS…)部分,以便ros2 run可以找到我们的可执行文件

    install(TARGETS
      talker
      DESTINATION lib/${PROJECT_NAME})
    
  4. 最终

    cmake_minimum_required(VERSION 3.5)
    project(cpp_pubsub)
    
    # Default to C++14
    if(NOT CMAKE_CXX_STANDARD)
      set(CMAKE_CXX_STANDARD 14)
    endif()
    
    if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
      add_compile_options(-Wall -Wextra -Wpedantic)
    endif()
    
    find_package(ament_cmake REQUIRED)
    find_package(rclcpp REQUIRED)
    find_package(std_msgs REQUIRED)
    
    add_executable(talker src/publisher_lambda_function.cpp)
    ament_target_dependencies(talker rclcpp std_msgs)
    
    install(TARGETS
      talker
      DESTINATION lib/${PROJECT_NAME})
    
    ament_package()
    	
    

5. 编写订阅者节点

5.1 获取代码

返回ros2_ws/src/cpp_pubsub/src以创建下一个节点。在终端中输入以下代码:

wget -O subscriber_lambda_function.cpp https://raw.githubusercontent.com/ros2/examples/rolling/rclcpp/topics/minimal_subscriber/lambda.cpp

确保publisher_lambda_function.cppsubscriber_lambda_function.cpp都存在

如果拉不到可以直接创建一个subscriber_lambda_function.cpp的文件,并将下面的代码拷贝进去
源代码如下所示:

#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

class MinimalSubscriber : public rclcpp::Node
{
public:
  MinimalSubscriber()
  : Node("minimal_subscriber")
  {
    auto topic_callback =
      [this](std_msgs::msg::String::UniquePtr msg) -> void {
        RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
      };
    subscription_ =
      this->create_subscription<std_msgs::msg::String>("topic", 10, topic_callback);
  }

private:
  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalSubscriber>());
  rclcpp::shutdown();
  return 0;
}

5.2 代码解读

第一段

#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
在头文件包含这块两边基本上都是相同的含义

第二段

class MinimalSubscriber : public rclcpp::Node
订阅者节点是继承Node 创建一个新的类MinimalSubscriber
public:
  MinimalSubscriber()
  : Node("minimal_subscriber")
  {
    auto topic_callback =
      [this](std_msgs::msg::String::UniquePtr msg) -> void {
        RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
      };
    subscription_ =
      this->create_subscription<std_msgs::msg::String>("topic", 10, topic_callback);
  }
  
订阅者和发布者相比没有了定时器,因为订阅者只是在数据发布到topic时进行响应。
topic_callback”函数接收通过主题发布的字符串消息数据,并使用“RCLCPP_INFO”宏将其简单地写入控制台。
private:
  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
  私有变量

第三段

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalSubscriber>());
  rclcpp::shutdown();
  return 0;
}

main函数中则是负责初始化让节点运行,并最终关闭ROS2的环境

rclcpp::spin 是一个函数,它将节点传递给它并让节点保持运行,直到程序被显式地关闭
(如用户按下 Ctrl+C)退出。

spin 函数在 ROS 2 中用来不断执行节点中的回调函数并维持节点生命周期,
通常用于订阅回调、定时器回调等。个人觉得spin里面的实现应该是类似于一个while(1){ }

5.2 添加依赖项

由于此节点与发布者节点具有相同的依赖项,因此无需向package.xml添加任何新内容。

5.3 修改CMakeLists.txt

重新打开 CMakeLists.txt 文件,并在发布者的条目下方为订阅者节点添加可执行文件和目标

add_executable(listener src/subscriber_lambda_function.cpp)
ament_target_dependencies(listener rclcpp std_msgs)

install(TARGETS
  talker
  listener
  DESTINATION lib/${PROJECT_NAME})

6. 编译和运行

6.1 检查是否缺少依赖项

像此次我们依赖了rclcppstd_msgs软件包,当然这两个软件包在安装ROS2时就顺带安装了。
但是后续可能有些其他的软件包需要我们单独安装。

所以我们可以先使用rosdep检查是否缺少依赖项,防止后面编译出错排查大半天。

这个指令要在ros2_ws/目录下执行

rosdep install -i --from-path src --rosdistro rolling -y

在这里插入图片描述

6.2 编译

在工作空间的根目录ros2_ws中,编译我们的软件包,

colcon build --packages-select cpp_pubsub

这代表着编译cpp_pubsub包。 相当于执行了该软件包的cmake并进行make.

编译过程中碰到的问题

在这里插入图片描述
编译过程中碰到了这种问题。
这个错误提示我们没有缺少catklink_tools,这个一般是ROS1中使用。
但是这里为啥会使用到我也不是特别清楚,我们就安装就完事了,缺啥补啥。

执行下方的指令进行安装。

pip3 install catkin_pkg
pip3 install lark

上面的指令最好是能在访问外网的情况下执行,如果没有梯子的话,可能会提示下方的问题
在这里插入图片描述

那么可以参考
【Python学习:解决pip总是WARNING: Retrying (Retry(total=4…】
如果上面的方式还不行大家可以检查下自己虚拟机的http和https的代理地址是否正确。

6.3 编译成功

在这里插入图片描述
就是编译的有点慢

6.4 加载setup.sh

打开一个新的终端后,要先加载设置文件。设置必要的环境变量,确保ROS2相同能够正常运行。
环境变量包括 ROS 2 的安装路径、库路径、消息和服务的定义路径等。

在ros2_ws 目录下

source ./install/setup.sh

6.5 运行

运行发布节点(新终端)

ros2 run cpp_pubsub talker

运行监听节点(新终端)

ros2 run cpp_pubsub listener

在这里插入图片描述

7. 参考链接

Writing a simple publisher and subscriber (C++)【官方教程】
Using colcon to build packages【官方教程】
Python学习:解决pip总是WARNING: Retrying (Retry(total=4…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2256348.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【5G】架构 Architecture

5G网络架构受到了多个因素的影响。首先是为云端实现做好准备&#xff0c;其次是应对比之前更大的数据速率和更低的时延&#xff0c;启用新服务的能力&#xff0c;以及特别是在初期阶段与长期演进&#xff08;LTE&#xff09;的互操作需求。所有这些因素都对5G架构产生了影响。除…

vue2+html2canvas+js PDF实现试卷导出和打印功能

1.首先安装 import html2canvas from html2canvas; import { jsPDF } from jspdf; 2.引入打印插件print.js import Print from "/assets/js/print"; Vue.use(Print) // 打印类属性、方法定义 /* eslint-disable */ const Print function (dom, options) {if (…

图像滤波和卷积的不同及MATLAB应用实例

滤波与卷积在图像处理中都是非常重要的运算&#xff0c;但它们有着明显的区别。以下是滤波与卷积的主要不同点&#xff0c;并附带一个MATLAB实例来展示两者在图像处理中的效果差异。 一、滤波与卷积的不同 定义与目的&#xff1a; 1&#xff09;滤波&#xff1a;滤波是一种信…

Java集合(三)- Stack Queue

目录 一、Stack & Queue概述 1.1、Queue 1.2、Deque 二、方法剖析 2.1、addFirst() 2.2、addLast() 2.3、pollFirst() 2.4、pollLast() 2.5、peekFirst() 2.6、peekLast() 一、Stack & Queue概述 Java里有一个叫做Stack的类&#xff0c;却没有叫做Queue的类…

数据结构理论

内容来源青岛大学数据结构与算法课程&#xff0c;链接&#xff1a;数据结构与算法基础&#xff08;青岛大学-王卓&#xff09;_哔哩哔哩_bilibili 绪论 数据结构概述 数据结构和算法的定义&#xff1a;我们如何把现实中大量而复杂的问题以特定的数据类型和特定的存储结构保存…

008.精读《Apache Paimon Docs - Table w/o PK》

文章目录 1. 引言2. 基本概念2.1 定义2.2 使用场景 3. 流式处理3.1 自动小文件合并3.2 流式查询 4. 数据更新4.1 查询4.2 更新4.3 分桶附加表 5 总结 1. 引言 通过本文&#xff0c;上篇我们了解了Apache Paimon 主键表&#xff0c;本期我们将继续学习附加表&#xff08;Append…

硬件选型规则

光源选型: 先用型号中带H的&#xff0c;没有的选标准的. 光源和光源控制器的搭配需要确保接口一致。 根据型号表中的最佳工作距离和相机的尺寸。 光源控制器选型&#xff1a; 首先选择海康风格系列光源控制器考虑与光源的接口匹配。功率应该满足接近光源功率。检查是否退市…

数据分析系列---requests的使用

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 主环境和虚拟环境 主环境是电脑上安装的python环境 虚拟环境在项目中可以实现环境的隔离&#xff0c;假设DemoA和DemoB分别用到了某个三方库1.0和2.0版本&#xff0c;那么在一个…

selenium常见接口函数使用

博客主页&#xff1a;花果山~程序猿-CSDN博客 文章分栏&#xff1a;测试_花果山~程序猿的博客-CSDN博客 关注我一起学习&#xff0c;一起进步&#xff0c;一起探索编程的无限可能吧&#xff01;让我们一起努力&#xff0c;一起成长&#xff01; 目录 1. 查找 查找方式 css_s…

深度学习之预备知识

深度学习是关于优化的学习。对一个带有参数的模型&#xff0c;找到其中能拟合数据最好的模型。 一、数据操作 张量&#xff1a;表示一个由数值组成的数组&#xff0c;这个数组可能有多个维度。具有一个轴的张量对应数学上的向量&#xff0c;具有两个轴的张量对应数学上的矩阵&…

【论文相关】期刊/会议 信息检索——IEEE各期刊投稿要求(待完善)

[吐槽] 外文期刊/会议网站的真的很难找&#xff0c;想要了解的信息很难检索&#xff1b;比如 旗下子期刊官网、子期刊具体要求不同等问题。 TIFS期刊 (IEEE Transactions on Information Forensics and Security) 期刊官网链接&#xff1a;https://mc.manuscriptcentral.com/…

ElementUI:el-tabs 切换之前判断是否满足条件

<div class"table-card"><div class"card-steps-class"><el-tabsv-model"activeTabsIndex":before-leave"beforeHandleTabsClick"><el-tab-pane name"1" label"基础设置"><span slot&…

入职体检心电图不合格的常见情况

心电图&#xff08;ECG&#xff09;是通过记录心脏的电活动来评估心脏健康状况的一项常见检查。它是体检中常见的检查项目之一&#xff0c;尤其对于从事需要高度体力活动的职业&#xff08;如医护、公安、消防等&#xff09;或对心脏健康有特别要求的行业&#xff0c;心电图的结…

释放 AI 潜能:掌握提问策略,让 AI 事半功倍

在这个 AI 大模型快速迭代的时代&#xff0c;人人都能免费使用 AI。然而&#xff0c;很多人却发现&#xff0c;自己与 AI 互动的效果并不理想。这并非 AI 的问题&#xff0c;而是方法不对。输入决定输出&#xff0c;只有掌握正确的提问策略&#xff0c;才能真正释放 AI 助手的生…

AI赋能:构建安全可信的智能电子档案库

在档案的政策与法规上&#xff0c;《中华人民共和国档案法》2020年修订新增&#xff0c;对电子档案的合法要件、地位和作用、安全管理要求和信息化系统建设等方面作出了明确规定&#xff0c;保障数字资源的安全保存和有效利用。 日前&#xff0c;国家档案局令第22号公布《电子…

【计算机网络】实验12:网际控制报文协议ICMP的应用

实验12 网际控制报文协议ICMP的应用 一、实验目的 验证ping命令和tracert命令的工作原理。 二、实验环境 Cisco Packet Tracer模拟器 三、实验过程 1.构建网络拓扑并进行信息标注&#xff0c;将所需要配置的IP地址写在对应的主机或者路由器旁边&#xff0c;如图1所示。 图…

【C语言】程序设计--算法

文章目录 1. 判断两个数的大小并交换2. 计算三角形面积3. 根据x的值计算y4. 字符大小写转换5. 百钱百鸡问题6. 计算公式y的值7. 输出所有的水仙花数8. 计算n的阶乘9. 下三角数据10. 斐波那契数列11. 学生成绩统计12. 数组的平均值1. 判断两个数的大小并交换 介绍: 从键盘输入…

使用IntelliJ IDEA开发Gradle插件并发布到本地

博主所用软件版本为&#xff1a; IntelliJ IDEA 2024.1.4 (Community Edition) Android Studio Ladybug Feature Drop | 2024.2.2 Beta 1 1、制作gradle插件&#xff08;IntelliJ IDEA 2024.1.4&#xff09; 新建groovy工程&#xff0c;File–>New–>Project… 右键点…

Trimble X9三维激光扫描仪高效应对化工厂复杂管道扫描测绘挑战【沪敖3D】

化工安全关系到国计民生&#xff0c;近年来随着化工厂数字化改革不断推进&#xff0c;数字工厂逐步成为工厂安全管理的重要手段。而化工管道作为工厂设施的重要组成部分&#xff0c;由于其数量多、种类繁杂&#xff0c;一直是企业管理的重点和难点。 传统的化工管廊往往缺乏详…

如何实现远程对主机进行关机、重启操作

WGCLOUD是一款开源免费的运维工具&#xff0c;使用简单&#xff0c;安装方便 我们主要通过 WGCLOUD监控系统的指令下发模块&#xff0c; 来实现关机和重启主机的操作 这里以Linux主机为例说明&#xff0c;Windows也是一样的&#xff0c;只是重启主机的命令不同而已&#xff0c…