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