【ROS2】节点间话题的订阅与发布

5 阅读4分钟

0 引言

在ROS2中,节点间的信息传输方式主要为以下几种:

  • Topic(话题,发布-订阅)
  • Service(服务,请求-响应)
  • Action(动作,长时间任务)
  • Parameter(参数服务)
  • Lifecycle(生命周期节点通信)
  • DDS(底层通信机制)

本文将针对Topic(话题)进行简要讲解与使用教学


1 Topic 介绍

Topic(话题)即 “发布-订阅” 模型,是ROS2中一个最常用的信息传输方法。运用此方法时,通常会有一个节点进行信息的发布,单个或多个节点进行信息的订阅。

通俗的来讲,Topic方法就是通过“广播”的方式将信息分发出去的。发布节点作为信号塔,订阅节点作为收音机。单向的接收信息。

在实际的使用场景中,通常被运用在连续数据流的传输中。例如:摄像头的帧数据获取、传感器的数据读取......


2 Topic 使用方法

2.1 Python 版本

2.1.1 创建包(pkg)

通过下述代码在工作区内创建节点,

ros2 pkg create --build-type ament_python "your_node_name" --dependencies rclpy
  • --build-type: 构建类型参数,选择 ament_python 构建Python包
  • --dependencies: 依赖, rclpy、std_msgs等

2.1.2 创建节点(Node)

在包对应文件夹中创建新的.py,如下图中的listener.py

image.png


2.1.3 发布话题(Publisher)

创建 talker.py

import rclpy                          # ROS2 Python接口库
from rclpy.node import Node          # ROS2节点基类
from std_msgs.msg import String      # 导入字符串消息类型

class Talker(Node):
    def __init__(self):
        super().__init__('talker')   # 初始化节点,节点名为 talker

        # 创建发布者(Publisher)
        self.publisher_ = self.create_publisher(
            String,                  # 消息类型
            'chatter',               # 话题名称
            10                       # 队列长度(QoS队列深度)
        )

        # 创建定时器,每 0.5 秒触发一次回调函数
        self.timer = self.create_timer(
            0.5,                     # 时间间隔(秒)
            self.timer_callback      # 回调函数
        )

        self.count = 0               # 计数器,用于生成消息内容

    def timer_callback(self):
        msg = String()                               # 创建消息对象
        msg.data = f'Hello ROS2 {self.count}'         # 填充消息内容

        self.publisher_.publish(msg)                  # 发布消息

        # 在终端打印日志(调试很重要)
        self.get_logger().info(f'Publishing: "{msg.data}"')

        self.count += 1                               # 计数递增


def main(args=None):
    rclpy.init(args=args)        # 初始化 ROS2 Python通信

    node = Talker()              # 创建节点对象

    rclpy.spin(node)             # 保持节点运行(循环等待回调)

    node.destroy_node()          # 销毁节点(释放资源)
    rclpy.shutdown()             # 关闭 ROS2

2.1.4 订阅话题(Subscriber)

创建 listener.py

import rclpy                          # ROS2 Python接口
from rclpy.node import Node          # 节点基类
from std_msgs.msg import String      # 字符串消息类型

class Listener(Node):
    def __init__(self, name):
        super().__init__(name)        # 初始化节点,节点名由外部传入

        # 创建订阅者(Subscriber)
        self.sub = self.create_subscription(
            String,                  # 消息类型(必须与发布者一致)
            "chatter",               # 订阅的话题名称
            self.listener_callback,  # 收到消息时触发的回调函数
            10                       # 队列长度(QoS)
        )

    def listener_callback(self, msg):
        # msg 是接收到的消息对象
        self.get_logger().info(f'I heard: "{msg.data}"')


def main(args=None):
    rclpy.init(args=args)                    # 初始化 ROS2

    node = Listener("topic_helloworld_sub")  # 创建订阅节点

    rclpy.spin(node)                         # 持续监听话题

    node.destroy_node()                      # 销毁节点
    rclpy.shutdown()                         # 关闭 ROS2

2.1.5 运行

将节点加入setup.pycolcon buildsetup后就可以打开终端运行两个节点了。可以观察到终端中发送和接收的效果。


2.2 C++ 版本

相比 Python,C++ 版本基于 rclcpp 实现,适用于对性能要求更高的场景(如机器人控制、实时系统等)。


2.2.1 创建包(pkg)

使用如下命令创建 C++ 功能包:

ros2 pkg create --build-type ament_cmake your_cpp_pkg --dependencies rclcpp std_msgs
  • --build-type:选择 ament_cmake(C++构建方式)
  • --dependencies:依赖项(rclcpp、std_msgs)

2.2.2 创建节点(Node)

src/ 目录下创建 .cpp 文件,例如:

src/talker.cpp
src/listener.cpp

2.2.3 发布话题(Publisher)

创建 talker.cpp

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

using namespace std::chrono_literals;

class Talker : public rclcpp::Node
{
public:
    Talker() : Node("talker_cpp")
    {
        // 创建发布者
        publisher_ = this->create_publisher<std_msgs::msg::String>("chatter", 10);

        // 创建定时器,每0.5秒发送一次
        timer_ = this->create_wall_timer(
            500ms, std::bind(&Talker::timer_callback, this));
    }

private:
    void timer_callback()
    {
        auto msg = std_msgs::msg::String();
        msg.data = "Hello ROS2 C++";

        // 打印日志
        RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", msg.data.c_str());

        // 发布消息
        publisher_->publish(msg);
    }

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

int main(int argc, char * argv[])
{
    rclcpp::init(argc, argv);                         // 初始化ROS2
    rclcpp::spin(std::make_shared<Talker>());         // 运行节点
    rclcpp::shutdown();                               // 关闭ROS2
    return 0;
}

2.2.4 订阅话题(Subscriber)

创建 listener.cpp

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

class Listener : public rclcpp::Node
{
public:
    Listener() : Node("listener_cpp")
    {
        // 创建订阅者
        subscription_ = this->create_subscription<std_msgs::msg::String>(
            "chatter",
            10,
            std::bind(&Listener::topic_callback, this, std::placeholders::_1)
        );
    }

private:
    void topic_callback(const std_msgs::msg::String::SharedPtr 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);                          // 初始化ROS2
    rclcpp::spin(std::make_shared<Listener>());        // 运行节点
    rclcpp::shutdown();                                // 关闭ROS2
    return 0;
}

2.2.5 修改 CMakeLists.txt

CMakeLists.txt 中添加:

add_executable(talker src/talker.cpp)
add_executable(listener src/listener.cpp)

ament_target_dependencies(talker rclcpp std_msgs)
ament_target_dependencies(listener rclcpp std_msgs)

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

2.2.6 编译与运行

colcon build
source install/setup.bash

运行:

ros2 run your_cpp_pkg talker
ros2 run your_cpp_pkg listener