在发布端发布的数据或者在订阅端订阅数据时,数据通常是连续发布或者被订阅的,这些信息没有特定的标识,搞得我们发和收了很多消息,都不知道具体发布了多少个数据,收了多少个数据。而且在ROS中有许多坐标系,我们光凭借数据也无法得知这些XYZ坐标数据到底是哪个坐标时下的,并且在机器人运动时,我们想要加上一些“时间戳”来显示机器人随着时间运动的状态或者规律。总之,我们想要添加一些数据标识来得知“这条数据到底是我们发送/被订阅到的第几条数据,这条数据到底是那个坐标系下的,什么时间发布的/被订阅到的”。根据这三个需求,我们找到了自定义消息文件下的header,这个是官方已经给我们定义好了的,我们直接用就可以了。注意Header只适用于话题通信,因为服务通信只进行一次通信就会结束要header也没用。
Header类类型包括stamp时间戳,frame_id坐标系名称(该参数是一个字符串类型,可以选择的地图类型如下连接中有详细介绍),seq顾名思义这就是一个记录消息序号的整型变量。
frame_id参数详解:Autolabor Simulation预备知识——坐标轴与坐标系 - 简书
Header自定义消息文件,我们只需要在publisher/server发布信息的一侧实例化header类对象并给予信息赋值,最终以“与自定义信息发布相同的频率”发布即可。其实header本质上也是一个自定义消息文件,它的发布和我们自定义/一般类型的消息发布操作是一样的。代码如下:
① publisher代码:
#include "ros/ros.h"
#include "std_msgs/Header.h"
#include "hello_ros/person.h"
int main(int argc, char* argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"pub");
ros::NodeHandle nh;
ros::Publisher pub_object = nh.advertise("chatter",10);
hello_ros::person p;
p.header.seq = 1;
p.header.stamp = ros::Time::now();
p.header.frame_id = "earth"; // 我这里选择earth类型的坐标系
p.height = 0.2;
p.age = 10;
p.name = "father";
ros::Rate r(10);
ros::Duration(3);
while(ros::ok()) {
pub_object.publish(p);
ROS_INFO("Sended infor:height:%f,name:%s,age:%d",p.height,p.name.c_str(),p.age);
p.header.seq+1;
p.header.stamp = ros::Time::now();
r.sleep();
}
return 0;
}
② subscriber代码:
#include "ros/ros.h"
#include "hello_ros/person.h"
#include "std_msgs/Header.h"
void ShowInf(const hello_ros::person::ConstPtr& ptr) {
ROS_INFO("Time:%d.%d;Seq:%d;Frame:%s:\n\t",ptr->header.stamp.sec,ptr->header.stamp.nsec,ptr->header.seq,ptr->header.frame_id.c_str());
ROS_INFO("Received infor:height:%f,name:%s,age:%d\n\t",ptr->height,ptr->name.c_str(),ptr->age);
}
int main(int argc, char* argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"sub");
ros::NodeHandle nh;
ros::Subscriber sub_object = nh.subscribe("chatter",10,ShowInf);
ros::spin();
return 0;
}
③ person.msg代码:
Header header
int32 age
float32 height
string name
这里切记:数据类型一定要是ROS中支持的,比如C++标准数据类型int,float就不可以用。一旦用错编译的时候就会出现如下错误:
Could not find messages which '/home/sophie/ghl/catkin_ghl/src/learning_communication/msg/Person.msg' depends on. Did you forget to specify generate_messages(DEPENDENCIES ...)?
其中,我们进行了以下步骤的操作:
① 自定义消息文件.msg文件的构建
要使得我们的自定义消息类型中包含header,那么一开始自定义消息文件时就必须在首行加上“Header header”:
Header header
string name
int32 age
float64 height
切记:一定要初次编写.msg文件时就要加上Header header,否则后期再加的话会报如下错误:
The dependencies of the message/service 'hello_ros/Person' have changed. Please rerun cmake.
② header类类型对应的topic的实例化(赋值操作)
person_object.header.frame_id = "frame";
person_object.header.seq++;
person_object.header.stamp = ros::Time::now();
当你的.msg文件中包含Header类对象时,此时你的自定义数据类类型中就已经包含了Header类对象并且将其绑定在你的数据信息中一起发布到订阅端,当你运行完文件publisher和subscriber所在的文件,你就会发现我们可以在订阅端订阅到Header消息:
① publisher端代码运行:
注意:一定在运行前,先执行“source ./devel/setup.bash”命令,启动下一启动项。
② subscriber端代码运行:
注意:一定在运行前,先执行“source ./devel/setup.bash”命令,启动下一启动项。
再运行“rostopic delay /topic_name”就会出现该话题消息所对应的header信息了:
一定要注意,一定要在Header_ros项目文件下运行(我这里的功能包名称为Header_ros)。
这里使用”rostopic delay /topic_name”获取的信息,是通过在通信中捕获header头消息得出的,如果没有header头消息,那么调用该命令无效。
函数原型:void Header_Object.stamp.setNow (const ros::Time &new_now)
函数操作:
std_msgs::Header header;
header.stamp.setNow(ros::Time::now();)
这样就可以把时间设置为当前的时间了。
函数原型:ros::Time& time_object = header_object.stamp.now ()
函数操作:
std_msgs::Header header;
ros::Time time_object = header.stamp.now();
该函数返回的是一个定时器类类型的变量。
变量原型:
u_int32_t = header.stamp.sec; // 秒
u_int32_t = header.stamp.nsec; // 纳秒
Stamp返回的是一个time类类型的定时器变量,time类类型的变量本来就可以和Duration类类型的变量进行+=,-+,+,-等算术运算,而且Stamp是基于time类类型实现的,因此可以使用+,-,-=,+=实现header中的时间戳和Duration的运算以及和timer类类型的赋值运算。
ros::Time header.stamp = header.stamp + ros::Duration(3);
ros::Time header.stamp = header.stamp - ros::Duration(3);
ros::Time header.stamp += ros::Duration(3);
ros::Time header.stamp -= ros::Duration(3);
ros::Time Time_Object = header.stamp;
Stamp返回的是一个time类类型的定时器变量,time类类型的变量本来就可以和Duration类类型的变量进行>=,<=,==,!=等逻辑运算,而且Stamp是基于time类类型实现的,因此可以使用>=,<=,==,!=实现header中的时间戳和timer类类型的逻辑运算。
ros::Time timer = ros::Time::now();
bool header.stamp <= timer ;
bool header.stamp >= timer ;
bool header.stamp == timer ;
bool header.stamp != timer ;
函数原型:void Header_Object.stamp.shutdown()
函数原型:void Header_Object.stamp.sleepUntil(ros::Time time_end)
以下函数的应用实现了程序延迟100秒工作的功能:
std_msgs::Header header;
header.stamp.sleepUntil(ros::Time::now() + ros::Duration(100));
上述代码等价于:
ros::Duration(100).sleep();
函数原型:bool Header_Object.stamp.useSystemTime();
当ROS系统使用的模拟时间和系统时间一致是返回true否则返回false。
什么是ROS模拟时间和ROS系统时间?系统时间就是我们实际的时间,ROS模拟时间是我们使用setNow(ros::Time& Time_Object)函数自己自定义的时间。