【ROS2】ROS2通讯机制Topic(C++示例)

ROS 系列学习教程(总目录)
ROS2 系列学习教程(总目录)

目录

  • 一、创建自定义话题C++版
    • [1.1 创建功能包](#1.1 创建功能包)
    • [1.2 编辑源文件](#1.2 编辑源文件)
    • [1.3 编辑编译配置文件CMakeList.txt](#1.3 编辑编译配置文件CMakeList.txt)
    • [1.4 编译工程](#1.4 编译工程)
    • [1.5 运行节点](#1.5 运行节点)

话题通信适用于不断更新数据、少逻辑处理的传输相关的应用场景。它是一种单向通讯方式,它通过发布和订阅的方式传递消息,与ROS1相比,该模型只涉及到两个角色:

  • Publisher(发布者)
  • Subscriber(订阅者)

消息数据由发布者传递到订阅者,如下图:

发布者和订阅者位于节点中,每个节点可以有多个不同的发布者和订阅者,一个节点可以向任意数量的主题发布数据,同时订阅任意数量的主题。发布者和订阅者的关系,不仅仅是一对一,还可以是一对多,多对一,多对多的关系。

一、创建自定义话题C++版

我们创建一个新的功能包来演示topic相关操作

1.1 创建功能包

bash 复制代码
cd ros2_learning/src
ros2 pkg create --build-type ament_cmake hello_world_topic_cpp

其中,

使用 --build-type 指定编译系统为 ament_cmake

hello_world_topic_cpp:自定义功能包名称

结果如下图:

其中,有 [WARNING]: Unknown license 'TODO: License declaration'. ROS2建议创建一个 License 文件以说明该功能包的发布许可。可以使用 --license LICENSE 参数指定,例如:

bash 复制代码
ros2 pkg create --build-type ament_cmake --license Apache-2.0 hello_world_topic_cpp

生成的目录结构如下:

bash 复制代码
hello_world_topic_cpp
├── CMakeLists.txt
├── include
│   └── hello_world_topic_cpp
├── LICENSE
├── package.xml
└── src

1.2 编辑源文件

我们需要编写一个发布者(talker)和一个订阅者(listener),让他们在不同的节点,分别发布topic和订阅topic。

hello_world_topic_cpp/include 目录下新增 talker.h 文件,文件内容如下:

cpp 复制代码
#ifndef __HELLO_WORLD_TOPIC_TALKER_H__
#define __HELLO_WORLD_TOPIC_TALKER_H__

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

class CTalker : public rclcpp::Node
{
public:
    CTalker();
    ~CTalker();

private:
    void timercallback();

private:
    rclcpp::TimerBase::SharedPtr m_timer;
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr m_publisher;
    size_t m_count;
};

#endif // __HELLO_WORLD_TOPIC_TALKER_H__

hello_world_topic_cpp/src 目录下新增 talker.cpp 文件,文件内容如下:

c++ 复制代码
#include "hello_world_topic_cpp/talker.h"

using namespace std::chrono_literals;

CTalker::CTalker() : Node("talker_node"), m_count(0)
{
    m_publisher = this->create_publisher<std_msgs::msg::String>("/hello_world_topic", 10);
    m_timer = this->create_wall_timer(500ms, std::bind(&CTalker::timercallback, this));
}

CTalker::~CTalker()
{
}

void CTalker::timercallback()
{
    auto message = std_msgs::msg::String();
    message.data = "Hello world ROS2 Topic! " + std::to_string(this->m_count++);
    
    RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
    this->m_publisher->publish(message);
}

int main(int argc, char **argv) 
{
    rclcpp::init(argc, argv);
    auto talker_node = std::make_shared<CTalker>();

    rclcpp::spin(talker_node);
    rclcpp::shutdown();
    return 0;
}

hello_world_topic_cpp/include 目录下新增 listener.h 文件,文件内容如下:

cpp 复制代码
#ifndef __HELLO_WORLD_TOPIC_LISTENER_H__
#define __HELLO_WORLD_TOPIC_LISTENER_H__

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

class CListener : public rclcpp::Node
{
public:
    CListener();
    ~CListener();

private:
    void topic_callback(const std_msgs::msg::String & msg) const;

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

#endif // __HELLO_WORLD_TOPIC_LISTENER_H__

hello_world_topic_cpp/src 目录下新增 listener.cpp 文件,文件内容如下:

cpp 复制代码
#include "hello_world_topic_cpp/listener.h"

CListener::CListener() : Node("listener_node")
{
    m_subscriber = this->create_subscription<std_msgs::msg::String>("/hello_world_topic", 10, 
        std::bind(&CListener::topic_callback, this, std::placeholders::_1));
}

CListener::~CListener()
{
}

void CListener::topic_callback(const std_msgs::msg::String &msg) const
{
    RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg.data.c_str());
}

int main(int argc, char **argv) 
{
    rclcpp::init(argc, argv);
    auto listener_node = std::make_shared<CListener>();

    rclcpp::spin(listener_node);
    rclcpp::shutdown();
    return 0;
}

1.3 编辑编译配置文件CMakeList.txt

默认生成的 CMakeList.txt 文件内容如下:

由于新增了talkerlistener,所以要配置该文件的编译规则。

找到ros2_learning/src/hello_world_topic_cpp/CMakeLists.txt,修改如下:

修改说明如下:

cmake 复制代码
# 指定头文件目录
include_directories(
  include
)

# 指定源文件,生成可执行文件
add_executable(talker_node src/talker.cpp)
# 指定可执行文件的依赖项
ament_target_dependencies(talker_node rclcpp std_msgs)
# 定义安装规则,指定可执行文件的安装目录
install(TARGETS
  talker_node
  DESTINATION lib/${PROJECT_NAME}
)

add_executable(listener_node src/listener.cpp)
ament_target_dependencies(listener_node rclcpp std_msgs)
install(TARGETS
  listener_node
  DESTINATION lib/${PROJECT_NAME}
)

1.4 编译工程

进入到工作空间 ros2_learning 目录,执行如下指令编译该工程:

bash 复制代码
colcon build

编译成功后,会在ros2_learning目录下生成 buildinstalllog 目录。

  • **build:**存放编译过程中产生的中间文件,包括临时文件、对象文件、依赖关系等。用户通常不需要直接访问这个目录,因为构建工具会自动管理其中的内容。然而,在调试构建问题时,有时可能需要查看这个目录中的文件以获取更多信息。
  • **install:**存放最终编译生成的可执行文件、库文件、配置文件、环境设置文件等资源。这些资源是构建完成后准备发布的独立包,可以被其他项目或用户直接使用。通常包含多个子目录,如bin(可执行文件)、lib(库文件)、share(配置文件、资源文件等)等。用户可以在 install 目录中查找可执行文件或库文件来运行或链接ROS2节点或服务。此外,ROS2环境设置文件(如setup.bash)也存放在该目录,用于将ROS2包添加到用户的 PATH 和其他环境变量中。
  • **log:**存放编译和运行过程中产生的各种日志信息。这些日志信息包括警告、错误、信息等,对于调试和监控 ROS2 系统的运行状态非常有用。这些日志文件可以是文本文件,也可以是其他格式的文件,具体取决于 ROS2 日志系统的配置。用户可以通过查看log目录中的日志文件来了解 ROS2 系统在编译或运行过程中的行为。此外,ROS2 还提供了日志查看工具(如rqt_console),这些工具可以方便地查看和分析日志文件中的内容。

1.5 运行节点

ROS2 提供了 run 命令,可以根据包名和节点名,在任何目录执行。

但需要先设置环境变量,即让系统可以找到节点,进入到工作空间目录,执行如下指令:

bash 复制代码
source install/setup.bash

运行如下命令分别启动发布者和订阅者节点:

bash 复制代码
ros2 run hello_world_topic_cpp talker_node
ros2 run hello_world_topic_cpp listener_node

启动节点后,打印如下: