• 正文
  • 相关推荐
申请入驻 产业图谱

ROS2订阅话题C++编程示例

08/24 08:38
111
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

上篇文章:ROS2发布自定义数据C++编程示例介绍了ROS2话题发布的C++编程示例,并通过ros2的topic指令查看了发布的消息,本篇,继续通过C++编来实现对话题的订阅,并简单了解QoS的概念。

1 回顾下发布器的写法

// 发布器声明
rclcpp::Publisher<test_msg::msg::PersonInfo>::SharedPtr publisher_;

// 创建发布器,话题名 /person_info,消息类型 test_msg::msg::PersonInfo,队列10
publisher_ = this->create_publisher<test_msg::msg::PersonInfo>("/person_info", 10);

// 发布数据
auto msg = test_msg::msg::PersonInfo();
msg.name = "zhangsan";
msg.age = 22;
msg.height = 1.75f;
RCLCPP_INFO(this->get_logger(), "发布: name=%s, age=%d, height=%.2f",msg.name.c_str(), msg.age, msg.height);
publisher_->publish(msg);

2 订阅器的写法也是类似

// 订阅器声明
rclcpp::Subscription<test_msg::msg::PersonInfo>::SharedPtr subscription_;

// 创建订阅器:话题 /person_info,队列10,回调函数
subscription_ = this->create_subscription<test_msg::msg::PersonInfo>(
    "/person_info",
    10,
    std::bind(&PersonInfoSubscriber::topic_callback, this, std::placeholders::_1)
);

// 订阅回调,参数是收到的消息共享指针
void topic_callback(const test_msg::msg::PersonInfo::SharedPtr msg) const
{
    RCLCPP_INFO(this->get_logger(),
                "收到消息:name=%s, age=%d, height=%.2f",
                msg->name.c_str(),
                msg->age,
                msg->height);
}

说明:

create_subscription后面尖括号中test_msg::msg::PersonInfo表示订阅的话题对应的数据类型

create_subscription后面参数:

topic_callback:回调函数,函数格式:void 函数名(const MessageT::SharedPtr msg)

this:绑定当前类实例对象

std::placeholders::_1:占位符,表示把收到的消息传给回调第一个入参

"/person_info":字符串,订阅的话题名称

10:无符号整数,代表消息队列深度 (history depth)

std::bind...:对消息的回调处理

3 create_subscription函数原型

create_subscription的函数原型有两个:

// 原型1
template<typename MessageT>
typename rclcpp::Subscription<MessageT>::SharedPtr
create_subscription(
  const std::string & topic_name,
  size_t qos_depth,
  std::function<void(const typename MessageT::SharedPtr)> callback,
  const rclcpp::CallbackGroup::SharedPtr group = nullptr
);

// 原型2
template<typename MessageT>
typename rclcpp::Subscription<MessageT>::SharedPtr
create_subscription(
  const std::string & topic_name,
  const rclcpp::QoS & qos_profile,
  std::function<void(const typename MessageT::SharedPtr)> callback,
  const rclcpp::CallbackGroup::SharedPtr group = nullptr
);

下面再来看下参数

参数1:话题名称

开头带/ → 绝对话题名;不带/为相对名字,会加上节点命名空间发布和订阅

字符串必须完全一模一样

    (大小写敏感),否则无法匹配通信

参数2:队列深度/QoS 服务质量

队列的深度,是指DDS(Data Distribution Service,‌数据分发服务)层本地缓存最多保留多少条还没处理的消息,消息超出数量旧消息丢弃。

只填数字时,ROS2 会使用默认QoS 策略(Quality of Service,服务质量

QoS 三个核心维度:

KeepLast(depth) / KeepAll

    1.  队列保存策略

KeepLast(depth):保存指定的深度

KeepAll:保存所有

Reliable / BestEffort

Reliable:可靠,保证送达,丢包会重传;适合业务消息。

BestEffort:尽力,不重传;适合高频摄像头、lidar。

Durability:消息持久化

Volatile:新订阅者收历史消息

TransientLocal:保存消息,晚加入订阅可以收到历史

rclcpp::QoS(qos_depth),使用默认策略:KeepLast | Reliable | Volatile

另外注意:发布者和订阅者QoS 必须兼容,否则可能收不到消息

参数3:回调函数

回调可以有两种写法

类成员函数bind

void topic_callback(const test_msg::msg::PersonInfo::SharedPtr msg) const;

std::bind(&PersonInfoSubscriber::topic_callback, this, std::placeholders::_1)

lambda 表达式

subscription_ = this->create_subscription<test_msg::msg::PersonInfo>(
  "/person_info",
  10,
  [this](const test_msg::msg::PersonInfo::SharedPtr msg){
    RCLCPP_INFO(this->get_logger(),"name=%s",msg->name.c_str());
  }
);

参数4:回调组

rclcpp::CallbackGroup::SharedPtr group

回调组,控制回调在哪一个执行组,线程调度。一般都是默认 nullptr

nullptr:使用节点默认回调组,所有回调共用 spin 的同一个线程。如果自定义回调组,可以实现多线程,避免一个耗时回调阻塞其他订阅。

4 完整的代码

#include "rclcpp/rclcpp.hpp"
#include "test_msg/msg/person_info.hpp"

class PersonInfoSubscriber : public rclcpp::Node
{
public:
    PersonInfoSubscriber() : Node("person_info_sub_node")
    {
        // 创建订阅器:话题 /person_info,队列10,回调函数
        subscription_ = this->create_subscription<test_msg::msg::PersonInfo>(
            "/person_info",
            10,
            std::bind(&PersonInfoSubscriber::topic_callback, this, std::placeholders::_1)
        );
        RCLCPP_INFO(this->get_logger(), "订阅节点已启动,等待 /person_info 消息...");
    }

private:
    // 订阅回调,参数是收到的消息共享指针
    void topic_callback(const test_msg::msg::PersonInfo::SharedPtr msg) const
    {
        RCLCPP_INFO(this->get_logger(),
                    "收到消息:name=%s, age=%d, height=%.2f",
                    msg->name.c_str(),
                    msg->age,
                    msg->height);
    }

    rclcpp::Subscription<test_msg::msg::PersonInfo>::SharedPtr subscription_;
};

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

结合上一篇的例程,先运行上一篇介绍发布器程序,再运行本篇的订阅器程序,结果如下:

5 总结

本篇介绍了ROS2的话题订阅的C++写法,并重点介绍了订阅接口的参数以及QoS的概念,结合上一篇的发布器程序,实现数据的发布和接收。

相关推荐

控制科学与工程硕士,日常分享单片机、嵌入式、C/C++、Linux等学习经验干货~