Skip to content

什么是ROS 2包?

1.什么是ROS 2包?

包是ROS 2代码的组织单元。如果您希望能够安装代码或与他人共享代码,那么您需要将其组织成一个包。通过包,您可以发布ROS 2工作并允许其他人轻松构建和使用它。

ROS 2中的包创建使用ament作为构建系统和colcon作为构建工具。您可以使用CMake或Python创建一个包,这两种方式得到官方支持,尽管还存在其他构建类型。

2.什么构成了一个ROS 2软件包?

ROS 2的Python和CMake软件包各自有其最小要求的内容:

CMake

  • CMakeLists.txt文件,描述了如何构建软件包内的代码

  • 包含该包的公共头文件的 include/<package_name> 目录

  • 包含有关该包的元信息的 package.xml 文件

  • 包含该包源代码的 src 目录

最简单的软件包的文件结构可能如下所示:

CMake

bash
my_package/
     CMakeLists.txt
     include/my_package/
     package.xml
     src/

Python

  • 包含有关该包的元信息的 package.xml 文件

  • 用于标记该包的 resource/<package_name> 文件

  • 当一个软件包包含可执行文件时,需要使用setup.cfg,以便ros2 run能够找到它们

  • setup.py包含了安装该软件包的指令

  • <package_name> - 与软件包同名的目录,被ROS 2工具用于查找软件包,包含__init__.py

最简单的软件包的文件结构可能如下所示:

Python

bash
my_package/
      package.xml
      resource/my_package
      setup.cfg
      setup.py
      my_package/
3.工作空间中的3个软件包

一个工作空间可以包含任意多个软件包,每个软件包位于自己的文件夹中。您还可以在一个工作空间中拥有不同构建类型的软件包(如CMake、Python等)。但不能嵌套软件包。

最佳实践是在工作空间中创建一个src文件夹,并在其中创建您的软件包。这样可以保持工作空间的顶层“清洁”。

一个简单的工作空间可能如下所示:

bash
workspace_folder/
    src/
      cpp_package_1/
          CMakeLists.txt
          include/cpp_package_1/
          package.xml
          src/

      py_package_1/
          package.xml
          resource/py_package_1
          setup.cfg
          setup.py
          py_package_1/
      ...
      cpp_package_n/
          CMakeLists.txt
          include/cpp_package_n/
          package.xml
          src/
4.创建一个包

首先, 配置您的 ROS 2 安装环境

让我们使用您在 先前教程 中创建的工作空间 ros2_ws 来创建您的新软件包。

在运行软件包创建命令之前,请确保您位于 src 文件夹中。

bash
cd ~/ros2_ws/src

在ROS 2中创建新包的命令语法如下:

bash
# python
ros2 pkg create --build-type ament_python <package_name>


# Cmake
ros2 pkg create --build-type ament_cmake <package_name>

在本教程中,您将使用可选参数--node-name,它将在包中创建一个简单的Hello World类型的可执行文件。

在终端中输入以下命令:

bash
# python
ros2 pkg create --build-type ament_python --node-name my_node my_package

#Cmake
ros2 pkg create --build-type ament_cmake --node-name my_node my_package

现在,您的工作空间的src目录中将有一个名为my_package的新文件夹。

运行该命令后,您的终端会返回以下消息:

bash
# python
going to create a new package
package name: my_package
destination directory: /home/user/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['<name> <email>']
licenses: ['TODO: License declaration']
build type: ament_python
dependencies: []
node_name: my_node
creating folder ./my_package
creating ./my_package/package.xml
creating source folder
creating folder ./my_package/my_package
creating ./my_package/setup.py
creating ./my_package/setup.cfg
creating folder ./my_package/resource
creating ./my_package/resource/my_package
creating ./my_package/my_package/__init__.py
creating folder ./my_package/test
creating ./my_package/test/test_copyright.py
creating ./my_package/test/test_flake8.py
creating ./my_package/test/test_pep257.py
creating ./my_package/my_package/my_node.py



# Cmake
going to create a new package
package name: my_package
destination directory: /home/user/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['<name> <email>']
licenses: ['TODO: License declaration']
build type: ament_cmake
dependencies: []
node_name: my_node
creating folder ./my_package
creating ./my_package/package.xml
creating source and include folder
creating folder ./my_package/src
creating folder ./my_package/include/my_package
creating ./my_package/CMakeLists.txt
creating ./my_package/src/my_node.cpp

您可以看到为新软件包自动生成的文件。

5.构建软件包

将软件包放入工作区尤其有价值,因为您可以通过在工作区根目录下运行colcon build一次性构建多个软件包。否则,您将需要逐个构建每个软件包。

返回工作区的根目录:

bash
cd ~/ros2_ws

现在你可以构建你的软件包:

bash
colcon build

从上个教程中你记得你的ros2_ws里也有ros_tutorials软件包。你可能已经注意到运行colcon build时还会构建turtlesim软件包。当你的工作区只有几个软件包时这没问题,但当有很多软件包时,colcon build会花费很长时间。

下次只构建my_package软件包,你可以运行:

bash
colcon build --packages-select my_package
6.导入设置文件

要使用您的新软件包和可执行文件,请首先打开一个新终端并导入您的主要ROS 2安装。

然后,在ros2_ws目录内运行以下命令以导入您的工作空间:

bash
source install/local_setup.bash

现在,您的工作空间已添加到路径中,您将能够使用您的新软件包的可执行文件。

7.使用软件包

要运行您使用--node-name参数在创建软件包时创建的可执行文件,请输入以下命令:

bash
ros2 run my_package my_node

将在终端返回一条消息:

bash
# python
Hi from my_package.




# Cmake
hello world my_package package
8.检查软件包内容

在 ros2_ws/src/my_package 内,您将看到 ros2 pkg create 自动创建的文件和文件夹:

bash
# Python
my_package  package.xml  resource  setup.cfg  setup.py  test

`my_node.py` 位于 `my_package` 目录中。这是您以后将放置所有自定义 Python 节点的位置。


# Cmake
CMakeLists.txt  include  package.xml  src
`my_node.cpp` 位于 `src` 目录中。这是您以后将放置所有自定义 C++ 节点的位置。
9.自定义 package.xml

10.总结

你创建了一个用于组织代码并方便他人使用的包。

您的软件包已自动填充所需文件,然后您使用 colcon 构建它,以便在本地环境中使用其可执行文件。

编写一个简单的发布者和订阅者(C++)

1.背景

节点 是在 ROS 图中进行通信的可执行进程。在本教程中,节点将以字符串消息的形式相互传递信息,通过一个 主题。这里使用的示例是一个简单的 "talker" 和 "listener" 系统;一个节点发布数据,另一个节点订阅该主题以接收数据。

这些示例中使用的代码可以在 这里 找到。

2.创建一个包

在一个新的终端中 初始化你的 ROS 2 安装,这样 ros2 命令才能正常工作。

进入在 上一个教程 中创建的 ros2_ws 目录。

记住,包应该创建在 src 目录中,而不是工作空间的根目录。所以,进入 ros2_ws/src 目录,并运行包创建命令:

bash
ros2 pkg create --build-type ament_cmake cpp_pubsub

终端会返回一条消息,确认已创建名为 cpp_pubsub 的包及其所有必要的文件和文件夹。

进入 ros2_ws/src/cpp_pubsub/src 目录。请注意,这是任何 CMake 包中包含可执行文件的源文件所在的目录。

bash
wget -O publisher_member_function.cpp https://raw.githubusercontent.com/ros2/examples/humble/rclcpp/topics/minimal_publisher/member_function.cpp

现在会有一个名为publisher_member_function.cpp的新文件。使用您喜欢的文本编辑器打开该文件。

Cpp
#include <chrono>
#include <functional>
#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 std::bind() to register a
* member function as a callback from the timer. */

class MinimalPublisher : public rclcpp::Node
{
  public:
    MinimalPublisher()
    : Node("minimal_publisher"), count_(0)
    {
      publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
      timer_ = this->create_wall_timer(
      500ms, std::bind(&MinimalPublisher::timer_callback, this));
    }

  private:
    void timer_callback()
    {
      auto message = std_msgs::msg::String();
      message.data = "Hello, world! " + std::to_string(count_++);
      RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
      publisher_->publish(message);
    }
    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;
}

2.1 检查代码

代码的开头包含了您将要使用的标准C++头文件。标准C++头文件之后是rclcpp/rclcpp.hpp的包含部分,它允许您使用ROS 2系统中最常见的组件。最后是std_msgs/msg/string.hpp,它包含了您将用于发布数据的内置消息类型。

Cpp
#include <chrono>
#include <functional>
#include <memory>
#include <string>

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

using namespace std::chrono_literals;

这些行表示节点的依赖关系。请记住,依赖关系必须添加到package.xmlCMakeLists.txt中,在下一节中您将进行这些操作。

下一行通过从rclcpp::Node继承来创建节点类MinimalPublisher。代码中的每个this都是指向该节点。

Cpp
class MinimalPublisher : public rclcpp::Node

公共构造函数将节点命名为minimal_publisher,并将count_初始化为0。在构造函数内部,使用String消息类型、主题名称topic以及必要的队列大小来初始化发布者。接下来,初始化了timer_,它导致timer_callback函数每秒执行两次。

Cpp
public:
  MinimalPublisher()
  : Node("minimal_publisher"), count_(0)
  {
    publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
    timer_ = this->create_wall_timer(
    500ms, std::bind(&MinimalPublisher::timer_callback, this));
  }

timer_callback函数是设置消息数据并实际发布消息的地方。RCLCPP_INFO宏确保每个发布的消息都会打印到控制台。

cpp
private:
  void timer_callback()
  {
    auto message = std_msgs::msg::String();
    message.data = "Hello, world! " + std::to_string(count_++);
    RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
    publisher_->publish(message);
  }

最后是定时器、发布者和计数器字段的声明。

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

下面是MinimalPublisher类的定义,接着是main函数,其中节点实际执行。rclcpp::init用于初始化ROS 2,rclcpp::spin则开始处理来自节点的数据,包括定时器的回调函数。

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

2.2 添加依赖项

返回到ros2_ws/src/cpp_pubsub目录,这是CMakeLists.txtpackage.xml文件所在的目录。这些文件已经为您创建好。

用文本编辑器打开package.xml文件。

如在:doc:之前的教程 &lt;./Creating-Your-First-ROS2-Package&gt;`中提到的,确保填写``&lt;description&gt;`&lt;maintainer&gt;&lt;license&gt;标签:

xml
<description>Examples of minimal publisher/subscriber using rclcpp</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache License 2.0</license>

ament_cmake构建工具依赖项之后添加一个新行,并粘贴以下与节点的包含语句对应的依赖项:

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

这将在构建和执行代码时声明该包需要rclcppstd_msgs.

确保保存文件.

2.3 CMakeLists.txt

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

make
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

然后,添加可执行文件并将其命名为 talker,这样你就可以使用 ros2 run 运行你的节点:

make
add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

最后,添加 install(TARGETS...) 部分,以便 ros2 run 可以找到你的可执行文件:

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

你可以通过删除一些不必要的部分和注释来简化你的CMakeLists.txt,使其看起来像这样:

make
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_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

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

ament_package()

现在您可以构建您的软件包,加载本地设置文件并运行它,但是让我们首先创建订阅者节点,这样您就可以看到整个系统的运行情况。

3.编写订阅者节点

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

bash
wget -O subscriber_member_function.cpp https://raw.githubusercontent.com/ros2/examples/humble/rclcpp/topics/minimal_subscriber/member_function.cpp

现在在控制台输入 ls 将会返回:

bash
publisher_member_function.cpp  subscriber_member_function.cpp

使用文本编辑器打开 subscriber_member_function.cpp

cpp
#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;

class MinimalSubscriber : public rclcpp::Node
{
  public:
    MinimalSubscriber()
    : Node("minimal_subscriber")
    {
      subscription_ = this->create_subscription<std_msgs::msg::String>(
      "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
    }

  private:
    void topic_callback(const std_msgs::msg::String & msg) const
    {
      RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg.data.c_str());
    }
    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;
}

3.1 检查代码

订阅者节点的代码与发布者几乎完全相同。现在节点被命名为 minimal_subscriber,构造函数使用节点的 create_subscription 类来执行回调函数。

没有定时器,因为订阅者只会在数据被发布到 topic 主题时做出响应。

cpp
public:
  MinimalSubscriber()
  : Node("minimal_subscriber")
  {
    subscription_ = this->create_subscription<std_msgs::msg::String>(
    "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
  }

回顾一下 主题教程 中提到,发布者和订阅者使用的主题名称和消息类型必须匹配,才能使它们进行通信。

topic_callback 函数接收通过主题发布的字符串消息数据,并使用 RCLCPP_INFO 宏将其简单地写入控制台。

这个类中仅有一个字段声明,即订阅。

cpp
private:
  void topic_callback(const std_msgs::msg::String & msg) const
  {
    RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg.data.c_str());
  }
  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;

main 函数完全相同,只是现在它旋转 MinimalSubscriber 节点。对于发布者节点,旋转意味着启动计时器,而对于订阅者节点,它只是准备在消息到来时接收消息。

由于该节点与发布者节点具有相同的依赖关系,因此在package.xml中没有新内容可添加。

3.2 CMakeLists.txt

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

make
add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp std_msgs)

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

确保保存文件,然后您的发布/订阅系统就准备好了。

4.构建和运行

很可能您已经在您的ROS 2系统中安装了rclcppstd_msgs软件包。在构建之前,最好在您的工作空间根目录(ros2_ws)中运行rosdep来检查是否存在缺失的依赖项:

bash
rosdep install -i --from-path src --rosdistro humble -y

仍然在您的工作空间根目录(ros2_ws)中,构建您的新软件包:

bash
colcon build --packages-select cpp_pubsub

打开一个新的终端,导航到 ros2_ws,并加载设置文件:

bash
. install/setup.bash

现在运行对话节点:

bash
ros2 run cpp_pubsub talker

终端应该会每0.5秒发布一条信息消息,如下所示:

bash
[INFO] [minimal_publisher]: Publishing: "Hello World: 0"
[INFO] [minimal_publisher]: Publishing: "Hello World: 1"
[INFO] [minimal_publisher]: Publishing: "Hello World: 2"
[INFO] [minimal_publisher]: Publishing: "Hello World: 3"
[INFO] [minimal_publisher]: Publishing: "Hello World: 4"

打开另一个终端,在ros2_ws内部再次source设置文件,然后启动监听节点:

bash
ros2 run cpp_pubsub listener

监听器将从发布者当前的消息计数开始,将消息打印到控制台,就像这样:

bash
[INFO] [minimal_subscriber]: I heard: "Hello World: 10"
[INFO] [minimal_subscriber]: I heard: "Hello World: 11"
[INFO] [minimal_subscriber]: I heard: "Hello World: 12"
[INFO] [minimal_subscriber]: I heard: "Hello World: 13"
[INFO] [minimal_subscriber]: I heard: "Hello World: 14"

在每个终端中输入Ctrl+C来停止节点的旋转。

5.总结

你创建了两个节点,用于在一个话题上发布和订阅数据。在编译和运行之前,你将它们的依赖项和可执行文件添加到了软件包配置文件中。

最近更新