ROS通信机制进阶
**
3. 1常用API介绍
**
-
ros::init()
作用:ros初始化函数 参数: (1)argc: 封装实参个数(n+1,其中n为实参个数); (2)argv: 封装参数的数组; (3)name: 为节点命名(唯一性); (4)options: 节点启动选项 返回值为void。 使用: (1)argc 和 argv 的使用:如果按照ROS中特定的格式传入实参,那么ROS可以加以使用,比如用来设置全局参数,给节点命名.... (2)options的使用:节点名称需要保证唯一,会导致一个问题:同一个节点不能重复启动。 结果:ROS中当有重名的节点启动时,之前的节点会被关闭。 需求:特定场景下,需要一个节点多次启动且能正常运行,怎么办? 解决:设置启动项ros::init_options::AnonymousName,当创建ros节点时,会在用户自定义的节点名称后缀随机数,从而避免重名问题。
-
ros::NodeHandle nh;
(1)advertise(): 作用:创建发布者对象; 模板:被发布的消息的类型,如:advertise< std_msgs::String >() 参数:1)话题名 ;2)队列长度;3)latch(可选):如果设置为true,会保存发布方的最后一条消息,并且新的订阅对象连接到发布方时,发布方会将这条消息发送给订阅者; 使用: latch设置为true的作用?以静态地图发布为例:方案1:可以使用固定频率发送地图数据,但是效率低;方案2:可以将地图发布对象的latch设置为true,并且发布方只发送一次数据,每当订阅者连接时,将地图数据发送给订阅者(只发送一次),这样提高了数据的发送效率。例如:advertise<std_msgs::String>("we", 10, true).
-
回旋函数:ros::spinOnce() 和 ros::spin() 二者的返回值均为void,
且无参数。相同点:二者都用于处理回调函数;不同点:ros::spin()是进入了循环执行回调函数,而ros::spinOnce()只会执行一次回调函数(没有循环),在ros::spin()后的语句不会执行到,而ros::spinOnce()后的语句可以执行; -
时间相关的API:
(1)时刻
#include <ros/ros.h> //这里包含时间相关的头文件
/*
需求:演示时间相关操作(获取当前时刻+设定指定时刻)
实现:
1.准备(头文件包含,节点初始化,节点句柄)
2.获取当前时刻、
3.设置指定时刻
*/
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc, argv, "hello_time");
ros::NodeHandle nh; //注意:句柄是必须的,若没有句柄则时间API调用(在nodehangdle中初始化时间操作)失败
//2.获取当前时刻
//now函数会将当前时刻封装 并返回
//当前时刻即调用now函数的时刻
//参考系:1970年01月01日 00:00:00
ros::Time right_now = ros::Time::now();//now函数的返回值也是Time类型的对象
ROS_INFO("当前时刻:%.2f",right_now.toSec()); //将时刻值转化为秒,返回浮点型
ROS_INFO("当前时刻:%d",right_now.sec);//将时刻值转化为秒,返回整型
//3.设置指定时刻(通过构造函数设置,如下列:t1和t2)
ros::Time t1(20,312345678); //构造函数第一个参数表示秒,第二个参数表示纳秒
ros::Time t2(100.35); //构造函数里直接设置一个浮点数的时间
ROS_INFO("t1=%.2f", t1.toSec());
ROS_INFO("t1=%.2f", t2.toSec());
return 0;
}
(2)持续时间:
//持续时间
ROS_INFO("-----------------持续时间-----------------");
ros::Duration du(4.5); //创建一个持续时间对象,构造函数有多个,和时刻对象的构造相似
ros::Time start = ros::Time::now(); //获取当前时刻
ROS_INFO("开始休眠,当前时刻为:%.2f", start.toSec());
du.sleep(); //使用sleep()函数休眠
ros::Time end = ros::Time::now(); //获取当前时刻
ROS_INFO("结束休眠,当前时刻为:%.2f", end.toSec());
(3)时间运算:时刻与持续时间(ros::Time和ros::Duration)、持续时间与持续时间(ros::Duration和ros::Duration)
//时间运算--------------------------------------------------------
ROS_INFO("-------------时刻与持续时间运算--------------");
//获取开始时刻
ros::Time begin = ros::Time::now();
//封装持续时间
ros::Duration du1(5.0);
//计算
ros::Time stop = begin + du1; //这里也可以进行减运算
ROS_INFO("开始的时刻:%.2f", begin.toSec());
ROS_INFO("结束的时刻:%.2f", stop.toSec());
//持续时间也可以相互运算
ros::Duration du_sum = du + du1; //4.5+5.0
ROS_INFO("持续时间相加结果:%.2f", du_sum.toSec()); //持续时间也有toSec()函数来看时间
//注意:时刻之间不能相互运算
下面是4.(1)-(3)的完整源代码:
#include <ros/ros.h> //这里包含时间相关的头文件
/*
需求1:演示时间相关操作(获取当前时刻+设定指定时刻)
实现:
1.准备(头文件包含,节点初始化,节点句柄)
2.获取当前时刻、
3.设置指定时刻
需求2:程序执行中停顿5s
实现:
1.创建持续时间对象ros::Duration du
2.休眠 du.sleep()
需求3:获取程序开始执行的时间,且已知程序运行的时间,计算程序结束的时间
实现:
1.ros::Time::now()获取开始时刻
2.ros::Duration 封装持续时间
3.结束时刻=开始时刻+持续时间
注意:
1.ros::Time 和 ros::Duration可以运算
2.ros::Duration之间可以运算
3.ros::Time之间不可以运算
*/
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc, argv, "hello_time");
ros::NodeHandle nh; //注意:句柄是必须的,若没有句柄则时间API调用(在nodehangdle中初始化时间操作)失败
//2.获取当前时刻
//now函数会将当前时刻封装 并返回
//当前时刻即调用now函数的时刻
//参考系:1970年01月01日 00:00:00
ros::Time right_now = ros::Time::now();//now函数的返回值也是Time类型的对象
ROS_INFO("当前时刻:%.2f",right_now.toSec()); //将时刻值转化为秒,返回浮点型
ROS_INFO("当前时刻:%d",right_now.sec);//将时刻值转化为秒,返回整型
//3.设置指定时刻(通过构造函数设置,如下列:t1和t2)
ros::Time t1(20,312345678); //构造函数第一个参数表示秒,第二个参数表示纳秒
ros::Time t2(100.35); //构造函数里直接设置一个浮点数的时间
ROS_INFO("t1=%.2f", t1.toSec());
ROS_INFO("t1=%.2f", t2.toSec());
//持续时间
ROS_INFO("-----------------持续时间-----------------");
ros::Duration du(4.5); //创建一个持续时间对象,构造函数有多个,和时刻对象的构造相似
ros::Time start = ros::Time::now(); //获取当前时刻
ROS_INFO("开始休眠,当前时刻为:%.2f", start.toSec());
du.sleep(); //使用sleep()函数休眠
ros::Time end = ros::Time::now(); //获取当前时刻
ROS_INFO("结束休眠,当前时刻为:%.2f", end.toSec());
//时间运算--------------------------------------------------------
ROS_INFO("-------------时刻与持续时间运算--------------");
//获取开始时刻
ros::Time begin = ros::Time::now();
//封装持续时间
ros::Duration du1(5.0);
//计算
ros::Time stop = begin + du1; //这里也可以进行减运算
ROS_INFO("开始的时刻:%.2f", begin.toSec());
ROS_INFO("结束的时刻:%.2f", stop.toSec());
//持续时间也可以相互运算
ros::Duration du_sum = du + du1; //4.5+5.0
ROS_INFO("持续时间相加结果:%.2f", du_sum.toSec()); //持续时间也有toSec()函数来看时间
//注意:时刻之间不能相互运算
return 0;
}
(4)设置运行频率:
ros::Rate rate(1); //指定频率
while(true){
ROS_INFO("------code------");
rate.sleep();//休眠,休眠时间 = 1/频率
}
(5)定时器:
ROS中内置了专门的定时器,可以实现与ros::Rate 类似的效果:
基本使用如下:
void cb(const ros::TimerEvent &event){
ROS_INFO("0-------------------0");
ROS_INFO("回调函数执行时刻:%.2f",event.current_real.toSec()); //current_real是ros::Time类型的
}
//创建一个定时器
/*
ros::Timer createTimer(Duration period, //时间间隔(持续时间)
const TimerCallback& callback, //回调函数
bool oneshot = false, //是否循环执行定时器
bool autostart = true) const //是否自动启动定时器
*/
// ros::Timer timer1 = nh.createTimer(ros::Duration(1), cb); //cb是一个回调函数,每隔一s执行一次
// ros::Timer timer2 = nh.createTimer(ros::Duration(1), cb,true); //cb是一个回调函数,每隔一s执行一次
ros::Timer timer3 = nh.createTimer(ros::Duration(1), cb,false,false); //cb是一个回调函数,每隔一s执行一次
timer3.start(); //手动启动定时器
ros::spin();
5.其他函数:
在发布实现时,一般会循环发布消息,循环的判断条件一般由节点状态来控制,C++中可以通过 ros::ok() 来判断节点状态是否正常,而 python 中则通过 rospy.is_shutdown() 来实现判断,导致节点退出的原因主要有如下几种:
节点接收到了关闭信息,比如常用的 ctrl + c 快捷键就是关闭节点的信号;
同名节点启动,导致现有节点退出;
程序中的其他部分调用了节点关闭相关的API(C++中是ros::shutdown(),python中是rospy.signal_shutdown())
另外,日志相关的函数也是极其常用的,在ROS中日志被划分成如下级别:
DEBUG(调试):只在调试时使用,此类消息不会输出到控制台; INFO(信息):标准消息,一般用于说明系统内正在执行的操作; WARN(警告):提醒一些异常情况,但程序仍然可以执行; ERROR(错误):提示错误信息,此类错误会影响程序运行; FATAL(严重错误):此类错误将阻止节点继续运行。
C++节点判断:
(1)节点状态判断:
/** \brief 检查节点是否已经退出
*
* ros::shutdown() 被调用且执行完毕后,该函数将会返回 false
*
* \return true 如果节点还健在, false 如果节点已经火化了。
*/
bool ok();
(2)节点关闭函数:
/*
* 关闭节点
*/
void shutdown();
(3)日志函数:
ROS_DEBUG("hello,DEBUG"); //不会输出
ROS_INFO("hello,INFO"); //默认白色字体
ROS_WARN("Hello,WARN"); //默认黄色字体
ROS_ERROR("hello,ERROR");//默认红色字体
ROS_FATAL("hello,FATAL");//默认红色字体