目录
简介
C++
初始化
话题与服务相关对象
回旋函数
ros::spinOnce()
ros::spin()
时间相关API
时刻
持续时间
持续时间与时刻运算
定时器
节点生命周期API
日志输出API
Python
初始化API
anonymous
话题与服务对象相关API
回旋函数
时间相关API
时刻API
持续时间(设置一个间隔)
持续时间与时刻运算
运行频率
定时器 (间隔某个时间间隔执行操作)
节点相关API
节点状态判断
节点关闭函数
日志输出
API个人理解:比如说ros::init这个函数其实你可以在代码中输入这个函数,然后编译后,ros系统内部会根据这个函数来对应重置节点,所以中文把API理解为接口,其实API就是一个命令去改动封装好的东西里的参数,对齐进行使用和更改
首先,建议参考官方API文档或参考源码:
参数服务器相关API在第二章已经有详细介绍和应用,在此不再赘述。2.3.2 参数操作A(C++) · Autolabor-ROS机器人入门课程《ROS理论与实践》零基础教程
另请参考:
APIs - ROS Wiki
roscpp: roscpp
这个包就包含了很多重要的消息封装,速度消息,传感器消息等
是很重要的API
这个库就是之前一直用的很多命令,roscpp、rosparam、rospy等
roscpp是ros一个关于c++实现的库,实现和ros的快速交互
通过话题、服务、参数来进行交互
是一个高性能库实现
还有关于roscpp的API文档roscpp: roscpp
上面介绍了常用的API
以ros::NodeHandle为例roscpp: ros::NodeHandle Class Reference
里面介绍了该接口的详细应用
rospy
介绍中说rospy并不是旨在提升性能的,而是为了更便利
新建功能包
ros::init(argc,argv,"erGouzi")
/*
参数:
1.argc-----封装实参的个数(N+1)传入的第一个是文件自身
2.argv-----封装参数的数组
3.name-----为节点命名(唯一性)
4.options--节点启动选项
返回值:void
使用:
1.argc与argv的使用
如果按照ROS中的特定格式传入实参,那么ROS可以加以使用,比如用来设置全局参数,给节点重命名
2.options的使用
节点名称需要保证唯一,会导致一个问题,同一个节点不能重复启动,如果重名,之前的节点会被关闭。
需求:特定场景下,需要一个节点多次启动且能正常运行,怎么办,此时就用options
解决:设置启动项 ros::init_options::AnonymousName
当创建ROS节点时,会在用户自定义的节点名称后缀岁数,从而避免重名问题
*/
此时_length就被传递给argv了
ROS就会解析它,把它放进参数服务器
需要一个节点启动多个
这样每次启动会给节点名后面加上一个随机数避免了名称重复问题
成功一个节点被启动两次
此时可以看到有后缀
其实就是产生的随机数值(不重复)
在 roscpp 中,话题和服务的相关对象一般由 NodeHandle 创建。
NodeHandle有一个重要作用是可以用于设置命名空间,这是后期的重点,但是本章暂不介绍。
创建发布对象调用的API是来自
ros::NodeHandle nh;
再通过NodeHandle调用nh.advertise函数
这个函数会返回一个发布者对象
nh.advertise
作用:创建发布者对象
模板:被发布的消息类型
参数:
1.话题名称
2.队列长度
3.latch(可选)
是一个bool值 如果设置了true它会保存发布方最后一条消息,并且新的订阅对象连接到发布方时,发布方会将这条消息发送给订阅者
使用:
latch设置为true的作用?
只发一次地图信息,但将latch设置为true,只要有新的订阅方连接就发送一次
以静态地图发布为例,
方案1:可以使用固定频率发送地图数据,但效率太低了
方案2:可以将latch设置为true,并且发布方只发送一次数据,每当订阅者连接时,将地图数据 发送给订阅者 (只发送一次),这样提高了数据的发送效率
ctrl+【 组合键是代码对齐
启动终端
先让发布方发完十条消息,此时发布方不发数据了
编写接收方(跟之前的话题通信的代码一致)
然后对应修改配置并且进行编译
运行终端
没有接收到任何数据
然后关掉终端使用latch--》改为true
然后编译终端运行
先启动发布方等数据发完
数据发完了,然后启动订阅方
此时可以看到不同于上面,改为true后,订阅方拿到了最后一条数据
在ROS程序中,频繁的使用了 ros::spin() 和 ros::spinOnce() 两个回旋函数,可以用于处理回调函数。
相同点:二者都用于处理回调函数;
不同点:ros::spin() 是进入了循环执行回调函数,而 ros::spinOnce() 只会执行一次回调函数(没有循环),在 ros::spin() 后的语句不会执行到,而 ros::spinOnce() 后的语句可以执行。
加这一句代码,编译执行
可以看到ros::spinOnce()只回调一次函数(回调到循环前的位置)
加上这一句
编译执行
看不到ROS_INFO的打印信息
证明spin函数回调到回调函数并且让它自身内循环
ROS中时间相关的API是极其常用,比如:获取当前时刻、持续时间的设置、执行频率、休眠、定时器...都与时间相关。
新建文件并配置
这是初始化操作
注意ros::NodeHandle这一步是初始化时间,如果没有会导致API调用失败
ros::Time right_now::Time::now();
//now函数会将当前时刻封装并返回
//当前时刻:now被调用执行的那一刻
//时间有参考系:(ex:当前时间是参考公元元年计算)计算机的时间参考是参考1970年1月1日00:00:00
(我们是东八区加8个小时)
right_now.toSec()可以将逝去时刻转换为秒为单位,输出数据类型时double类型
right_now.sec返回的时整形的
完整代码如下
编译执行
此时就获取到时间了
ros::Time t1(20,312424);设置指定时刻
ros::Time t2(20.312424);
ROS_INFO("t1=%.2f",t1.toSec());
ROS_INFO("t2=%.2f",t2.toSec());
编译执行
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());
复制以下代码编译执行
休眠了4.5s
需求3:已知程序开始运行时刻,和程序运行时间,求运行结束时刻
1.获取开始运行时刻
2.模拟运行时间
3.计算时间=开始-持续时间
ROS_INFO("时间运算");
ros::Time now = ros::Time::now();
ros::Duration du1(10);
ros::Duration du2(20);
ROS_INFO("当前时刻:%.2f",now.toSec());
//1.time 与 duration 运算
ros::Time after_now = now + du1;
ros::Time before_now = now - du1;
ROS_INFO("当前时刻之后:%.2f",after_now.toSec());
ROS_INFO("当前时刻之前:%.2f",before_now.toSec());
//2.duration 之间相互运算
ros::Duration du3 = du1 + du2;
ros::Duration du4 = du1 - du2;
ROS_INFO("du3 = %.2f",du3.toSec());
ROS_INFO("du4 = %.2f",du4.toSec());
PS: time 与 time 不可以相加
比如 ros::Time nn = now + before_now;//异常
复制以下代码
编译运行
编译运行
运行时间5s
ROS 中内置了专门的定时器,可以实现与 ros::Rate 类似的效果:
ros::NodeHandle nh;//必须创建句柄,否则时间没有初始化,导致后续API调用失败
需求4:每隔1s再控制台输出一段文本
1.策略1:ros::Rate()
2.策略2:定时器
ros::Timer =nh.createTimer()
ros::Timer createTimer(ros::Duration period, 时间间隔---1s
const ros::TimerCallback &callback, 回调函数----封装业务(输出文本)
bool oneshot = false, 是否是一次性--根据bool值
bool autostart = true) 自动启动
编写回调函数(因为需求里就要求输出一段文本,所以就写了一个ROS_INFO
以下为定时器实现
打开终端测试
成功
间隔一秒输出文本------
下面是修改bool oneshot这个参数
ros::Timer createTimer(ros::Duration period, 时间间隔---1s
const ros::TimerCallback &callback, 回调函数----封装业务(输出文本)
bool oneshot = false, 是否是一次性--根据bool值
此时只输出一条消息
再看最后一个参数
bool autostart = true) 自动启动
编译执行
未发送消息,这是由于关闭了自动启动
此时可以调用上图的函数进行手动启动
定时器启动成功
编译执行
成功了
注意:
创建:nh.createTimer()
参数一:时间间隔
参数二:回调函数(时间事件 TimerEvent)
参数三:是否只执行一次
参数四:是否自动启动(当设置为false 需要手动调用 time.start())
定时器启动:ros::spin()
在发布实现时,一般会循环发布消息,循环的判断条件一般由节点状态来控制,C++中可以通过 ros::ok() 来判断节点状态是否正常
- 节点接收到了关闭信息,比如常用的 ctrl + c 快捷键就是关闭节点的信号;
- 同名节点启动,导致现有节点退出;
- 程序中的其他部分调用了节点关闭相关的API(C++中是ros::shutdown()
加上这一段
编译执行
然后自动终止了
日志相关的函数也是极其常用的,在ROS中日志被划分成如下级别:
- DEBUG(调试):只在调试时使用,此类消息不会输出到控制台;
- INFO(信息):标准消息,一般用于说明系统内正在执行的操作;
- WARN(警告):提醒一些异常情况,但程序仍然可以执行;
- ERROR(错误):提示错误信息,此类错误会影响程序运行;
- FATAL(严重错误):此类错误将阻止节点继续运行。
新建文件
并进行相关配置
ROS中的日志
演示不同级别的日志基本使用
复制以下代码
ROS_DEBUG("hello,DEBUG"); //不会输出
ROS_INFO("hello,INFO"); //默认白色字体
ROS_WARN("Hello,WARN"); //默认黄色字体
ROS_ERROR("hello,ERROR");//默认红色字体
ROS_FATAL("hello,FATAL");//默认红色字体
ctrl + 】 组合键是右对齐
编译执行
- ROS节点的初始化相关API;
- NodeHandle 的基本使用相关API;
- 话题的发布方,订阅方对象相关API;
- 服务的服务端,客户端对象相关API;
- 时间相关API;
- 日志输出相关API。
新建对应文件和修改配置添加权限
ros::init_node该API函数作用是初始化
def init_node(name,--设置节点名称
argv=None, -----封装节点调用时传递到=的参数
anonymous=False, ----可以为节点名称生成随机后缀,可以解决重名问题
log_level=None,
disable_rostime=False,
disable_rosout=False,
disable_signals=False,
xmlrpc_port=0,
tcpros_port=0):
不传参的话就是单纯发布
当我们传参时
可以看到参数传入成功
anonymous=False, ----可以为节点名称生成随机后缀,可以解决重名问题
此时启动
再开一个终端启动时
上面的节点被关闭
如果我们想让一个节点多次启动,就得让它的节点名称每次不一样
原因是后缀了随机数,避免重复问题
class Publisher(Topic):
"""
在ROS master注册为相关话题的发布方
"""
def __init__(self, name, data_class, subscriber_listener=None, tcp_nodelay=False, latch=False, headers=None, queue_size=None):
"""
Constructor
@param name: 话题名称
@type name: str
@param data_class: 消息类型
@param latch: 如果为 true,该话题发布的最后一条消息将被保存,并且后期当有订阅者连接时会将该消息发送给订阅者
@type latch: bool
@param queue_size: 等待发送给订阅者的最大消息数量
@type queue_size: int
"""
latch:bool值,默认是Falese
如果设置为True,可以将最后一条发布数据保存,且后续有连接者时,会将该数据发送给订阅者
复制以下代码
import rospy
from std_msgs.msg import String
if __name__ == "__main__":
#2.初始化 ROS 节点:命名(唯一)
rospy.init_node("talker_p",anonymous=True)
#3.实例化 发布者 对象
pub = rospy.Publisher("chatter",String,queue_size=10,latch=True)
#4.组织被发布的数据,并编写逻辑发布数据
msg = String() #创建 msg 对象
msg_front = "hello 你好"
count = 0 #计数器
# 设置循环频率
rate = rospy.Rate(1)
while not rospy.is_shutdown():
count += 1
#拼接字符串
if count<=10:
msg.data = msg_front + str(count)
pub.publish(msg)
rate.sleep()
rospy.loginfo("写出的数据:%s",msg.data)
rate.sleep()
编译终端实现
此时订阅了最后一条消息
但如果不改latch
结果如下
没有消息
def spin():
"""
进入循环处理回调
"""
ROS中时间相关的API是极其常用,比如:获取当前时刻、持续时间的设置、执行频率、休眠、定时器...都与时间相关。
'''
需求1:演示时间相关操作(获取当前时刻+设置指定时刻)
需求2:程序执行中停顿五秒
需求3:获取程序开始执行时刻,且已知持续运行时间,计算程序结束时间
需求4:创建定时器,实现类似于ros::Rate 的功能(隔某个时间间隔执行某种操作)
'''
新建文件并授权编译
复制以下代码
#! /usr/bin/env python
#coding=utf-8
import rospy
if __name__ == "__main__":
rospy.init_node("hello_time")
# 获取当前时刻
right_now = rospy.Time.now()
rospy.loginfo("当前时刻:%.2f",right_now.to_sec())
rospy.loginfo("当前时刻:%.2f",right_now.to_nsec())
# 自定义时刻
some_time1 = rospy.Time(1234.567891011)
some_time2 = rospy.Time(1234,567891011)
rospy.loginfo("设置时刻1:%.2f",some_time1.to_sec())
rospy.loginfo("设置时刻2:%.2f",some_time2.to_sec())
# 从时间创建对象
# some_time3 = rospy.Time.from_seconds(543.21)
some_time3 = rospy.Time.from_sec(543.21) # from_sec 替换了 from_seconds
rospy.loginfo("设置时刻3:%.2f",some_time3.to_sec())
pass
编译并且运行
设置一个时间区间(间隔):
加入以下代码
#需求2:程序执行中停顿5s
#1.封装一个时间持续对象
# 持续时间相关API
rospy.loginfo("持续时间测试开始.....")
du = rospy.Duration(3.3)
rospy.loginfo("du1 持续时间:%.2f",du.to_sec())
rospy.sleep(du) #休眠函数
rospy.loginfo("持续时间测试结束.....")
ctrl+ 】时对齐代码
'''
需求3:获取程序开始执行的时刻,且已知持续运行时间,计算程序结束的时间
'''
#获取一个时间
rospy.loginfo("时间运算")
now = rospy.Time.now()
du1 = rospy.Duration(10)
du2 = rospy.Duration(20)
rospy.loginfo("当前时刻:%.2f",now.to_sec())
before_now = now - du1
after_now = now + du1
dd = du1 + du2
# now = now + now #非法
rospy.loginfo("之前时刻:%.2f",before_now.to_sec())
rospy.loginfo("之后时刻:%.2f",after_now.to_sec())
rospy.loginfo("持续时间相加:%.2f",dd.to_sec())
注意:时刻之间无法相加,但可以相减
# 设置执行频率
rate = rospy.Rate(0.5)
while not rospy.is_shutdown():
rate.sleep() #休眠
rospy.loginfo("+++++++++++++++")
ROS 中内置了专门的定时器,可以实现与 ros::Rate 类似的效果:
需求:创建定时器
#定时器设置
"""
def __init__(self, period, callback, oneshot=False, reset=False):
Constructor.
@param period: 回调函数的时间间隔
@type period: rospy.Duration
@param callback: 回调函数
@type callback: function taking rospy.TimerEvent
@param oneshot: 设置为True,就只执行一次,否则循环执行
@type oneshot: bool
@param reset: if True, timer is reset when rostime moved backward. [default: False]
@type reset: bool
"""
rospy.Timer(rospy.Duration(1),doMsg)
# rospy.Timer(rospy.Duration(1),doMsg,True) # 只执行一次
rospy.spin()
然后创建回调函数,传入一个变量event
执行结果每隔1s发布info
更改回调函数为
def doMsg(event):
rospy.loginfo("+++++++++++")
rospy.loginfo("当前时刻:%s",str(event.current_real))
这就是里面变量的作用
可以调用回调函数的时刻
执行结果如下
def is_shutdown():
"""
@return: True 如果节点已经被关闭
@rtype: bool
"""
def signal_shutdown(reason):
"""
关闭节点
@param reason: 节点关闭的原因,是一个字符串
@type reason: str
"""
保存执行
被关闭
def on_shutdown(h):
"""
节点被关闭时调用的函数
@param h: 关闭时调用的回调函数,此函数无参
@type h: fn()
"""
on_shutdown会在结束时调用回调函数
然后再写一个回调函数
保存执行
rospy.logdebug("hello,debug") #不会输出
rospy.loginfo("hello,info") #默认白色字体
rospy.logwarn("hello,warn") #默认黄色字体
rospy.logerr("hello,error") #默认红色字体
rospy.logfatal("hello,fatal") #默认红色字体
编译并且执行