首页 > 其他分享 >ROS2话题模型代码解读

ROS2话题模型代码解读

时间:2024-12-09 16:56:53浏览次数:5  
标签:std topic 代码 rclcpp 解读 callback command 节点 ROS2

        本人在学习鱼香ROS2的过程中对代码还是有些不了解,于是把所写的代码进行解构分析其流程和逻辑,适合像我一样的初学者进行辅助学习。我们这里以c++代码为例。

一、发布者

1.cpp

发布者全部代码为:

#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);
        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_;
};

int main(int argc, char **argv)
{
    rclcpp::init(argc, argv);
    auto node = std::make_shared<TopicPublisher01>("topic_publisher_01");
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

代码的框架为:

节点类TopicPublisher01:
    继承于rclcpp::Node,表示这是一个 ROS 2 节点类
    成员函数:
        构造函数:初始化节点、创建定时器和发布者
        timer_callback:定时触发,发布消息
    成员变量:
        command_publisher_:发布器,发布 std_msgs::msg::String 类型的消息
        timer_:定时器,定时调用 timer_callback()
    
主函数:
    调用 rclcpp::init 进行初始化。
    创建 TopicPublisher01 节点对象。
    使用 rclcpp::spin 让节点保持运行。
    结束时调用 rclcpp::shutdown 关闭节点

整体流程为:

1.main函数中启动节点

2.调用构造函数创建TopicPublisher01 节点对象,名称为“topic_publisher_01”

3.创建 Publisher (发布者),名称为command,消息队列为10

4.创建 Timer (定时器),500ms进行触发

5.Timer 定时触发

6.程序终止

1.启动节点

rclcpp::init(argc, argv)

初始化 ROS 2 客户端库,解析命令行参数

2.创建节点

auto node = std::make_shared<TopicPublisher01>("topic_publisher_01");

使用make_shared来创建一个TopicPublisher01的节点,传入节点名称"topic_publisher_01',这里调用了TopicPublisher01类的构造函数TopicPublisher01(std::string name)

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);
        timer_=this->create_wall_timer(std::chrono::milliseconds(500),std::bind(&TopicPublisher01::timer_callback,this));
    }

进入构造函数后,使用RCLCPP_INFO函数,用于将日志消息输出到终端,我们就能在命令行中看到这一条消息

3.创建发布者

在这个api文档里面可以看到,该函数要传入消息类型T,话题名称topic_name,服务指令qos和其他选项。

在构造函数里面,创建了发布者

command_publisher_ = this->create_publisher<std_msgs::msg::String>("command", 10);

类里面成员变量定义了发布者

rclcpp::Publisher<std_msgs::msg::String>::SharedPtr command_publisher_;

发布者command_publisher的类型为智能指针SharedPtr,用以管理发布者的生命周期,<>内存放了发布的消息类型,这里为std_msgs::msg::String,后续我们还可以自己定义数据类型。

再回到我们创建发布者的函数,create_publisher<T>()函数是ROS2提供用于创建我们的发布者。

这里Topic的名称为"command",消息会发布到command这个话题中,队列大小为10,表示消息的缓冲区大小。

4.定时器创建

这里定时器创建函数需要我们传入回调周期period,回调函数callback和调用回调函数所在的回调组group,group默认为nullptr

在构造函数里面,创建了定时器,

timer_ = this->create_wall_timer(
    std::chrono::milliseconds(500), 
    std::bind(&TopicPublisher01::timer_callback, this));

在类里面定义了定时器成员变量

rclcpp::TimerBase::SharedPtr timer_;

定时器timer_的类型为智能指针SharedPtr,用以管理定时器的生命周期

回到定时器的创建,create_wall_timer()函数用于创建定时器,

第一个参数std::chrono::milliseconds(500),设置了定时器触发周期为500毫秒

第二个参数,这里其实是要我们传入回调函数,std::bind(&TopicPublisher01::timer_callback, this)),这里使用bind函数,将对象指针this和回调函数timer_callback进行绑定,当对象创建的定时器触发后,会调用回调函数,这样设置好了500ms触发一次,进入回调函数。

5.Timer 定时触发

我们定义了私有方法,定时器回调

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);
    }

进入回调函数后,创建了消息对象message,其类型为std_msgs::msg::String,设置消息的内容为"forward",使用RCLCPP_INFO函数,用于将日志消息输出到终端,将当前消息的内容"forward"输出到终端,使用c_str()将string转为和c兼容的char *类型,用于输出。

调用发布者的publishe()方法,将消息发布到command话题中,便于订阅者subscribe进行消息订阅。

6.程序执行并终止

rclcpp::spin(node)

让节点保持运行,监听事件并调用回调函数,用于触发timer_callback()

rclcpp::shutdown()

停止运行

根据这些,我们能总结出写一个发布节点的模板代码

#include "rclcpp/rclcpp.hpp"            // ROS 2 核心客户端库
#include "std_msgs/msg/string.hpp"      // 消息类型,根据需求修改
//我们用T代替消息类型

// 自定义发布者节点类
class PublisherNode : public rclcpp::Node
{
public:
    // 构造函数,设置节点名称
    PublisherNode(const std::string &node_name) : Node(node_name)
    {
        RCLCPP_INFO(this->get_logger(), "%s 节点已启动", node_name.c_str());

        // 创建发布者
        publisher_ = this->create_publisher<T>(topic_name_, 10);//主题名称和队列长度

        // 创建定时器,定期调用回调函数
        timer_ = this->create_wall_timer(
            std::chrono::milliseconds(timer_interval_), 
            std::bind(&PublisherNode::timer_callback, this)
        );
    }

private:
    // 定时器回调函数
    void timer_callback()
    {
        auto message = T();
        message.data = "Hello, ROS 2!";  // 修改这里的消息内容
        RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
        publisher_->publish(message);
    }

    // 成员变量
    rclcpp::Publisher<T>::SharedPtr publisher_;  // 发布者
    rclcpp::TimerBase::SharedPtr timer_;                             // 定时器

    // 可修改的参数
    const std::string topic_name_ = "topic_name";   // 发布的主题名称
    const int timer_interval_ = 500;               // 定时器间隔 (单位:毫秒)
};

// 主函数
int main(int argc, char **argv)
{
    rclcpp::init(argc, argv);  // 初始化 ROS 2 客户端
    auto node = std::make_shared<PublisherNode>("publisher_node");  // 创建节点对象
    rclcpp::spin(node);        // 运行节点,保持监听回调
    rclcpp::shutdown();        // 关闭 ROS 2 客户端
    return 0;
}

二、订阅者

1.cpp

订阅者的全部代码为:

#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(),"hello, im %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(),"cover [%s] command, speed %f",msg->data.c_str(),speed);
    }
};
int main(int argc,char **argv)
{
    rclcpp::init(argc,argv);
    auto node =std::make_shared<TopicSubscribe01>("topic_subscribe_01");
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

代码的框架为:

节点类 TopicSubscribe01:
    继承自 rclcpp::Node,表示这是一个 ROS2订阅者节点类
    成员函数:
        构造函数:初始化节点并创建订阅者
        command_callback:处理接收到的消息
    成员变量:
    command_subscribe_:订阅者,订阅 std_msgs::msg::String 类型的消息
主函数:
    调用 rclcpp::init 进行初始化
    创建 TopicSubscribe01 节点对象
    使用 rclcpp::spin 让节点保持运行
    结束时调用 rclcpp::shutdown 关闭节点

整体流程为:

1.main函数中启动节点

2.调用构造函数创建TopicSubscribe01节点对象,名称为“topic_subscribe_01”

3.创建Subscribe(订阅者),订阅话题为command,消息队列为10

4.等待并处理消息

5.消息到达,进入回调函数command_callback()处理消息

6.程序终止

1.启动节点

rclcpp::init(argc, argv)

初始化 ROS 2 客户端库,解析命令行参数

2.创建节点

auto node =std::make_shared<TopicSubscribe01>("topic_subscribe_01");

使用make_shared来创建一个TopicSubscribe01的节点,传入节点名称"topic_subscribe_01',这里调用了TopicSubscribe01类的构造函数TopicSubscribe01(std::string name)

3.订阅者创建

在这个api文档里面可以看到,该函数要传入消息类型T,话题名称topic_name,服务指令qos,回调函数callback,后面两个参数都有默认值。

在构造函数里面,创建了订阅者

command_subscribe_=this->create_subscription<std_msgs::msg::String>("command",10,std::bind(&TopicSubscribe01::command_callback,this,std::placeholders::_1));

类里面成员变量定义了订阅者

rclcpp::Subscription<std_msgs::msg::String>::SharedPtr command_subscribe_;

发布者command_subscribe_的类型为智能指针SharedPtr,用以管理发布者的生命周期,<>内存放了发布的消息类型,这里为std_msgs::msg::String,后续我们还可以自己定义数据类型。

再回到我们创建订阅者的函数,create_subscription<T>()函数是ROS2提供用于创建我们的发布者。

这里订阅的Topic的名称为"command",消息会发布到command这个话题中,队列大小为10,表示消息的缓冲区大小,第三个参数,这里其实是要我们传入回调函数,std::bind(&TopicSubscribe01::command_callback,this,std::placeholders::_1),这里使用bind函数,将对象指针this和回调函数timer_callback进行绑定,std::placeholders::_1 是一个占位符,表示 command_callback 函数的第一个参数。

我们的command_callback参数为

void command_callback(const std_msgs::msg::String::SharedPtr msg)

因此这里的std::placeholders::_1代表msg,所以std::bind(&TopicSubscribe01::command_callback, this, std::placeholders::_1) 创建了一个可调用对象,它绑定了成员函数 command_callback,并且在调用时会将 this 指针和一个参数(即接收到的消息)传递给它。

4.等待消息

rclcpp::spin(node);

这句话里面,我们进入事件循环,等待接收消息。

5.处理消息

接收到消息msg后,我们进入回调函数command_callback中,处理接受到的消息

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(),"cover [%s] command, speed %f",msg->data.c_str(),speed);
    }

command_callback 方法:这是处理收到消息的回调函数。它接受一个std_msgs::msg::String::SharedPtr 类型的消息指针msg,然后进行判断,如果接受的消息数据为"forward",则把speed设为0.2,使用RCLCPP_INFO打印日志到终端,打印内容为接受到的消息数据msg->data和speed,同样使用c_str()将string转为c的char*。

6.结束程序

rclcpp::shutdown()

停止运行

根据这些,我们能总结出写一个订阅节点的模板代码

#include "rclcpp/rclcpp.hpp"            // ROS 2 核心客户端库
#include "std_msgs/msg/string.hpp"      // 消息类型,根据需求修改
// 我们用T代替消息类型

// 自定义订阅者节点类
class SubscriberNode : public rclcpp::Node
{
public:
    // 构造函数,设置节点名称
    SubscriberNode(const std::string &node_name) : Node(node_name)
    {
        RCLCPP_INFO(this->get_logger(), "%s 节点已启动", node_name.c_str());

        // 创建订阅者
        subscription_ = this->create_subscription<T>(
            topic_name_, 10,  // 主题名称和队列长度
            std::bind(&SubscriberNode::message_callback, this, std::placeholders::_1)
        );
    }

private:
    // 回调函数:处理接收到的消息
    void message_callback(const typename T::SharedPtr msg)
    {
        RCLCPP_INFO(this->get_logger(), "Received message: '%s'", msg->data.c_str());
        // 这里可以添加自定义的消息处理逻辑
    }

    // 成员变量
    rclcpp::Subscription<T>::SharedPtr subscription_;  // 订阅者

    // 可修改的参数
    const std::string topic_name_ = "topic_name";   // 订阅的主题名称
};

// 主函数
int main(int argc, char **argv)
{
    rclcpp::init(argc, argv);  // 初始化 ROS 2 客户端
    auto node = std::make_shared<SubscriberNode>("subscriber_node");  // 创建节点对象
    rclcpp::spin(node);        // 运行节点,保持监听回调
    rclcpp::shutdown();        // 关闭 ROS 2 客户端
    return 0;
}

三、CMakeLists的修改和package.xml的修改

1.package.xml中添加依赖

在package.xml中,添加对消息信息std_msgs包的依赖

2.cmakelists.txt

1.add_executable

add_executable(topic_publisher_01 src/topic_publisher_01.cpp)
add_executable(topic_subscribe_01 src/topic_subscribe_01.cpp)

添加这两行是为了将指定的 源代码文件 编译成可执行文件

topic_publisher_01 是基于 src/topic_publisher_01.cpp 构建的可执行文件。
topic_subscribe_01 是基于 src/topic_subscribe_01.cpp 构建的可执行文件。

2.ament_target_dependencies

ament_target_dependencies(topic_publisher_01 rclcpp std_msgs)
ament_target_dependencies(topic_subscribe_01 rclcpp std_msgs)

这两句是为可执行文件添加依赖项,确保正确链接到相应的库,这里将可执行文件正确地链接到 rclcppstd_msgs

3.install

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

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

这两句是为了将编译好的可执行文件安装到指定目录,以便在运行时可以找到它们,

install(TARGETS <target_name> DESTINATION <directory>)

<target_name>:要安装的目标(如可执行文件)
<directory>:目标被安装到的目录

这样做的目的是:确保构建和安装之后,可以通过 ROS 2 的命令找到这些可执行文件,使得程序能够通过 ros2 run example_topic_rclcpp topic_publisher_01 启动节点

标签:std,topic,代码,rclcpp,解读,callback,command,节点,ROS2
From: https://blog.csdn.net/fegxg/article/details/144310049

相关文章

  • [c++]c++ 工程代码中的debug时条件编译隐去的代码会影响程序运行的性能和耗时吗
    前言 理解在C++(或任何编程语言)中,使用条件编译(如通过#ifdef,#ifndef,#endif预处理指令)来根据调试(debug)或发布(release)模式包含或排除代码段,对程序在最终编译后的性能和耗时通常是没有直接影响的。这是因为条件编译指令是在编译之前处理的,它们决定了哪些代码会被编译器实际编......
  • Scala隐式转换:提高代码灵活性的利器
    引言Scala的隐式转换是一种允许开发者在特定情境下自动转换类型的特性,它极大地提高了代码的灵活性和可读性。本文将全面介绍Scala隐式转换的概念、应用场景、代码示例、注意事项以及最佳实践。隐式转换基础在Scala中,隐式转换通过implicit关键字定义,可以是隐式值、隐式类或者......
  • Qt/C++离线读取全国任意经纬度高程海拔值/无任何依赖/纯原创代码解析
    一、前言说明做地图开发会遇到一个常规需求,就是获取当前经纬度对应的海拔高度,也叫做高程值,很遗憾各大地图厂商都未提供接口获取,可能是有明文规定,不能地图中提供对应的海拔高度值,于是需要另想他法,尽管谷歌地图在线的api接口是提供了海拔高度值,但是懂得都懂,国内哪里还能用谷歌地图?......
  • SAP QM 事务代码QA02取消以及反取消检验批
    SAPQM事务代码QA02取消以及反取消检验批   SAPQM里的检验批,正常情况下都是由某个前段的业务活动而自动触发的。如果前端的业务活动被取消了,比如前端的货物移动被冲销(Reverse)了,比如前端的交货单被删除了,那么相关的检验批会自动被取消。 当然我们也可以手工方式将某......
  • gitlab极狐企业版实战推拉代码
    目录一、修改gitlab.rb文件1、gitlab配置修改2、网络互通3、拉取代码出错4、解决问题二、总结从gitlab创建项目到本地拉取代码、推送代码。以及其中出现的问题疑点讲解清楚。我太懂各位看官需要看什么了。如有不懂,评论私信一、修改gitlab.rb文件1、gitlab配置......
  • SAP QM不常用功能之事务代码QE01界面里的User Setting
    SAPQM不常用功能之事务代码QE01界面里的UserSetting   SAPQM模块中的QE01事务代码,用于为检验批录入检验结果。 在这个界面里,有一个笔者之前从未关注过的菜单Settings->UserSettings,如下图示,     弹出如下窗口,     激活如下三个选项:  ......
  • RAG综述:探索检索增强生成技术的多样性与代码实践
    当前LLM受限于其训练时所用的固定数据集,难以处理私有或最新的信息,且可能存在“幻觉”现象,即提供错误但看似合理的答案。为了解决这些问题,检索增强型生成(Retrieval-AugmentedGeneration,简称RAG)框架应运而生。RAG(微软最新研究:RAG(Retrieval-AugmentedGeneration)的四个级别深度解......
  • Microi吾码|开源低代码.NET、VUE低代码项目,表单引擎介绍
    Microi吾码|开源低代码.NET、VUE低代码项目,表单引擎介绍一、摘要二、Microi吾码介绍2.1功能介绍2.2团队介绍2.3上线项目案例三、Microi吾码表单引擎是什么?四、Microi吾码表单引擎功能4.1模块引擎-由表单引擎驱动4.2流程引擎-由表单引擎驱动4.3接口引擎-由......
  • python语言dwtppccx代码
    importrequestsimportosfromlxmlimportetreeurl=‘https://pic.netbian.com/4kdongwu/’headers={‘User-Agent’:‘Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/131.0.0.0Safari/537.36Edg/131.0.0.0’,......
  • 一片代码让你搞懂队列(用顺序表实现队列)
    基础数据结构---------队列将持续更新基础数据结构和算法用得到的零基础语法:类,数组,函数,初始化列表,构造函数,析构函数,标准输入输出流,指针,if-else语句,for循环,动态内存管理(堆区)#include<iostream>usingnamespacestd;#include<stdexcept>template<typenamet>classqueue{......