【ROS2】初级:客户端-创建和使用插件(C++)

news/2024/7/23 16:26:38 标签: c++, 开发语言

目标:学习使用 pluginlib 创建和加载一个简单的插件。

 教程级别:初学者

 时间:20 分钟

 目录

  •  背景

  •  先决条件

  •  任务

    • 创建基类包

    • 创建插件包

      2.1 插件的源代码

      2.2 插件声明 XML

      2.3 CMake 插件声明

    • 使用插件

    • 构建并运行

  •  摘要

 背景

这个教程源自于 http://wiki.ros.org/pluginlib,以及编写和使用一个简单插件教程。

pluginlib 是一个 C++库,用于在 ROS 包内加载和卸载插件。插件是动态可加载的类,它们从运行时库(即共享对象,动态链接库)中加载。使用 pluginlib,您无需显式地将应用程序与包含类的库链接——相反, pluginlib 可以在任何时候打开包含导出类的库,而无需应用程序事先了解库或包含类定义的头文件。插件对于扩展/修改应用程序行为很有用,而无需应用程序源代码。

 先决条件

本教程假设您具有基本的 C++知识,并且已经成功安装了 ROS 2。

 任务

在本教程中,您将创建两个新的包,一个定义基类,另一个提供插件。基类将定义一个通用的多边形类,然后我们的插件将定义特定的形状。

1 创建基类包

在您的 ros2_ws/src 文件夹中使用以下命令创建一个新的空包:

1bcb27eb4c645401e46d740293b3951c.png

cxy@ubuntu2404-cxy:~$ cd ~/ros2_ws/src
cxy@ubuntu2404-cxy:~/ros2_ws/src$ ros2 pkg create --build-type ament_cmake --license Apache-2.0 --dependencies pluginlib --node-name area_node polygon_base
going to create a new package
package name: polygon_base
destination directory: /home/cxy/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['cxy <cxy@todo.todo>']
licenses: ['Apache-2.0']
build type: ament_cmake
dependencies: ['pluginlib']
node_name: area_node
creating folder ./polygon_base
creating ./polygon_base/package.xml
creating source and include folder
creating folder ./polygon_base/src
creating folder ./polygon_base/include/polygon_base
creating ./polygon_base/CMakeLists.txt
creating ./polygon_base/src/area_node.cpp

打开您最喜欢的编辑器,编辑 ros2_ws/src/polygon_base/include/polygon_base/regular_polygon.hpp ,然后将以下内容粘贴进去:

#ifndef POLYGON_BASE_REGULAR_POLYGON_HPP  // 如果没有定义 POLYGON_BASE_REGULAR_POLYGON_HPP
#define POLYGON_BASE_REGULAR_POLYGON_HPP  // 那么就定义 POLYGON_BASE_REGULAR_POLYGON_HPP


namespace polygon_base  // 定义命名空间 polygon_base
{
  class RegularPolygon  // 定义一个名为 RegularPolygon 的类
  {
    public:
      virtual void initialize(double side_length) = 0;  // 定义一个纯虚函数 initialize,接受一个 double 类型的参数 side_length
      virtual double area() = 0;  // 定义一个纯虚函数 area,返回一个 double 类型的值
      virtual ~RegularPolygon(){}  // 定义一个虚析构函数


    protected:
      RegularPolygon(){}  // 定义一个受保护的构造函数
  };
}  // 结束命名空间 polygon_base


#endif  // 结束条件编译,如果已经定义了 POLYGON_BASE_REGULAR_POLYGON_HPP,那么就不会再执行这个头文件中的代码

上面的代码创建了一个名为 RegularPolygon 的抽象类。需要注意的一点是 initialize 方法的存在。使用 pluginlib 时,需要一个没有参数的构造函数,所以如果类需要任何参数,我们使用 initialize 方法将它们传递给对象。

我们需要使这个头文件可以被其他类使用,因此打开 ros2_ws/src/polygon_base/CMakeLists.txt 进行编辑。在 ament_target_dependencies 命令之后添加以下行:

install(
  DIRECTORY include/
  DESTINATION include
)

在 ament_package 命令之前添加这个命令:

ament_export_include_directories(
  include
)

我们稍后将返回此软件包以编写我们的测试节点。

2. 创建插件包

现在我们将为我们的抽象类编写两个非虚拟实现。在您的 ros2_ws/src 文件夹中使用以下命令创建第二个空包:

cf1910f2b093f5309461de0079e54e98.png

ros2 pkg create --build-type ament_cmake --license Apache-2.0 --dependencies polygon_base pluginlib --library-name polygon_plugins polygon_plugins
cxy@ubuntu2404-cxy:~/ros2_ws/src$ ros2 pkg create --build-type ament_cmake --license Apache-2.0 --dependencies polygon_base pluginlib --library-name polygon_plugins polygon_plugins
going to create a new package
package name: polygon_plugins
destination directory: /home/cxy/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['cxy <cxy@todo.todo>']
licenses: ['Apache-2.0']
build type: ament_cmake
dependencies: ['polygon_base', 'pluginlib']
library_name: polygon_plugins
creating folder ./polygon_plugins
creating ./polygon_plugins/package.xml
creating source and include folder
creating folder ./polygon_plugins/src
creating folder ./polygon_plugins/include/polygon_plugins
creating ./polygon_plugins/CMakeLists.txt
creating ./polygon_plugins/include/polygon_plugins/polygon_plugins.hpp
creating ./polygon_plugins/src/polygon_plugins.cpp
creating ./polygon_plugins/include/polygon_plugins/visibility_control.h
2.1 插件的源代码

打开 ros2_ws/src/polygon_plugins/src/polygon_plugins.cpp 进行编辑,并将以下内容粘贴进去:

// 引入polygon_plugins头文件
#include "polygon_plugins/polygon_plugins.hpp"
// 引入regular_polygon头文件
#include <polygon_base/regular_polygon.hpp>
// 引入cmath库,用于进行数学运算
#include <cmath>


// 定义polygon_plugins命名空间
namespace polygon_plugins
{
  // 定义Square类,继承自RegularPolygon类
  class Square : public polygon_base::RegularPolygon
  {
    public:
      // 重写initialize方法,用于初始化边长
      void initialize(double side_length) override
{
        side_length_ = side_length;
      }


      // 重写area方法,计算正方形的面积
      double area() override
{
        return side_length_ * side_length_;
      }


    protected:
      // 定义边长属性
      double side_length_;
  };


  // 定义Triangle类,继承自RegularPolygon类
  class Triangle : public polygon_base::RegularPolygon
  {
    public:
      // 重写initialize方法,用于初始化边长
      void initialize(double side_length) override
{
        side_length_ = side_length;
      }


      // 重写area方法,计算三角形的面积
      double area() override
{
        return 0.5 * side_length_ * getHeight();
      }


      // 定义getHeight方法,计算三角形的高
      double getHeight()
{
        return sqrt((side_length_ * side_length_) - ((side_length_ / 2) * (side_length_ / 2)));
      }


    protected:
      // 定义边长属性
      double side_length_;
  };
}


// 引入pluginlib库的class_list_macros头文件
#include <pluginlib/class_list_macros.hpp>


// 使用PLUGINLIB_EXPORT_CLASS宏,导出Square类
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Square, polygon_base::RegularPolygon)
// 使用PLUGINLIB_EXPORT_CLASS宏,导出Triangle类
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Triangle, polygon_base::RegularPolygon)

正方形和三角形类的实现相当直接:保存边长,并使用它来计算面积。唯一特定于 pluginlib 的部分是最后三行,它调用了一些神奇的宏,将类注册为实际的插件。让我们来看一下 PLUGINLIB_EXPORT_CLASS 宏的参数:

  1. 插件类的完全限定类型,在这种情况下为 polygon_plugins::Square 。

  2. 基类的完全限定类型,在这种情况下为 polygon_base::RegularPolygon 。

2.2 插件声明 XML

上述步骤允许在加载包含库时创建插件实例,但插件加载器仍然需要一种方法来找到该库,并知道在该库内引用什么。为此,我们还将创建一个 XML 文件,该文件与包清单中的特殊导出行一起,使我们的插件的所有必要信息可供 ROS 工具链使用。

创建 ros2_ws/src/polygon_plugins/plugins.xml ,使用以下代码:

<!-- 定义一个名为polygon_plugins的库 -->
<library path="polygon_plugins">
  <!-- 定义一个名为Square的类,该类的类型为polygon_plugins::Square,基类类型为polygon_base::RegularPolygon -->
  <class type="polygon_plugins::Square" base_class_type="polygon_base::RegularPolygon">
    <!-- 对Square类的描述 -->
    <description>这是一个正方形插件。</description>
  </class>
  <!-- 定义一个名为Triangle的类,该类的类型为polygon_plugins::Triangle,基类类型为polygon_base::RegularPolygon -->
  <class type="polygon_plugins::Triangle" base_class_type="polygon_base::RegularPolygon">
    <!-- 对Triangle类的描述 -->
    <description>这是一个三角形插件。</description>
  </class>
</library>

几点需要注意:

  1. `< library >标签提供了一个库的相对路径,该库包含我们想要导出的插件。在 ROS 2 中,这只是库的名称。在 ROS 1 中,它包含前缀 lib 或有时是 lib/lib (即 lib/libpolygon_plugins ),但在这里它更简单。`

  2. `< class >标签声明了我们想要从库中导出的插件。让我们来看一下它的参数:`

  • type :插件的完全限定类型。对我们来说,那就是 polygon_plugins::Square 。

  • base_class :插件的完全限定基类类型。对我们来说,那就是 polygon_base::RegularPolygon 。

  • description:插件的描述及其功能。

2.3 CMake 插件声明 

最后一步是通过 CMakeLists.txt 导出您的插件。这是 ROS 1 的一个变化,在 ROS 1 中,导出是通过 package.xml 完成的。在读取 find_package(pluginlib REQUIRED) 的行之后,将以下行添加到您的 ros2_ws/src/polygon_plugins/CMakeLists.txt 中:

pluginlib_export_plugin_description_file(polygon_base plugins.xml)

命令 pluginlib_export_plugin_description_file 的参数是:

  1. 包含基类的包,即 polygon_base 。

  2. 插件声明 xml 的相对路径,即 plugins.xml 。

# 设置CMake的最低版本要求为3.8
cmake_minimum_required(VERSION 3.8)
# 定义项目名称为polygon_plugins
project(polygon_plugins)


# 如果编译器是GNU C++或Clang,则添加编译选项
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(ament_cmake_ros REQUIRED)
find_package(polygon_base REQUIRED)
find_package(pluginlib REQUIRED)
# 导出插件描述文件
pluginlib_export_plugin_description_file(polygon_base plugins.xml)


# 添加库
add_library(polygon_plugins src/polygon_plugins.cpp)
# 添加库别名
add_library(polygon_plugins::polygon_plugins ALIAS polygon_plugins)
# 设置编译特性,要求C99和C++17
target_compile_features(polygon_plugins PUBLIC c_std_99 cxx_std_17)
# 设置包含目录
target_include_directories(polygon_plugins PUBLIC
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:include/${PROJECT_NAME}>)
# 设置目标依赖项
ament_target_dependencies(
  polygon_plugins
  "polygon_base"
  "pluginlib"
)


# 设置宏定义,使用dllexport而不是dllimport,适用于构建dll而不是使用它
target_compile_definitions(polygon_plugins PRIVATE "POLYGON_PLUGINS_BUILDING_LIBRARY")


# 安装include目录
install(
  DIRECTORY include/
  DESTINATION include/${PROJECT_NAME}
)
# 安装目标
install(
  TARGETS polygon_plugins
  EXPORT export_${PROJECT_NAME}
  ARCHIVE DESTINATION lib
  LIBRARY DESTINATION lib
  RUNTIME DESTINATION bin
)


# 如果构建测试,则找到测试依赖项
if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # 跳过检查版权的linter
  set(ament_cmake_copyright_FOUND TRUE)
  # 跳过cpplint(只在git仓库中工作)
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()


# 导出包含目录
ament_export_include_directories(
  "include/${PROJECT_NAME}"
)
# 导出库
ament_export_libraries(
  polygon_plugins
)
# 导出目标
ament_export_targets(
  export_${PROJECT_NAME}
)


# 打包
ament_package()

3. 使用插件

现在是时候使用插件了。这可以在任何包中完成,但我们这里要在base 包中进行。编辑 ros2_ws/src/polygon_base/src/area_node.cpp 以包含以下内容:

// 引入pluginlib的类加载器
#include <pluginlib/class_loader.hpp>
// 引入polygon_base的规则多边形
#include <polygon_base/regular_polygon.hpp>


int main(int argc, char** argv)
{
  // 避免未使用参数的警告
  (void) argc;
  (void) argv;


  // 创建一个类加载器,用于加载polygon_base::RegularPolygon类型的插件
  pluginlib::ClassLoader<polygon_base::RegularPolygon> poly_loader("polygon_base", "polygon_base::RegularPolygon");


  try
  {
    // 创建一个三角形的实例
    std::shared_ptr<polygon_base::RegularPolygon> triangle = poly_loader.createSharedInstance("polygon_plugins::Triangle");
    // 初始化三角形,边长为10.0
    triangle->initialize(10.0);


    // 创建一个正方形的实例
    std::shared_ptr<polygon_base::RegularPolygon> square = poly_loader.createSharedInstance("polygon_plugins::Square");
    // 初始化正方形,边长为10.0
    square->initialize(10.0);


    // 打印三角形的面积
    printf("Triangle area: %.2f\n", triangle->area());
    // 打印正方形的面积
    printf("Square area: %.2f\n", square->area());
  }
  catch(pluginlib::PluginlibException& ex)
  {
    // 如果插件加载失败,打印错误信息
    printf("The plugin failed to load for some reason. Error: %s\n", ex.what());
  }


  // 返回0,表示程序正常结束
  return 0;
}

ClassLoader `是关键类,需要理解,定义在` class_loader.hpp `头文件中https://github.com/ros/pluginlib/blob/ros2/pluginlib/include/pluginlib/class_loader.hpp :

  • 它是以基类为模板的,即 polygon_base::RegularPolygon 。

  • 第一个参数是基类包名称的字符串,即 polygon_base 。

  • 第二个参数是一个字符串,它是插件的完全限定基类类型,即 polygon_base::RegularPolygon 。

有多种方法可以实例化类的一个实例。在这个例子中,我们使用共享指针。我们只需要用插件类的完全限定类型来调用 createSharedInstance ,在这种情况下是 polygon_plugins::Square 。

重要提示:定义此节点的 polygon_base 包不依赖于 polygon_plugins 类。插件将动态加载,无需声明任何依赖。此外,我们正在用硬编码的插件名称实例化类,但您也可以使用参数等动态地进行实例化。

3. 构建并运行

导航回到您的工作区根目录, ros2_ws ,然后构建您的新包:

cxy@ubuntu2404-cxy:~/ros2_ws$ colcon build --packages-select polygon_base polygon_plugins
Starting >>> polygon_base
Finished <<< polygon_base [6.14s]                     
Starting >>> polygon_plugins
Finished <<< polygon_plugins [3.41s]                     


Summary: 2 packages finished [9.78s]

从 ros2_ws 开始,一定要找到设置文件:

source install/setup.bash

现在运行节点:它应该打印:

cxy@ubuntu2404-cxy:~/ros2_ws$ ros2 run polygon_base area_node
Triangle area: 43.30
Square area: 100.00

 摘要

恭喜你!你刚刚编写并使用了你的第一个插件。


http://www.niftyadmin.cn/n/5546621.html

相关文章

Python入门 2024/7/8

目录 数据容器 dict(字典&#xff0c;映射) 语法 定义字典字面量 定义字典变量 定义空字典 从字典中基于key获取value 字典的嵌套 字典的常用操作 新增元素 更新元素 删除元素 清空字典 获取全部的key 遍历字典 统计字典内的元素数量 练习 数据容器的通用操作…

Gunicorn+Flask+Docker初体验

1. 什么是 Gunicorn? Gunicorn 是一个 Python WSGI 服务器&#xff0c;可以用来部署 Python Web 应用程序。它提供了高性能、高可用性和灵活的配置选项。 2. 什么是 Flask? Flask 是一个轻量级的 Python Web 框架&#xff0c;提供了灵活的路由、模板引擎和请求对象等功能。…

第十八节 LLaVA如何按需构建LORA训练(视觉、语言、映射多个组合训练)

文章目录 前言一、基于llava源码构建新的参数1、添加lora_vit参数2、训练命令脚本设置二、修改源码,构建lora训练1、修改源码-lora训练2、LLM模型lora加载3、VIT模型加载4、权重冻结操作5、结果显示三、实验结果前言 如果看了我前面文章,想必你基本对整个代码有了更深认识。…

【Python】一文向您详细介绍 np.inner()

【Python】一文向您详细介绍 np.inner() 下滑即可查看博客内容 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我静心耕耘深度学习领域、真诚分享知识与智慧的小天地&#xff01;&#x1f387; &#x1f393; 博主简介&#xff1a;985高校的普通本硕&#xff0c;曾…

源代码加密软件哪家好?盘点2024年10款王炸级加密软件

看到一条新闻&#xff1a; 这个新闻让我想到了数据加密的重要性&#xff01;尤其是&#xff0c;源&#xff01;代&#xff01;码&#xff01; 源代码真的太重要了&#xff01;作为软件公司里的一个劳工&#xff0c;我比谁都清楚源代码的重要性&#xff01; 所以今天&#xff…

【leetcode】双指针算法题

文章目录 1.算法思想2.移动零3.复写零方法一方法二 4.快乐数5.盛水最多的容器方法一&#xff08;暴力求解&#xff09;方法二&#xff08;左右指针&#xff09; 6.有效三角形的个数方法一&#xff08;暴力求解&#xff09;方法二&#xff08;左右指针&#xff09; 7.两数之和8.…

C#中 有内置环形链表吗,如果有请给出使用示例代码

在C#中&#xff0c;并没有内置的环形链表数据结构&#xff0c;但是可以通过自定义实现来模拟环形链表的功能。一种常见的方法是创建一个环形链表的节点类&#xff0c;并在链表类中实现插入、删除、遍历等操作。 下面是一个简单的示例代码&#xff0c;展示了如何实现一个简单的…

GPT-4o无法取代程序员! IEEE研究显示,困难编码正确率仅为0.66%!

目录 01 0.66%-89%&#xff0c;惊人的成功率差异 02 实验评估 生成功能性正确代码的能力 研究动机&#xff1a; 研究方法&#xff1a; 研究实施&#xff1a; 研究结果&#xff1a; 有了ChatGPT&#xff0c;还需要人类程序员编码吗&#xff1f;上个月&#xff0c;一项发表…