ROS2 节点和话题

引言

本篇是ROS2最核心的通信模式:发布-订阅(Pub-Sub)

1. 发布-订阅模型的数学模型

1.1 基本概念

发布-订阅模型可以用集合论来描述:
Topic(t) = {m_1, m_2, ..., m_n} at time t
其中:

  • 发布者节点 P_i 向话题 T_j 发送消息 m_i
  • 订阅话题 T_j 的节点 S_k 接收消息

1.2 通信时序图

img

1.3 通信模式对比

  • 1对多:一个发布节点广播消息到订阅节点
  • 多对1:多个发布节点发送消息到一个订阅节点
  • 多对多:多个发布节点发送消息到话题总线,多个接收节点从总线上接受消息

2. 话题通信的QoS配置

2.1 基本介绍

QoS(Quality of Service)定义了消息传递的可靠性和持久性,话题端点(Endpoint)的属性,发布者端和订阅者端各自独立配置,两者会进行"协商。配置时注意兼容问题,不兼容时会拒绝连接。

2.2 配置项

核心策略:

  1. history: 历史策略。决定是只保留最新的消息(KEEP_LAST)还是保留所有消息(KEEP_ALL)。默认值:KEEP_LAST
  2. depth: 队列深度。配合 history 使用,如果是 KEEP_LAST,这个值决定了队列里存几条消息。
  3. reliability: 可靠性策略。这是最重要的之一,决定是“必须收到”(RELIABLE)还是“丢了就丢了”(BEST_EFFORT)。默认值:RELIABLE
  4. durability: 持久性策略。决定“晚加入的订阅者能不能收到之前的消息”(TRANSIENT_LOCAL vs VOLATILE)。默认值:VOLATILE

高级/时间策略:

  1. deadline: 截止期。期望收到消息的最大时间间隔。如果超时没收到,会触发回调报错。 默认(无限制)
  2. lifespan: 消息寿命。消息在队列里能活多久。如果订阅者处理太慢,消息过期了就会被丢弃,不再发送。默认(无限制)
  3. liveliness: 存活策略。用来检测对方是不是还“活着”(比如节点挂了或者网线拔了)。 默认(AUTOMATIC)
  4. liveliness_lease_duration: 存活租约时间。配合上面的 liveliness 使用,定义“多久没动静就算挂了”。默认(无限制)

2.3 兼容性

QoS可靠性策略的兼容性:
img

QoS持久性策略的兼容性:
img

QoS时间期限策略的兼容性(假设x和y是任意有效的持续时间值):
img

QoS活力策略的兼容性:
img

QoS租期策略的兼容性(假设x和y是任意有效的持续时间值):
img

为了建立连接,所有影响兼容性的策略都必须兼容。例如,即使请求和提供的QoS配置文件在QoS可靠性策略方面兼容,但它们在QoS持久性策略方面不兼容,仍然不会建立连接。

如果未建立连接,则发布者和订阅者之间将不会传递任何消息。

3. 演示项目

3.1 构建功能包

API相关详解可查看官方文档https://docs.ros2.org/latest/api/rclcpp/
依次输入下面的命令,创建chapt3_ws工作空间、example_topic_rclcpp功能包和topic_publisher_01.cpp和topic_subscribe_01.cpp

mkdir -p chapt3/chapt3_ws/src
cd chapt3/chapt3_ws/src
ros2 pkg create example_topic_rclcpp --build-type ament_cmake --dependencies rclcpp
touch example_topic_rclcpp/src/topic_publisher_01.cpp
touch example_topic_rclcpp/src/topic_subscribe_01.cpp

workspace目录结构如下

chapt3_ws
└── src
    └── example_topic_rclcpp
        ├── CMakeLists.txt
        ├── include
        │   └── example_topic_rclcpp
        ├── package.xml
        └── src
            └── topic_publisher_01.cpp
            └── topic_subscribe_01.cpp

修改CMakeLists.txt

add_executable(topic_subscribe_01 src/topic_subscribe_01.cpp)
ament_target_dependencies(topic_subscribe_01 rclcpp)

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

3.2 发布者实现

定义了一个周期500ms发布字符类型消息到command话题的节点,qos配置depth=10

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

class TopicPublisher01 : public rclcpp::Node
{
public:
    // 构造函数,有一个参数为节点名称
    TopicPublisher01(std::string name) : Node(name)
    {
        RCLCPP_INFO(this->get_logger(), "大家好,我是%s.", name.c_str());
        // 创建发布者
        command_publisher_ = this->create_publisher<std_msgs::msg::String>("command", 10);
        // 创建定时器,500ms为周期,定时发布
        timer_ = this->create_wall_timer(std::chrono::milliseconds(500), std::bind(&TopicPublisher01::timer_callback, this));
    }

private:
    void timer_callback()
    {
        // 创建消息
        std_msgs::msg::String message;
        message.data = "forward";
        // 日志打印
        RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
        // 发布消息
        command_publisher_->publish(message);
    }
    // 声名定时器指针
    rclcpp::TimerBase::SharedPtr timer_;
    // 声明话题发布者指针
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr command_publisher_;
};

3.3 订阅者实现

定义了接收节点,根据接收到的消息,执行相应动作。qos配置depth=10.

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

class TopicSubscribe01 : public rclcpp::Node
{
public:
    TopicSubscribe01(std::string name) : Node(name)
    {
        RCLCPP_INFO(this->get_logger(), "大家好,我是%s.", name.c_str());
          // 创建一个订阅者订阅话题
        command_subscribe_ = this->create_subscription<std_msgs::msg::String>("command", 10, std::bind(&TopicSubscribe01::command_callback, this, std::placeholders::_1));
    }

private:
     // 声明一个订阅者
    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr command_subscribe_;
     // 收到话题数据的回调函数
    void command_callback(const std_msgs::msg::String::SharedPtr msg)
    {
        double speed = 0.0f;
        if(msg->data == "forward")
        {
            speed = 0.2f;
        }
        RCLCPP_INFO(this->get_logger(), "收到[%s]指令,发送速度 %f", msg->data.c_str(),speed);
    }
};

3.4 导入消息接口

消息接口是ROS2通信时必须的一部分,通过消息接口ROS2才能完成消息的序列化和反序列化。ROS2为我们定义好了常用的消息接口,并生成了C++和Python的依赖文件,我们可以直接在程序中进行导入。

ament_cmake类型功能包导入消息接口分为三步:

在CMakeLists.txt中导入,具体是先find_packages再ament_target_dependencies。
在packages.xml中导入,具体是添加depend标签并将消息接口写入。
在代码中导入,C++中是#include"消息功能包/xxx/xxx.hpp"。
我们依次做完这三步后文件内容如下:

CMakeLists.txt

find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
...
add_executable(topic_publisher_01 src/topic_publisher_01.cpp)
ament_target_dependencies(topic_publisher_01 rclcpp std_msgs)
install(TARGETS
  topic_publisher_01
  DESTINATION lib/${PROJECT_NAME}  
)

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

packages.xml

  <buildtool_depend>ament_cmake</buildtool_depend>

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

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

代码文件topic_publisher_01.cpp、topic_subscribe_01.cpp

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

3.5 运行及监控工具命令

编译,source,运行

cd chapt3_ws/
colcon build --packages-select example_topic_rclcpp
source install/setup.bash
ros2 run example_topic_rclcpp topic_publisher_01
ros2 run example_topic_rclcpp topic_subscribe_01

监控工具

  1. 查看话题列表
# 列出所有活跃的话题
ros2 topic list

# 输出示例:
# /command
# /parameter_events
# /rosout
  1. 监听话题消息
# 实时查看话题上的消息
ros2 topic echo /command

# 输出示例:
# data: forward
# ---
# data: forward
  1. 查看话题信息
# 显示话题的详细信息,节点数量
ros2 topic info /command

# 输出:
# Type: std_msgs/msg/String
# Publisher count: 1
# Subscription count: 1
  1. 查看消息统计信息
# 显示话题的消息速率等信息
ros2 topic hz /command

# 输出示例:
#average rate: 1.944
#        min: 0.431s max: 0.650s std dev: 0.08708s window: 4
#average rate: 2.053
#        min: 0.399s max: 0.650s std dev: 0.07737s window: 7
posted @ 2026-08-17 00:13  觅踪人  阅读(10)  评论(0)    收藏  举报