前言
本人ROS小白,利用寒假时间学习ROS,在此以笔记的方式记录自己每天的学习过程。争取写满15篇(5/15)。
环境:Ubuntu20.04、ROS1:noetic
环境配置:严格按照下方学习链接的教程配置,基本一次成功。
学习链接:【Autolabor初级教程】ROS机器人入门
对应链接文档:ROS机器人入门课程《ROS理论与实践》
笔记绝大部分代码使用Python语言编写。
本期关键词:初始化,话题服务,时间,Python模块导入
常用API
初始化API
- 初始化API我们在编写节点时都会用到:
rospy.init_node("turtle_cir")
大多情况下只使用了“name"这个参数,接下来介绍另外两个常用的参数,先看看init_node
的定义:
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):
具体的定义内容很长,这里就不列举了。
2. 常用的大概就是前三个:name, argv=None, anonymous=False
。
3. name
就是节点名称,节点名称中不能包含’/'字符。
4. argv
使用时不用在代码里单独列写,只需要在执行rosrun
指令时添加参数即可:(注意添加参数的格式,_
和:=
,执行指令后可通过rosparam list
和rosparam get 参数名
来查看)
rosrun helloworld demo_pub.py _A:=100
anonymous
使用时需要令anonymous=True
,此时程序会为节点名称后缀随机编号,能保证节点名称的唯一性,在多个终端同时执行同一指令时不会报错。
rospy.init_node("talker_pub",anonymous=True)
# 在两个终端同时运行该py文件,再通过rosnode list查看,结果如下:
/rosout
/talker_pub_219549_1706194705863
/talker_pub_219598_1706194710878
话题服务API
rospy.Publisher
的latch
参数:默认是False
,设置成True
后,发布方发送的最后一条消息会被保存,当新的订阅方订阅该话题时,将最后一条消息发送给订阅方。可以用在发送雷达地图数据上。建好图后,地图数据都是一样的,循环发送没有意义。可以令latch=True
,如果有新的订阅者则发送。latch
是我目前遇到比较常见的参数,其他函数可以在VScode中“转到定义”查看。
回旋函数API
Python中只有rospy.spin()
,作用是循环处理回调函数
时间API
1.获取时刻
rospy.Time.now()
是获取当前时间rospy.Time(时间)
是设置时间,基准是1970年1月1日 00:00:00
#! usr/bin/env python
import rospy
if __name__=="__main__":
rospy.init_node("time_test")
right_now=rospy.Time.now()
rospy.loginfo("Righe now time: %.2f",right_now.to_sec())# s
rospy.loginfo("Righe now time: %.2f",right_now.to_nsec())# ns
some_time1 = rospy.Time(1234.567891011)
some_time2 = rospy.Time(1234,567)# the former is s, the latter is ns
rospy.loginfo("设置时刻1:%.2f",some_time1.to_sec())
rospy.loginfo("设置时刻2:%.2f",some_time2.to_sec())
2.持续时间
rospy.Duration()
rospy.sleep()
#! usr/bin/env python
import rospy
if __name__=="__main__":
rospy.init_node("time_test")
# duration
# 持续时间相关API
rospy.loginfo("持续时间测试开始.....")
du = rospy.Duration(2)
rospy.sleep(du) #休眠函数
rospy.loginfo("持续时间测试结束.....")
3.时间运算
持续时间可以相加减,时刻不可以相加减
#! usr/bin/env python
import rospy
if __name__=="__main__":
rospy.init_node("time_test")
# duration add
rospy.loginfo("持续时间测试开始.....")
du1 = rospy.Duration(1)
du2 = rospy.Duration(2)
du3=du1+du2
rospy.sleep(du3)
rospy.loginfo("持续时间测试结束.....")
如果想尝试一下时刻相加的话,错误代码奉上:
#! usr/bin/env python
import rospy
if __name__=="__main__":
rospy.init_node("time_test")
right_now=rospy.Time.now()
right_now=right_now+right_now
rospy.loginfo("double right now:%.2f",right_now.to_nsec())
报错如下:
TypeError: unsupported operand type(s) for +: 'Time' and 'Time'
4.定时器
测试代码如下:
#! usr/bin/env python
import rospy
def doTime(event):
rospy.loginfo("+++++++++++++++++++++++")
rospy.loginfo("right now time: %s",str(event.current_real))
if __name__=="__main__":
rospy.init_node("time_test")
rospy.Timer(rospy.Duration(1),doTime)
rospy.spin()
其中rospy.Duration(1)
表示的是回调函数doTime
执行的时间间隔(按周期执行),可以类比一下STM32中定时器的捕获中断。
5.节点状态
测试代码如下:
#! /usr/bin/env python
import rospy
from std_msgs.msg import String
def doShut():
rospy.loginfo("shutdown......")
if __name__ == "__main__":
rospy.init_node("talker_pub",anonymous=True)
pub = rospy.Publisher("chat",String,queue_size=10,latch=True)
msg=String()
cont="hello! How are you? "
count=0
rate=rospy.Rate(1)
# rospy.sleep(3.0)
while not rospy.is_shutdown():
if count<=5:
msg.data=cont+str(count)
pub.publish(msg)
rate.sleep()
rospy.loginfo("output data is: %s",msg.data)
count+=1
else:
rospy.on_shutdown(doShut)
rospy.signal_shutdown("shutdown")
rospy.is_shutdown()
是我们很常用的,如果节点关闭,则会返回True
。rospy.on_shutdown()
的输入是一个回调函数,当节点被关闭时会调用这个回调函数。rospy.signal_shutdown()
的作用是关闭节点,输入参数是节点关闭的原因,这个原因不会输出在终端。- 小tips:
rospy.on_shutdown()
和rospy.signal_shutdown()
最好搭配使用,如上代码所示,不然只使用rospy.on_shutdown()
的话,我们在终端Ctrl+C终止进程时,终端会一直执行(输出)回调函数的内容,无法正常关闭。
日志函数
# 参考赵虚左老师的文档:
rospy.logdebug("hello,debug") #不会输出
rospy.loginfo("hello,info") #默认白色字体
rospy.logwarn("hello,warn") #默认黄色字体
rospy.logerr("hello,error") #默认红色字体
rospy.logfatal("hello,fatal") #默认红色字体
模块导入
- 模块导入时,路径是一个特别需要注意的点,我们先看以下代码:
#! /usr/bin/env python
num = 1000
# 这个是demo_tools.py的内容,是即将被导入的模块
#! /usr/bin/env python
import rospy
import demo_tools # 导入自定义模块
if __name__ == "__main__":
rospy.init_node("hello_num")
rospy.loginfo("ready......")
rospy.loginfo("output:%d",demo_tools.num)
- 在工作空间下用
rosrun
运行,输出报错:
AttributeError: module 'demo_tools' has no attribute 'num'
大致意思就是说模块’demo_tools’没有属性’num’(也有可能是ModuleNotFoundError
的报错)。这个报错的主要原因是rosrun
执行时,不能找到demo_tools
模块所在路径,因为rosrun
运行时的默认查找路径是不包含工作空间下的scripts
的。
3. 在XXXX_ws/src/helloworld(功能包)/scripts
目录下执行:
python3 hello.py(文件名)
会发现并没有报错,num
的值也能正确输出。据此也可以得出造成rosrun
报错的原因是路径。
4. 解决方法:
#! /usr/bin/env python
import rospy
import os
import sys
# 先动态获取工作空间路径(因为rosrun是在工作空间目录下执行的)
path=os.path.abspath(".")
# 将获取到的路径与固定路径(一般来说是固定的)相加,得到自定义模块所在位置
sys.path.insert(0,path+"/src/helloworld/scripts")
import demo_tools
if __name__ == "__main__":
rospy.init_node("hello_num")
rospy.loginfo("ready......")
rospy.loginfo("output:%d",demo_tools.num)