行业资讯
一文搞懂 ROS2 C++ 订阅节点类成员
目录前言摘要一、完整代码示例/cmd_vel 订阅节点二、LimoTopicSub 类本身详解2.1 class LimoTopicSub : public rclcpp::Node 是什么意思2.2 rclcpp::Node 是什么2.3 继承后 LimoTopicSub 就能当 ROS2 节点用吗2.4 LimoTopicSub 和 rclcpp::Node 是完全一样的吗三、public 公有成员详解3.1 构造函数 LimoTopicSub()3.2 构造函数里面做了什么3.3 Node(limo_topic_sub) 是什么意思3.4 this-create_subscription() 是什么意思3.5 RCLCPP_INFO(this-get_logger(), ...) 是什么意思四、private 私有成员详解4.1 成员函数cmd_callback()4.2 为什么说 cmd_callback() 是回调函数4.3 const geometry_msgs::msg::Twist::SharedPtr msg 是什么4.4 为什么是 msg-linear.x4.5 成员变量cmd_sub_4.6 为什么 cmd_sub_ 要写成成员变量五、ROS2 C 常见写法归纳5.1 geometry_msgs::msg::Twist5.2 std::bind5.3 std::placeholders::_15.4 SharedPtr5.5 std::make_shared5.6 this5.7 :: 作用域解析符六、哪些是 LimoTopicSub 自己的成员七、总结前言在之前的专栏已经详细讲解了话题 Topic 消息 msg、服务 Service 消息 srv 和 行为 Action 消息 action 通信详细内容见以下链接1ROS2 C 小车控制完整实战一官方标准 msg 消息发布与订阅保姆级教程ROS2 C 小车控制完整实战一官方标准 msg 消息发布与订阅保姆级教程-CSDN博客https://blog.csdn.net/m0_58954356/article/details/161829804?spm1001.2014.3001.55022ROS2 C 小车控制完整实战二自定义 msg 消息发布与订阅保姆级教程ROS2 C 小车控制完整实战二自定义 msg 消息发布与订阅保姆级教程-CSDN博客https://blog.csdn.net/m0_58954356/article/details/161834874?spm1001.2014.3001.55023ROS2 C 小车控制完整实战三自定义 srv 服务通信保姆级教程ROS2 C 小车控制完整实战三自定义 srv 服务通信保姆级教程-CSDN博客https://blog.csdn.net/m0_58954356/article/details/161834909?spm1001.2014.3001.55024ROS2 C 小车控制完整实战四自定义 Action 通信保姆级教程ROS2 C 小车控制完整实战四自定义 Action 通信保姆级教程-CSDN博客https://blog.csdn.net/m0_58954356/article/details/162141534?spm1001.2014.3001.5502除此之外还有 ROS2 与 C 相关保姆级教程1ROS2 C 回调函数 保姆级教程ROS2 C 回调函数 保姆级教程-CSDN博客https://blog.csdn.net/m0_58954356/article/details/162178534?spm1001.2014.3001.5502摘要在 ROS2 C 编程中我们经常会看到下面这种代码class LimoTopicSub : public rclcpp::Node { public: LimoTopicSub() : Node(limo_topic_sub) { cmd_sub_ this-create_subscriptiongeometry_msgs::msg::Twist( /cmd_vel, 10, std::bind(LimoTopicSub::cmd_callback, this, _1) ); RCLCPP_INFO(this-get_logger(), limo_topic_sub node has started.); } private: void cmd_callback(const geometry_msgs::msg::Twist::SharedPtr msg) { RCLCPP_INFO(this-get_logger(), 收到速度指令linear.x %.2f, angular.z %.2f, msg-linear.x, msg-angular.z); } rclcpp::Subscriptiongeometry_msgs::msg::Twist::SharedPtr cmd_sub_; };这段代码看起来不长但里面包含了ROS2 C 节点开发中非常核心的内容类继承构造函数ROS2 节点基类订阅者对象回调函数智能指针std::bind占位符 _1消息类型 geometry_msgs::msg::Twist很多初学者看到rclcpp::Node、rclcpp::Subscription、geometry_msgs::msg::Twist::SharedPtr、std::bind这些写法时会感觉非常绕。本篇文章就以/cmd_vel订阅节点为例详细讲解LimoTopicSub 这个类到底由哪些成员组成rclcpp::Node 是什么为什么继承它之后就能当 ROS2 节点使用cmd_callback() 为什么是回调函数cmd_sub_ 为什么要写成成员变量一、完整代码示例/cmd_vel 订阅节点首先来看完整代码。这个节点的作用是订阅 /cmd_vel 话题接收 geometry_msgs::msg::Twist 速度消息收到消息后自动触发回调函数打印 linear.x 和 angular.z完整代码如下#include memory #include functional #include rclcpp/rclcpp.hpp #include geometry_msgs/msg/twist.hpp using std::placeholders::_1; class LimoTopicSub : public rclcpp::Node { public: LimoTopicSub() : Node(limo_topic_sub) { cmd_sub_ this-create_subscriptiongeometry_msgs::msg::Twist( /cmd_vel, 10, std::bind(LimoTopicSub::cmd_callback, this, _1) ); RCLCPP_INFO(this-get_logger(), limo_topic_sub node has started.); } private: void cmd_callback(const geometry_msgs::msg::Twist::SharedPtr msg) { RCLCPP_INFO(this-get_logger(), 收到速度指令linear.x %.2f, angular.z %.2f, msg-linear.x, msg-angular.z); } rclcpp::Subscriptiongeometry_msgs::msg::Twist::SharedPtr cmd_sub_; }; int main(int argc, char * argv[]) { rclcpp::init(argc, argv); auto node std::make_sharedLimoTopicSub(); rclcpp::spin(node); rclcpp::shutdown(); return 0; }这段代码中最核心的类是class LimoTopicSub : public rclcpp::Node我们可以把这个类里面的内容分成三部分来看1. 类本身2. public 公有成员3. private 私有成员二、LimoTopicSub 类本身详解2.1class LimoTopicSub : public rclcpp::Node是什么意思代码如下class LimoTopicSub : public rclcpp::Node这句话可以拆成三部分理解class LimoTopicSub表示定义一个类类名叫LimoTopicSub。public表示公开继承。rclcpp::Node表示ROS2 C 提供的节点基类。所以整句话的意思是定义一个名为 LimoTopicSub 的类它公开继承自 rclcpp::Node。也就是说LimoTopicSub 继承了 rclcpp::Node所以它拥有 ROS2 节点的基本能力。2.2rclcpp::Node是什么rclcpp::Node是 ROS2 C 提供的节点基类。可以把它理解成ROS2 官方已经写好的“节点模板类”。它里面封装了一个 ROS2 节点常用的基础功能例如节点名称日志功能话题发布功能话题订阅功能服务端功能客户端功能定时器功能参数功能时间功能节点运行相关接口所以我们自己的类继承了rclcpp::Node之后才能直接使用这些函数this-create_subscription(...) this-create_publisher(...) this-create_wall_timer(...) this-create_service(...) this-create_client(...) this-get_logger() this-now()这些函数并不是我们自己定义的而是从rclcpp::Node继承来的。2.3 继承后 LimoTopicSub 就能当 ROS2 节点用吗可以这样理解。因为LimoTopicSub继承了rclcpp::Node所以它本质上就是一个带有 ROS2 节点能力的类。在main()函数中我们可以这样创建它auto node std::make_sharedLimoTopicSub();然后交给 ROS2 运行rclcpp::spin(node);这里spin()能运行node是因为LimoTopicSub继承了rclcpp::Node。所以可以总结成一句话rclcpp::Node 提供 ROS2 节点基础能力LimoTopicSub 继承它之后就可以作为一个 ROS2 节点运行。2.4 LimoTopicSub 和 rclcpp::Node 是完全一样的吗不是完全一样。更准确地说LimoTopicSub rclcpp::Node 的基础功能 自己新增的订阅功能rclcpp::Node本身提供基础节点能力。而LimoTopicSub在这个基础上自己又添加了构造函数 LimoTopicSub()订阅者对象 cmd_sub_订阅回调函数 cmd_callback()所以它不是一个普通节点而是一个专门用来订阅 /cmd_vel 话题的自定义 ROS2 节点类。可以这样理解1. rclcpp::NodeROS2 官方提供的基础节点类。2. LimoTopicSub继承 rclcpp::Node并添加 /cmd_vel 订阅逻辑后的自定义节点类。三、public 公有成员详解在这段代码中public里面主要有一个成员LimoTopicSub() : Node(limo_topic_sub) { cmd_sub_ this-create_subscriptiongeometry_msgs::msg::Twist( /cmd_vel, 10, std::bind(LimoTopicSub::cmd_callback, this, _1) ); RCLCPP_INFO(this-get_logger(), limo_topic_sub node has started.); }这个成员就是构造函数。3.1 构造函数LimoTopicSub()LimoTopicSub() : Node(limo_topic_sub)这是类的构造函数。构造函数的特点是函数名和类名相同 ✔✔✔没有返回值创建对象时自动调用 ✔✔✔也就是说当main()函数执行它的作用是创建 LimoTopicSub 对象时自动执行这里面的代码也就是说当 main 函数里执行auto node std::make_sharedLimoTopicSub();创建一个 LimoTopicSub 对象并用 std::shared_ptr 智能指针管理它。node 是一个智能指针它指向一个 LimoTopicSub 节点对象。就会自动进入这个构造函数。LimoTopicSub()这个构造函数。3.2 构造函数里面做了什么构造函数里面主要做了两件事情。第一件事创建/cmd_vel订阅者。cmd_sub_ this-create_subscriptiongeometry_msgs::msg::Twist( /cmd_vel, 10, std::bind(LimoTopicSub::cmd_callback, this, _1) );第二件事打印节点启动日志。RCLCPP_INFO(this-get_logger(), limo_topic_sub node has started.);所以构造函数的作用可以总结为创建 LimoTopicSub 节点对象时自动初始化节点创建 /cmd_vel 订阅者并打印节点启动日志。3.3Node(limo_topic_sub)是什么意思这一行LimoTopicSub() : Node(limo_topic_sub)其中的Node(limo_topic_sub)表示调用父类rclcpp::Node的构造函数。它的作用是初始化 ROS2 节点部分并把节点名称设置为 limo_topic_sub。所以节点启动后可以通过命令查看ros2 node list正常情况下会看到/limo_topic_sub注意Node(limo_topic_sub)不是普通成员变量也不是普通函数调用。它是在初始化父类rclcpp::Node。也就是说创建LimoTopicSub对象时先要把它继承来的rclcpp::Node那部分初始化好并给这个节点起名叫limo_topic_sub3.4this-create_subscription()是什么意思代码如下cmd_sub_ this-create_subscriptiongeometry_msgs::msg::Twist( /cmd_vel, 10, std::bind(LimoTopicSub::cmd_callback, this, _1) );这里的this-create_subscription表示调用当前节点对象的create_subscription()函数。这个函数是从rclcpp::Node继承来的。它的作用是创建一个订阅者。完整参数可以这样理解this-create_subscriptiongeometry_msgs::msg::Twist( /cmd_vel, // 订阅的话题名 10, // 队列深度 std::bind(LimoTopicSub::cmd_callback, this, _1) // 回调函数 );也就是说创建一个订阅者订阅 /cmd_vel 话题消息类型是 geometry_msgs::msg::Twist当收到消息时自动调用 cmd_callback()。3.5RCLCPP_INFO(this-get_logger(), ...)是什么意思代码如下RCLCPP_INFO(this-get_logger(), limo_topic_sub node has started.);这里可以拆成两部分看。第一部分this-get_logger()表示获取当前节点的日志器。第二部分RCLCPP_INFO(...)表示以 INFO 级别打印日志。所以整体意思是使用当前节点的日志系统打印一条 INFO 级别的日志。终端输出类似[INFO] [limo_topic_sub]: limo_topic_sub node has started.四、private 私有成员详解在这段代码中private里面有两个成员void cmd_callback(const geometry_msgs::msg::Twist::SharedPtr msg) { ... } rclcpp::Subscriptiongeometry_msgs::msg::Twist::SharedPtr cmd_sub_;分别是cmd_callback()成员函数 / 回调函数 cmd_sub_成员变量 / 订阅者对象4.1 成员函数cmd_callback()代码如下void cmd_callback(const geometry_msgs::msg::Twist::SharedPtr msg) { RCLCPP_INFO(this-get_logger(), 收到速度指令linear.x %.2f, angular.z %.2f, msg-linear.x, msg-angular.z); }这是一个私有成员函数。它的作用是当 /cmd_vel 话题收到 Twist 消息时自动执行这个函数。也就是说它是一个订阅回调函数。4.2 为什么说 cmd_callback() 是回调函数因为它不是我们主动调用的。代码中没有写cmd_callback();它是在创建订阅者时交给了 ROS2std::bind(LimoTopicSub::cmd_callback, this, _1)意思是以后 /cmd_vel 收到消息时 ROS2 自动调用 LimoTopicSub 类中的 cmd_callback() 函数。所以它的执行流程是发布端发布 /cmd_vel 消息 ↓ 订阅端收到 Twist 消息 ↓ ROS2 自动触发 cmd_callback() ↓ cmd_callback() 读取 msg 数据并打印4.3const geometry_msgs::msg::Twist::SharedPtr msg是什么回调函数参数是const geometry_msgs::msg::Twist::SharedPtr msg可以拆开理解geometry_msgs::msg::Twist表示消息类型是Twist。SharedPtr表示智能指针。msg表示变量名。所以这一整句的意思是接收一个 Twist 类型的消息指针 这个指针变量名叫 msg。当/cmd_vel话题收到消息后ROS2 会把收到的消息传给msg。所以在回调函数里面可以通过msg-linear.x msg-angular.z访问速度数据。4.4 为什么是msg-linear.x因为msg是一个指针。在 C 中普通对象访问成员用 . 指针访问成员用 -比如普通对象vel_cmd.linear.x指针对象msg-linear.x所以msg-linear.x表示访问 msg 指向的 Twist 消息中的 linear.x 字段。同理msg-angular.z表示访问 msg 指向的 Twist 消息中的 angular.z 字段。4.5 成员变量cmd_sub_代码如下rclcpp::Subscriptiongeometry_msgs::msg::Twist::SharedPtr cmd_sub_;这是一个私有成员变量。它的作用是保存 /cmd_vel 订阅者对象。完整拆开看rclcpp::Subscriptiongeometry_msgs::msg::Twist表示这是一个ROS2 订阅者类型订阅的消息类型是geometry_msgs::msg::Twist。SharedPtr表示智能指针。cmd_sub_是变量名表示 command velocity subscriber也就是速度指令订阅者。所以整体意思是定义一个订阅者对象指针 cmd_sub_ 它用于保存 /cmd_vel 话题的订阅者。4.6 为什么 cmd_sub_ 要写成成员变量因为订阅者对象必须在节点运行期间一直存在。如果订阅者对象只是一个普通局部变量那么构造函数执行结束后局部变量可能被释放订阅关系也可能失效。例如不推荐这样写auto cmd_sub this-create_subscriptiongeometry_msgs::msg::Twist( /cmd_vel, 10, std::bind(LimoTopicSub::cmd_callback, this, _1) );这里的cmd_sub是局部变量。构造函数执行结束后它的生命周期就结束了。所以更稳妥的写法是rclcpp::Subscriptiongeometry_msgs::msg::Twist::SharedPtr cmd_sub_;然后在构造函数中赋值cmd_sub_ this-create_subscriptiongeometry_msgs::msg::Twist( /cmd_vel, 10, std::bind(LimoTopicSub::cmd_callback, this, _1) );这样cmd_sub_就会随着LimoTopicSub节点对象一起存在。节点不销毁订阅者对象就一直存在。五、ROS2 C 常见写法归纳5.1geometry_msgs::msg::Twistgeometry_msgs::msg::Twist是ROS2 官方提供的一种消息类型常用于表示速度。它里面主要有两个成员linear angular其中linear.x linear.y linear.z表示线速度。angular.x angular.y angular.z表示角速度。小车控制中最常用的是linear.x angular.z含义是linear.x小车前进 / 后退速度 angular.z小车绕 z 轴旋转速度例如vel_cmd.linear.x 0.1; vel_cmd.angular.z 0.0;表示小车以 0.1 m/s 向前运动不旋转。5.2std::bindstd::bind(LimoTopicSub::cmd_callback, this, _1)std::bind是 C 标准库提供的函数绑定工具。它的作用是把类里面的成员函数绑定成一个可以被 ROS2 调用的回调函数。因为cmd_callback()是类成员函数不能像普通函数一样直接传给 ROS2。所以需要写成std::bind(LimoTopicSub::cmd_callback, this, _1)可以理解为请 ROS2 将来使用当前这个 LimoTopicSub 对象去调用它里面的 cmd_callback() 函数并把收到的消息作为第一个参数传进去。5.3std::placeholders::_1using std::placeholders::_1;这句话表示引入占位符_1。_1的作用是占住第一个参数的位置。在订阅回调中std::bind(LimoTopicSub::cmd_callback, this, _1)其中_1对应的是收到的消息msg。也就是说当 /cmd_vel 收到消息时ROS2 会把这条消息作为第一个参数传给 cmd_callback()。如果是 Service 回调通常会看到std::bind(LimoSrvServer::srv_callback, this, _1, _2)其中_1 对应 request _2 对应 response5.4SharedPtrROS2 C 中经常看到SharedPtr例如geometry_msgs::msg::Twist::SharedPtr msg或者rclcpp::Subscriptiongeometry_msgs::msg::Twist::SharedPtr cmd_sub_;SharedPtr本质上是智能指针。可以简单理解为SharedPtr 用来安全管理对象生命周期避免手动 new 和 delete。在 ROS2 中很多对象都用SharedPtr管理例如节点对象发布者对象订阅者对象定时器对象消息对象服务对象客户端对象5.5std::make_shared主函数中有这样一行auto node std::make_sharedLimoTopicSub();意思是创建一个 LimoTopicSub 类型的智能指针对象。它等价于创建一个 LimoTopicSub 节点对象并交给智能指针管理。为什么用智能指针因为 ROS2 中节点通常需要在运行期间一直存在使用智能指针可以更安全地管理节点生命周期。5.6this代码中经常出现this-create_subscription(...) this-get_logger()这里的this表示当前对象自己。在本例中this指向当前的LimoTopicSub节点对象。所以this-create_subscription(...)意思是当前这个节点对象创建一个订阅者。this-get_logger()意思是获取当前这个节点对象的日志器。5.7::作用域解析符ROS2 C 代码中经常有很多::例如rclcpp::Node geometry_msgs::msg::Twist std::placeholders::_1这里的::是 C 的作用域解析符。可以简单理解为到某个命名空间或类里面去找东西。例如1rclcpp::Node表示到 rclcpp 命名空间中找 Node。例如2geometry_msgs::msg::Twist表示到 geometry_msgs 命名空间中再到 msg 命名空间中找到 Twist 类型。例如3std::bind表示到 std 标准库命名空间中找 bind。六、哪些是 LimoTopicSub 自己的成员表格总结代码类型是否属于 LimoTopicSub 自己定义的成员LimoTopicSub()构造函数是cmd_callback()成员函数 / 回调函数是cmd_sub_成员变量 / 订阅者对象是Node(limo_topic_sub)父类构造函数调用不是普通成员this-create_subscription()从rclcpp::Node继承来的函数不是自己定义的成员this-get_logger()从rclcpp::Node继承来的函数不是自己定义的成员_1占位符不是成员msg回调函数参数不是类成员geometry_msgs::msg::Twist消息类型不是类成员rclcpp::Subscription订阅者类型不是类成员所以LimoTopicSub里面真正自己定义的核心成员主要有三个LimoTopicSub()构造函数创建节点对象时自动执行用来初始化节点并创建订阅者。cmd_callback()成员函数收到/cmd_vel消息时自动触发用来处理接收到的速度消息。cmd_sub_成员变量保存/cmd_vel订阅者对象让订阅者在节点运行期间一直存在七、总结本篇文章主要围绕LimoTopicSub订阅节点类详细拆解了 ROS2 C 中类成员、继承、回调函数和订阅者对象的关系。核心结论如下rclcpp::Node 是 ROS2 C 提供的节点基类 里面封装了节点名称、日志、话题、服务、定时器、参数等基础功能。LimoTopicSub 继承 rclcpp::Node 后 就拥有了 ROS2 节点的基本能力 可以调用 create_subscription() 创建订阅者 也可以调用 get_logger() 打印日志。LimoTopicSub() 是构造函数 创建节点对象时自动执行 主要负责初始化节点和创建 /cmd_vel 订阅者。cmd_sub_ 是成员变量 用于保存 /cmd_vel 订阅者对象 保证订阅者在节点运行期间一直存在。cmd_callback() 是订阅回调函数 当 /cmd_vel 收到 Twist 消息时 ROS2 会自动调用它 并通过 msg-linear.x 和 msg-angular.z 读取速度信息。最后用一句话总结LimoTopicSub 是一个继承自 rclcpp::Node 的自定义 ROS2 订阅节点类它通过构造函数创建订阅者通过 cmd_sub_ 保存订阅对象通过 cmd_callback() 处理收到的 /cmd_vel 速度消息。
郑州网站建设
网页设计
企业官网