04C++常用API

C++常用API

初始化

/** @brief ROS初始化函数。
 *
 * 该函数可以解析并使用节点启动时传入的参数(通过参数设置节点名称、命名空间...) 
 *
 * 该函数有多个重载版本,如果使用NodeHandle建议调用该版本。 
 *
 * \param argc 参数个数(n+1)
 * \param argv 封装参数的数组
 * \param name 节点名称,需要保证其唯一性,不允许包含命名空间
 * \param options 节点启动选项,被封装进了ros::init_options
 	返回值void
 *
 */
void init(int &argc, char **argv, const std::string& name, uint32_t options = 0);

/*
使用:
1.argc和argv的使用
	如果按照ROS中的特定格式传入实参,那么ROS可以加以使用,比如用来设置全局参数,给节点重命名。
	
2.options的使用
    节点名称需要保证唯一,同一个节点不能重复启动。
    结果导致:ROS中当有重名的节点启动时,之前的节点就会被关闭。
    
需求:特定场景下,需要一个节点多次启动
解决:
void init(int &argc, char **argv, const std::string& name, ros::init_options::AnonymousName);//当创建ROS节点时,会在用户自定义的节点名称后缀随机数,从而避免重名问题。
    
*/

使用:

  1. argc和argv的使用
    如果按照ROS中的特定格式传入实参,那么ROS可以加以使用,比如用来设置全局参数,给节点重命名。

  2. options的使用
    节点名称需要保证唯一,同一个节点不能重复启动。
    结果导致:ROS中当有重名的节点启动时,之前的节点就会被关闭。

  3. 需求:特定场景下,需要一个节点多次启动
    解决:

	void init(int &argc, char **argv, const std::string& name, ros::init_options::AnonymousName);//当创建ROS节点时,会在用户自定义的节点名称后缀随机数,从而避免重名问题。

话题与服务相关对象

在 roscpp 中,话题和服务的相关对象一般由 NodeHandle 创建。

NodeHandle有一个重要作用是可以用于设置命名空间,这是后期的重点,但是本章暂不介绍。

发布对象

对象获取:

/**
* \brief 根据话题生成发布对象
*
* 在 ROS master 注册并返回一个发布者对象,该对象可以发布消息
*
* 使用示例如下:
*
* ros::Publisher pub = handle.advertise<std_msgs::Empty>("my_topic", 1);
*
* \param topic 发布消息使用的话题
*
* \param queue_size 等待发送给订阅者的最大消息数量
*
* \param latch (optional) 如果为 true,该话题发布的最后一条消息将被保存,并且后期当有订阅者连接时会将该消息发送给订阅者
*
* \return 调用成功时,会返回一个发布对象
*
*
*/
template <class M>
Publisher advertise(const std::string& topic, uint32_t queue_size, bool latch = false)
    
/*

使用:
	latch设置为true的作用?
	以静态低通发布为例,
	方案1:可以适应固定频率发送地图数据,但是效率低;
	方案2:地图发布对象latch设置为true,并且发布方只发送一次数据,没当订阅者连接时,将地图数据发送给订阅者(只发送一次),这样提高了数据的发送效率。

使用:
latch设置为true的作用?
以静态低通发布为例,
方案1:可以适应固定频率发送地图数据,但是效率低;
方案2:地图发布对象latch设置为true,并且发布方只发送一次数据,没当订阅者连接时,将地图数据发送给订阅者(只发送一次),这样提高了数据的发送效率。

消息发布函数:

/**
* 发布消息          
*/
template <typename M>
void publish(const M& message) const

订阅对象

对象获取:

/**
   * \brief 生成某个话题的订阅对象
   *
   * 该函数将根据给定的话题在ROS master 注册,并自动连接相同主题的发布方,每接收到一条消息,都会调用回调
   * 函数,并且传入该消息的共享指针,该消息不能被修改,因为可能其他订阅对象也会使用该消息。
   * 
   * 使用示例如下:

void callback(const std_msgs::Empty::ConstPtr& message)
{
}

ros::Subscriber sub = handle.subscribe("my_topic", 1, callback);

   *
* \param M [template] M 是指消息类型
* \param topic 订阅的话题
* \param queue_size 消息队列长度,超出长度时,头部的消息将被弃用
* \param fp 当订阅到一条消息时,需要执行的回调函数
* \return 调用成功时,返回一个订阅者对象,失败时,返回空对象
* 

void callback(const std_msgs::Empty::ConstPtr& message){...}
ros::NodeHandle nodeHandle;
ros::Subscriber sub = nodeHandle.subscribe("my_topic", 1, callback);
if (sub) // Enter if subscriber is valid
{
...
}

*/
template<class M>
Subscriber subscribe(const std::string& topic, uint32_t queue_size, void(*fp)(const boost::shared_ptr<M const>&), const TransportHints& transport_hints = TransportHints())

服务对象

对象获取:

/**
* \brief 生成服务端对象
*
* 该函数可以连接到 ROS master,并提供一个具有给定名称的服务对象。
*
* 使用示例如下:
\verbatim
bool callback(std_srvs::Empty& request, std_srvs::Empty& response)
{
return true;
}

ros::ServiceServer service = handle.advertiseService("my_service", callback);
\endverbatim
*
* \param service 服务的主题名称
* \param srv_func 接收到请求时,需要处理请求的回调函数
* \return 请求成功时返回服务对象,否则返回空对象:
\verbatim
bool Foo::callback(std_srvs::Empty& request, std_srvs::Empty& response)
{
return true;
}
ros::NodeHandle nodeHandle;
Foo foo_object;
ros::ServiceServer service = nodeHandle.advertiseService("my_service", callback);
if (service) // Enter if advertised service is valid
{
...
}
\endverbatim

*/
template<class MReq, class MRes>
ServiceServer advertiseService(const std::string& service, bool(*srv_func)(MReq&, MRes&))

客户端对象

对象获取:

/** 
  * @brief 创建一个服务客户端对象
  *
  * 当清除最后一个连接的引用句柄时,连接将被关闭。
  *
  * @param service_name 服务主题名称
  */
 template<class Service>
 ServiceClient serviceClient(const std::string& service_name, bool persistent = false, 
                             const M_string& header_values = M_string())

请求发送函数:

/**
   * @brief 发送请求
   * 返回值为 bool 类型,true,请求处理成功,false,处理失败。
   */
  template<class Service>
  bool call(Service& service)

等待服务函数1:

/**
 * ros::service::waitForService("addInts");
 * \brief 等待服务可用,否则一致处于阻塞状态
 * \param service_name 被"等待"的服务的话题名称
 * \param timeout 等待最大时常,默认为 -1,可以永久等待直至节点关闭
 * \return 成功返回 true,否则返回 false。
 */
ROSCPP_DECL bool waitForService(const std::string& service_name, ros::Duration timeout = ros::Duration(-1));

等待服务函数2:

/**
* client.waitForExistence();
* \brief 等待服务可用,否则一致处于阻塞状态
* \param timeout 等待最大时常,默认为 -1,可以永久等待直至节点关闭
* \return 成功返回 true,否则返回 false。
*/
bool waitForExistence(ros::Duration timeout = ros::Duration(-1));

回旋函数

在ROS程序中,频繁的使用了 ros::spin() 和 ros::spinOnce() 两个回旋函数,可以用于处理回调函数。

ROS的消息接收回调机制(callbacks and spinning)原理是这样的:除了用户的主程序以外,ROS的socket连接控制进程会在后台接收订阅的消息,所有接收到的消息并不是立即处理,而是等到spin()或者spinOnce()执行时才集中处理。消息到来并不会立即执行消息处理回调函数,而是在调用ros::spin()或者spinOnce()之后,才进行消息处理的轮转,消息回调函数统一处理订阅话题的消息。

ROS的主循环中需要不断调用ros::spin()或ros::spinOnce(),两者区别在于前者调用后不会再返回,而后者在调用后还可以继续执行之后的程序。

在使用ros::spin()的情况下,一般来说在初始化时已经设置好所有消息的回调,并且不需要其他背景程序运行。这样一来,每次消息到达时会执行用户的回调函数进行操作,相当于程序是消息事件驱动的;

而在使用ros::spinOnce()的情况下,一般来说仅仅使用回调不足以完成任务,还需要其他辅助程序的执行:比如定时任务、数据处理、用户界面等。

spinOnce()

/**
 * \brief 处理一轮回调
 *
 * 一般应用场景:
 *     在循环体内,处理所有可用的回调函数
 * 
 */
ROSCPP_DECL void spinOnce();

执行到spinOnce()回头执行回调函数,执行一次后接着spinOnce()后顺序执行。

Snipaste_2022-03-15_21-15-18

spin()

/** 
 * \brief 进入循环处理回调 
 */
ROSCPP_DECL void spin();

执行到spin(),回头一直循环执行回调函数,这样就会导致spin()后面写的代码不会被执行。

Snipaste_2022-03-15_21-18-53

时间

时刻

ROS中时间相关的API是极其常用,比如:获取当前时刻、持续时间的设置、执行频率、休眠、定时器…都与时间相关。

#include "ros/ros.h"
/* 
需求:演示时间相关的操作(获取当前时刻+设定制定时刻)
    实现:
        1.准备(头文件,节点初始化,NodeHandle创建...)
        2.获取当前时刻
        3.设置制定时刻
 */

int main(int argc, char  *argv[])
{
    setlocale(LC_ALL,"");
    ros::init(argc,argv,"hello_time");
    ros::NodeHandle nh;
    //获取当前时刻
    //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);
    return 0;
    //设置指定时刻
    ros::Time t1(20,3123456);
    ROS_INFO("t1=%.2f",t1.sec());

    
    ros::Time
}

int main(int argc, char  *argv[])
{
    setlocale(LC_ALL,"");
    ros::init(argc,argv,"hello_time");
    ros::NodeHandle nh;//必须创建句柄,否则时间没有初始化,导致后续API调用失败
    ros::Time right_now = ros::Time::now();//将当前时刻封装成对象
    ROS_INFO("当前时刻:%.2f",right_now.toSec());//获取距离 1970年01月01日 00:00:00 的秒数
    ROS_INFO("当前时刻:%d",right_now.sec);//获取距离 1970年01月01日 00:00:00 的秒数

    ros::Time someTime(100,100000000);// 参数1:秒数  参数2:纳秒
    ROS_INFO("时刻:%.2f",someTime.toSec()); //100.10
    ros::Time someTime2(100.3);//直接传入 double 类型的秒数
    ROS_INFO("时刻:%.2f",someTime2.toSec()); //100.30
}

输出:

2022-03-16 09-41-53屏幕截图

持续时间

设置一个时间区间(间隔):

/*
需求:程序执行中停顿5秒
	实现:
		1.创建持续时间对象
		2.休眠
*/
int main(int argc, char  *argv[])
{
    setlocale(LC_ALL,"");
    ros::init(argc,argv,"hello_time");
    ros::NodeHandle nh;//必须创建句柄,否则时间没有初始化,导致后续API调用失败
    ros::Time right_now = ros::Time::now();//将当前时刻封装成对象
    ROS_INFO("当前时刻:%.2f",ros::Time::now().toSec());
    ros::Duration du(10);//持续10秒钟,参数是double类型的,以秒为单位
    du.sleep();//按照指定的持续时间休眠
    ROS_INFO("持续时间:%.2f",du.toSec());//将持续时间换算成秒
    ROS_INFO("当前时刻:%.2f",ros::Time::now().toSec());
}

输出:

2022-03-16 09-46-39屏幕截图

持续时间与时刻运算

  1. Time对象与Duration对象相加减结果都是Time对象

  2. Time对象的相减结果是Duration对象

  3. Time对象之间不能相加

为了方便使用,ROS中提供了时间与时刻的运算:

/*
需求:一直程序开始运动的时刻,和程序运行的时间,求运行结束的时刻
	实现:
		1.获取开始执行的时刻
		2.模拟运行时间(N秒)
		3.计算结束时刻=开始-持续时间
*/
int main(int argc, char  *argv[])
{
    setlocale(LC_ALL,"");
    ros::init(argc,argv,"hello_time");
    ros::NodeHandle nh;//必须创建句柄,否则时间没有初始化,导致后续API调用失败
    ros::Time right_now = ros::Time::now();//将当前时刻封装成对象
    //时刻与持续时间运算
    //1.获取开始执行的时刻
    ros::Time begin = ros::Time::now();
    //2.模拟运行时间
    ros::Duration du1(5);
    //3.计算解散时刻 = 开始+持续时间
    ros::Time stop = begin + du1;
    ROS_INFO("开始时刻“%.2f",begin.toSec());
    ROS_INFO("结束时刻:%.2f",stop.toSec());

    //时刻与时刻运算
    // ros::Time sum = begin + stop;//不可以相加
    ros::Duration du2 = begin - stop;
    ROS_INFO("时刻之间相减:%.2f",du2.toSec());
    //持续时间与持续时间的运算
    ros::Duration du3 = du1+du2;//0
    ros::Duration du4 = du1-du2;//10
    ROS_INFO("du1+du2=%.2f",du3.toSec());
    ROS_INFO("du1-du2=%.2f", du4.toSec());

}

输出:

2022-03-16 10-03-46屏幕截图

设置运行频率

ros::Rate rate(1);//指定频率
while (true)
{
    ROS_INFO("-----------code----------");
    rate.sleep();//休眠,休眠时间 = 1 / 频率。
}

定时器

ROS 中内置了专门的定时器,可以实现与 ros::Rate 类似的效果:

 // ROS 定时器
Timer createTimer(Duration period, const TimerCallback& callback, bool oneshot = false,bool autostart = true) const;

 /**
 
* \brief 创建一个定时器,按照指定频率调用回调函数。
*
* \param period 时间间隔
* \param callback 回调函数
* \param oneshot 如果设置为 true,只执行一次回调函数,设置为 false,就循环执行。
* \param autostart 如果为true,返回已经启动的定时器,设置为 false,需要手动启动。
*/
   timer.start()//手动启动
   定时器启动后需要ros::spin()

/*
    需求:每隔1秒钟,在控制台输出一段文字。
        实现:
            策略1:ros::Rate()
            策略2:定时器
 
 */

void cb(const ros::TimerEvent event){
    ROS_INFO("-------------cb");
}

int main(int argc, char  *argv[])
{
    setlocale(LC_ALL,"");
    ros::init(argc,argv,"hello_time");
    ros::NodeHandle nh;//必须创建句柄,否则时间没有初始化,导致后续API调用失败
    ros::Time right_now = ros::Time::now();//将当前时刻封装成对象
    ROS_INFO("--------------定时器------------");

    ros::Timer timer = nh.createTimer(ros::Duration(1),cb);
    ros::spin();
}

输出:

2022-03-16 10-22-36屏幕截图

其他函数

在发布实现时,一般会循环发布消息,循环的判断条件一般由节点状态来控制,C++中可以通过 ros::ok() 来判断节点状态是否正常,而 python 中则通过 rospy.is_shutdown() 来实现判断,导致节点退出的原因主要有如下几种:

  • 节点接收到了关闭信息,比如常用的 ctrl + c 快捷键就是关闭节点的信号;
  • 同名节点启动,导致现有节点退出;
  • 程序中的其他部分调用了节点关闭相关的API(C++中是ros::shutdown(),python中是rospy.signal_shutdown())

另外,日志相关的函数也是极其常用的,在ROS中日志被划分成如下级别:

  • DEBUG(调试):只在调试时使用,此类消息不会输出到控制台;
  • INFO(信息):标准消息,一般用于说明系统内正在执行的操作;
  • WARN(警告):提醒一些异常情况,但程序仍然可以执行;
  • ERROR(错误):提示错误信息,此类错误会影响程序运行;
  • FATAL(严重错误):此类错误将阻止节点继续运行。

节点状态判断

/** \brief 检查节点是否已经退出
 *
 *  ros::shutdown() 被调用且执行完毕后,该函数将会返回 false
 *
 * \return true 如果节点还健在, false 如果节点已经火化了。
 */
bool ok();

节点关闭函数

/*
*   关闭节点
*/
void shutdown();

日志函数

使用示例

ROS_DEBUG("hello,DEBUG"); //不会输出
ROS_INFO("hello,INFO"); //默认白色字体
ROS_WARN("Hello,WARN"); //默认黄色字体
ROS_ERROR("hello,ERROR");//默认红色字体
ROS_FATAL("hello,FATAL");//默认红色字体

2022-03-16 10-38-52屏幕截图

RROR(错误):提示错误信息,此类错误会影响程序运行;

  • FATAL(严重错误):此类错误将阻止节点继续运行。

节点状态判断

/** \brief 检查节点是否已经退出
 *
 *  ros::shutdown() 被调用且执行完毕后,该函数将会返回 false
 *
 * \return true 如果节点还健在, false 如果节点已经火化了。
 */
bool ok();

节点关闭函数

/*
*   关闭节点
*/
void shutdown();

日志函数

使用示例

ROS_DEBUG("hello,DEBUG"); //不会输出
ROS_INFO("hello,INFO"); //默认白色字体
ROS_WARN("Hello,WARN"); //默认黄色字体
ROS_ERROR("hello,ERROR");//默认红色字体
ROS_FATAL("hello,FATAL");//默认红色字体
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值