接上一篇我们完成了一个发布者的程序,本篇将完成一个订阅者(接受其他节点消息)的程序
我们将订阅turtlesim_node发布的/turtle1/pose话题,这一话题描述海龟的位置和朝向。通过ROS_INFO_STREAM输出到控制台上。
程序代码
#include <ros / ros.h>
#include <turtlesim / Pose.h>
#include <iomanip>
void poseMessageReceived ( const turtlesim::Pose& msg )
{
ROS_INFO_STREAM( std::setprecision ( 2 ) << std::fixed
<< " position =(" << msg . x << " , " << msg . y << " ) "
<< " *direction=" << msg . theta ) ;
return ;
}
int main ( int argc , char ** argv )
{
ros::init ( argc , argv , " subscribe_to _pose " ) ;
ros::NodeHandle nh ;
ros::Subscriber sub = nh.subscribe ( " turtle1/pose " ,1000, &poseMessageReceived ) ;
ros::spin ( ) ;
}
其CMakelist和package.xml文件配置和上个基本相同,区别就是消息的头文件不同则对应xml文件也不同,更改一下便可以。
程序解读
I、编写回调函数
- 接受消息的代码都放到回调函数中,ROS每接收到一个新的消息将调用一次该回调函数,该回调函数伪代码如下:
void function_name(const package_name::type_name &msg)
{
. . .
}
- 其中package_name和type_name和发布消息时相同,它指明了我们想订阅的话题的消息类。
- 本例程中,回调函数接受类型turtlesim::Pose的消息,所以头文件为turtlesim/Pose.h。
II、创建订阅者对象
代码如下:
ros::Subscriber sub = node_handle.subscribe
(topic_name,queue_size, pointer_to_callback_function);
- node_handle还是句柄
- topic_name是想要订阅的话题的名称,“turtle1/pose”。
- queue_size是接收消息的队列大小。
当有新消息时,保存在一个队列中,直到ROS有机会执行相应的回调函数,此参数表示同一时刻可以储存的消息最大值。通过ros::spin或ros::spinOnce、减少每个回调函数的计算时间来尽量确保接受队列不溢出。
- 最后一个参数是志向回调函数的指针。本例中&poseMessageReceived。
- 当我们建立订阅者时,它会一直与发布消息的节点建立连接。直到此对象被销毁
III、几点不同
- 创建sub对象时没有显式地提到消息类型。实际上subscribe是模板化地,C++编译器会根据后边提供地函数指针自动判断正确的消息类型。
关于ros和ros_spinonce
ros::spinOnce() ;
- 这个代码要求ROS去执行所有挂起地回调函数,然后将控制权限返回给我们。
ros::spin() ;
- 这个代码要求ROS等待并且执行回调函数,直到这个节点关机。
ros::spin大致等于这样一个循环:
while(ros::ok( ))
{
ros::spinOnce();
}
- 使用 ros::spinOnce()还是使用 ros::spin()的建议如下:
你的程序除了响应回调函数,还有其他重复性工作要做吗? 如果答案是“否”: 那么使用 ros::spin(); 否则,合理的选择是写一个循环,做其他需要做的事情,并且周期性地调用 ros::spinOnce()来处理回调。 我们地代码使用 ros::spin(),因为程序唯一的工作就是接收和打印接收到的位姿消息。
程序执行
不要忘记确认为你的包添加了 turtlesim 依赖库,我们使用了 turtlesim/Pose 消息类型。参考 3章复习如何声明依赖库
- 编译和source完毕之后执行其他可以让小乌龟动起来的代码,再执行如下订阅代码:
rosrun subpose subb
展望
下一章我们要系统性地学习ROS地日志系统,ROS_INFO_STREAM仅仅是一小部分。明天导师让我去改个32程序,可能停学一天。
|