ROS中自定义带有header的消息文件

ROS中自定义带有header的消息文件

为什么需要header?

在发布端发布的数据或者在订阅端订阅数据时,数据通常是连续发布或者被订阅的,这些信息没有特定的标识,搞得我们发和收了很多消息,都不知道具体发布了多少个数据,收了多少个数据。而且在ROS中有许多坐标系,我们光凭借数据也无法得知这些XYZ坐标数据到底是哪个坐标时下的,并且在机器人运动时,我们想要加上一些“时间戳”来显示机器人随着时间运动的状态或者规律。总之,我们想要添加一些数据标识来得知“这条数据到底是我们发送/被订阅到的第几条数据,这条数据到底是那个坐标系下的,什么时间发布的/被订阅到的”。根据这三个需求,我们找到了自定义消息文件下的header,这个是官方已经给我们定义好了的,我们直接用就可以了。注意Header只适用于话题通信,因为服务通信只进行一次通信就会结束要header也没用。

Header信息中的元素

Header类类型包括stamp时间戳,frame_id坐标系名称(该参数是一个字符串类型,可以选择的地图类型如下连接中有详细介绍),seq顾名思义这就是一个记录消息序号的整型变量。

frame_id参数详解:Autolabor Simulation预备知识——坐标轴与坐标系 - 简书

Header如何使用?

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

如何在命令行查看header数据呢?

当你的.msg文件中包含Header类对象时,此时你的自定义数据类类型中就已经包含了Header类对象并且将其绑定在你的数据信息中一起发布到订阅端,当你运行完文件publisher和subscriber所在的文件,你就会发现我们可以在订阅端订阅到Header消息:

① publisher端代码运行:

ROS中自定义带有header的消息文件_第1张图片

注意:一定在运行前,先执行“source ./devel/setup.bash”命令,启动下一启动项。

② subscriber端代码运行:

ROS中自定义带有header的消息文件_第2张图片

注意:一定在运行前,先执行“source ./devel/setup.bash”命令,启动下一启动项。

再运行“rostopic delay /topic_name”就会出现该话题消息所对应的header信息了:

ROS中自定义带有header的消息文件_第3张图片

一定要注意,一定要在Header_ros项目文件下运行(我这里的功能包名称为Header_ros)。

这里使用”rostopic delay /topic_name”获取的信息,是通过在通信中捕获header头消息得出的,如果没有header头消息,那么调用该命令无效。

Header中stamp的相关函数说明

设置当前时间的函数

函数原型: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();  

将ROS当前设置为系统时间的函数

函数原型:bool Header_Object.stamp.useSystemTime();

当ROS系统使用的模拟时间和系统时间一致是返回true否则返回false。

什么是ROS模拟时间和ROS系统时间?系统时间就是我们实际的时间,ROS模拟时间是我们使用setNow(ros::Time& Time_Object)函数自己自定义的时间。

你可能感兴趣的:(ROS,c++,header,ROS)