当前位置: 首页 > article >正文

ROS 2机器人开发--第一个节点

目录

1 编写第一个节点

2 使用功能包组织C++节点

3 功能包结构分析

4 C++面向对象编程

5 C++新特性 (必看)

5.1 自动类型推导

5.2 智能指针

5.3 Lambda表达式


今天,我们将一起探索如何编写ROS 2程序,以及如何将代码编译成可执行文件。

1 编写第一个节点

首先在主目录下创建chapt2/ 文件夹 , 并用VS Code打开该文件夹

新建ros2_cpp_node.cpp文件,在文件中编写如图所示的代码。

新建CMakeLists.txt文件, 在chapt2/下编写同名文件,内容如图所示。

然后继续输入cmake .              make编译。

发现报错!

        这是因为代码中引用了rclcpp.hpp头文件,但该文件并不位于系统的默认头文件路径中,而是存储在ROS 2的安装目录下。为了使ros2_cpp_node能够正确找到并依赖该头文件,可以通过CMake指令在chapt2/CMakeLists.txt文件末尾添加相应的代码,如图所示。

# 指定 CMake 的最低版本要求
cmake_minimum_required(VERSION 3.8)

# 定义项目的名称
project(ros2_cpp)

# 添加一个可执行文件
# 第一个参数是目标名称(可执行文件名),第二个参数是源文件
add_executable(ros2_cpp_node ros2_cpp_node.cpp)

# 查找必要的 ROS 2 组件(rclcpp 是 ROS 2 的 C++ 客户端库)
# REQUIRED 表示如果找不到该组件,CMake 将报错并停止
find_package(rclcpp REQUIRED)

# 将 rclcpp 的头文件目录添加到目标的包含目录中
# PUBLIC 表示这些头文件目录也会被链接到目标的依赖项中
target_include_directories(ros2_cpp_node PUBLIC ${rclcpp_INCLUDE_DIRS})

# 将 rclcpp 的库链接到目标可执行文件中
target_link_libraries(ros2_cpp_node ${rclcpp_LIBRARIES})

        这三行代码分别完成了依赖库的查找、头文件路径的添加以及动态库的链接。其中,find_package指令能够在更广泛的目录中搜索依赖项。在找到rclcpp的头文件和库文件后,它还会递归地查找rclcpp所依赖的其他组件。

        添加后再次执行make 操作,就可以看到可执行文件 ros2_cpp_node 已经生成,命令及结果如图所示。

输入./ros2_cpp_node运行,命令及结果如代码如图所示。

2 使用功能包组织C++节点

        一个完整的机器人系统通常由多个功能模块构成,因此需要将多个功能包进行整合。为此,ROS 2开发者引入了“工作空间(Workspace)”的概念。使用VS Code打开chapt4/目录,通过集成终端进入chapt4目录,并输入以下代码。

mkdir -p ws00_helloworld/src #创建工作空间以及子级目录 src,工作空间名称可以自定义
cd ws00_helloworld #进入工作空间
colcon build #编译

        在上述命令中,-p参数用于递归创建目录。在创建ws00_helloworld目录后,还会在其下创建一个src文件夹,从而形成一个工作空间。你可能会疑惑,这不就是一个普通的文件夹吗?为何能称作工作空间?实际上,工作空间是一种概念和约定。在开发过程中,所有功能包都会被放置在src目录下,并在与src同级的目录中运行colcon进行构建。此时,生成的buildinstalllog等目录会与src保持同级。

        上述指令执行完毕,将创建ws00_helloworld目录,且该目录下包含build、install、log、src共四个子级目录。

先进入src,再输入如下所示的命令:

ros2 pkg create pkg01_helloworld_cpp --build-type ament_cmake --dependencies rclcpp --node-name helloworld

        ros2 pkg create 是用于创建功能包的指令,其中 pkg01_helloworld_cpp 是功能包的名称,而 --build-type ament_cmake 参数用于指定功能包的构建类型为 ament_cmake。从日志信息可以看出,该命令在当前目录下生成了一个名为 pkg01_helloworld_cpp 的文件夹,并在其中创建了一些默认的文件和子文件夹。在 VS Code 的左侧资源管理器中展开该文件夹,其目录结构如图所示。

进入pkg01_helloworld_cpp/src目录,该目录下有一helloworld.cpp文件,修改文件内容如下:

#include "rclcpp/rclcpp.hpp"
 
int main(int argc, char ** argv)
{
  // 初始化 ROS2
  rclcpp::init(argc,argv);
  // 创建节点
  auto node = rclcpp::Node::make_shared("helloworld_node");
  // 输出文本
  RCLCPP_INFO(node->get_logger(),"hello world!");
  // 释放资源
  rclcpp::shutdown();
  return 0;
}

        在步骤1中创建功能包时,所使用的命令已经自动生成并配置了必要的配置文件。然而,在实际应用中,通常需要手动编辑这些配置文件以满足特定需求。因此,这里对相关内容进行简要介绍。主要涉及的配置文件有两个,分别是功能包目录下的package.xmlCMakeLists.txt

package.xml文件内容如下:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>pkg01_helloworld_cpp</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="ubuntu64@todo.todo">ros2</maintainer>
  <license>TODO: License declaration</license>
 
  <buildtool_depend>ament_cmake</buildtool_depend>
 
  <!-- 所需要依赖 -->
  <depend>rclcpp</depend>
 
  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>
 
  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

CMakeLists.txt文件内容如下:

cmake_minimum_required(VERSION 3.8)
project(pkg01_helloworld_cpp)
 
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()
 
# find dependencies
find_package(ament_cmake REQUIRED)
# 引入外部依赖包
find_package(rclcpp REQUIRED)
 
# 映射源文件与可执行文件
add_executable(helloworld src/helloworld.cpp)
# 设置目标依赖库
ament_target_dependencies(
  helloworld
  "rclcpp"
)
# 定义安装规则
install(TARGETS helloworld
  DESTINATION lib/${PROJECT_NAME})
 
if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # comment the line when a copyright and license is added to all source files
  set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # comment the line when this package is in a git repo and when
  # a copyright and license is added to all source files
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()
 
ament_package()

编译,终端下进入到工作空间,执行如下指令:

colcon build

执行,终端下进入到工作空间,执行如下指令:

source install/setup.bash
ros2 run pkg01_helloworld_cpp helloworld

成功执行!!!

3 功能包结构分析

在 VS Code中的资源管理器中打开文件夹,并将其子文件夹完全展开,

可以看到如图所示的目录结构。

        该目录非常简洁,包含2个文件夹、3个文件,下面将逐 一介绍。

● include

        该目录用于存放C++ 的头文件,如果要编写头文件, 一般 都放置在这个目录下。

● src

代码资源目录,可以放置节点或其他相关代码。

● CMakeLists.txt

        该文件是C/C++ 构建系统CMake 的配置文件,在该文件中添加指令,即可完成依赖查 找、可执行文件添加、安装等工作。

● LICENSE

        该文件是功能包的许可证。创建该功能包时使用了--license Apache-2.0参数,这个文件内容就是Apache-2.0 的协议内容,在2.2.2节中有关于这个协议的简单介绍。

● package.xml

        该文件是功能包的清单文件,每个ROS 2的功能包都会包含这个文件,和2.2.2节中 Python 功能包中的package.xml 功能相同。它的更多用法会在下一小节进行讲解。

        当然,除了上面这些文件和文件夹,在实际开发中还可以添加其他目录和文件,比如用于放置地图的map目录、用于放置参数的config 目录等。

4 C++面向对象编程

C++是一门面向对象的语言,接下来通过一个例子去学习他。

#include<string>
#include"rclcpp/rclcpp.hpp"
class PersonNode :public rclcpp::Node
{
private:
    std::string name_;
    int age_;

public:
    PersonNode(const std::string &node_name, const std::string &name,
    const int &age) : Node(node_name) 
    {
        this->name_ =name;
        this->age_ =age; 
    };
    void eat(const std::string &food_name)
    {
        RCLCPP_INEO(this->get_logger(),"我是%s,今年%d岁,我现在正在%s",
            name_.c_str(),age_,food_name.c_str()); 
    };
};

int main(int argc,char **argv)
{
    rclcpp::init(argc,argv);
    auto node = td::make_shared<PersonNode>("cpp_node","法外狂徒张三",18); node->eat("北京烤鸭");
    rclcpp::spin(node);  
    rclcpp::shutdown();
    return 0;
}

        从上至下分析代码,首先引入了stringrclcpp/rclcpp.hpp两个头文件。其中,包含string的原因是节点名称和姓名需要用字符串形式表示。

        接着,通过class关键字定义了PersonNode类,并使其继承自rclcpp::Node。在类的内部,定义了private部分,包含姓名和年龄两个属性。

        在public部分,定义了PersonNode的构造函数,并传入节点名称、姓名和年龄作为参数。需要注意的是,参数传递采用的是引用方式,在std::string后添加&表示传递引用。引用类似于指针,但避免了不必要的数据复制,从而提高代码效率。std::string前的const修饰符限制变量为只读,防止其在方法内被意外修改,增强代码的安全性。构造函数后通过Node(node_name)调用父类的构造函数,传递节点名称参数,这与Python类似,但语法有所不同。

        在构造函数内部,通过this指针对姓名和年龄进行赋值。

        随后是eat方法的实现,该方法接收食物名称的引用作为参数。方法体中调用了ROS 2的日志模块来输出信息。由于RCLCPP_INFO采用C风格的格式化输出,因此需要调用c_str()name_food_name从字符串类型转换为C风格字符串。

        在main函数中,通过std::make_shared传入节点名、姓名和年龄,构造一个PersonNode对象,并将其智能指针赋值给node。接着调用nodeeat方法以及ROS 2的相关方法。

5 C++新特性 (必看)

5.1 自动类型推导

        自动类型推导体现在代码中就是auto 关键字。我们在实例化ROS 2节点的对象时使用 的就是auto, 它可以在给变量赋值时根据等号右边的类型自动推导变量的类型。

auto node=std::make_shared<rclcpp::Node> ("cpp_node"), 

5.2 智能指针

        智能指针是C++11引入的一种机制,用于自动管理动态分配的内存,从而避免内存泄漏和空指针等问题。C++11提供了三种智能指针类型:std::unique_ptrstd::shared_ptrstd::weak_ptr。在代码中,std::make_shared用于创建一个std::shared_ptr智能共享指针。接下来,我们将重点探讨std::shared_ptr

        在C语言中,指针用于存储其他变量的地址,而智能指针的功能也类似。然而,当指针指向的动态内存不再需要时,传统指针需要手动调用free来释放内存。如果忘记释放或过早释放,就会导致内存泄漏或空指针调用等问题。std::shared_ptr的智能之处在于它能够自动管理内存。它通过引用计数机制记录指向同一资源的指针数量。当引用计数为零时,std::shared_ptr会自动释放所管理的内存,从而有效避免了提前释放或忘记释放内存的问题。

    

5.3 Lambda表达式

        Lambda表达式是C++11引入的一种匿名函数形式,尽管它没有显式的名字,但可以像普通函数一样被调用。它拥有一套独特的语法结构,其格式如代码所示。

[capture list](parameters)->return type{function body}

        其中 ,capture list 表示捕获列表,可以用于捕获外部变量; parameters 表示参数列表; return_type 表示返回值类型; function body 表示函数体。


http://www.kler.cn/a/554633.html

相关文章:

  • HTTP SSE 实现
  • 【清华大学】DeepSeek从入门到精通完整版pdf下载
  • Could not initialize class io.netty.util.internal.Platfor...
  • nginx配置ssl
  • 《Spring实战》(第6版) 第3章 使用数据
  • 前端PDF转图片技术调研实战指南:从踩坑到高可用方案的深度解析
  • 基于Java爬虫获取1688商品分类信息(cat_get接口)的实现指南
  • 常用网络工具分析(ping,tcpdump等)
  • 如何平衡网络服务的成本、质量和可扩展性?
  • Nginx环境安装
  • 工业安卓主板在智慧粮仓设备中发挥着至关重要的作用
  • 黑盒测试、白盒测试、单元测试、集成测试、系统测试、验收测试的区别与联系
  • CVTE面经学习
  • AI训练中的常用指令
  • js安全开发值dombomxss重定向安全实例
  • uniapp 编译成鸿蒙应用包提示【未正确配置鸿蒙应用的包名】
  • 洛谷 P10726 [GESP202406 八级] 空间跳跃 C++ 完整题解
  • MATLAB学习之旅:数据插值与曲线拟合
  • LLM增强强化学习:开启智能决策的新篇章
  • 14天速成PAT-BASIC基础知识!