Rospy Overview
ROS 官网链接 http://wiki.ros.org/rospy/Overview
第一部分 初始化和关闭
rospy.init_node(name, anonymous=True, log_level=rospy.INFO, disable_signals=False)
anonymous :确保节点的唯一性
log_level :将日志消息输出到 rosout
disable_signals :如果你不需要以ctrl+c结束,就需要设置这个值
在rospy中最常用的测试rospy是否关闭的方法
方法一:
while not rospy.is_shutdown():
do some work
方法二:
...setup callbacks
rospy.spin() # spin()部分代码会一直休眠,直到is_shutdown()为真
在程序结束的时候,可以运行一下某个函数 myhook
def myhook():
print "Shutdown time!"
rospy.on_shutdown(myhook)
第二部分 Messages
生成消息
msg 文件的格式是
package_name/msg/Foo.msg → package_name.msg.Foo
srv 文件的格式是
package_name/srv/Bar.srv → package_name.srv.Bar
可以用下面的声明语句来使用 std_msgs/String 文件
import std_msgs.msg
msg = std_msgs.msg.String()
或者
from std_msgs.msg import String
msg = String()
可以搭配rosmsg show 使用
初始化消息
有三种方式来初始化一个新的消息实例
- 无参数
- 顺序参数
- 关键参数
第三部分 发布者和订阅者
- 向一个topic发布
可以使用rospy.Publisher类创建一个句柄来发布消息。最常用的使用方法是提供话题的名字和话题消息的类型。接着你可以在句柄里调用publish()来发布一个消息,e.g:
pub = rospy.Publisher('topic_name', std_msgs.msg.String, queue_size=10)
pub.publish(std_msgs.msg.String("foo"))
1.1 rospy.Publisher 详解
rospy.Publisher(topic_name, msg_class, queue_size)
创建一个rospy.Publisher 只需要 话题名称,话题类型,和 队列大小 三个参数
备注:队列大小这个参数仅在Hydro或者更新的版本中可用
pub = rospy.Punlisher('topic_name', std_msgs.msg.String, queue_size=10)
还有更多的一些高级选项来配置发布者:
subscriber_listener=rospy.SubscribeListener
在新的订阅者连接和断开的时候,通过一个rospy.SubscribeListener实例获得回调信号
tcp_nodelay=False
以效率为代价的低延迟发布
latch=False
在连接时允许暂停。当一个连接暂停时,最后发布的一条消息将被保存并且发送给后续的任意订阅者。对于变化率比较慢或者静态的数据来说,这个很有效,就像 地图。这条消息不是复制,如果你在发布之后改变了消息,改变后的消息会被发送给以后的订阅者
headers=None(dict)
在头文件中为以后的连接添加额外的 键-值-对
queue_size=None(int)[New in Hydro]
这是为异步发布设置的流出消息队列大小
1.2 Publisher.publish()
通过改变一个详细的格式,有三种办法来调用publish(),下面是例子
明确的方式
明确的方式很简单:创建独立的消息实例,发布它:
pub.publish(std_msgs.msg.String("Hello world"))
顺序参数的隐式方式
在顺序类型,你在创建实例时必须按照顺序提供所有的参数。参数顺序和消息类型里的顺序一致。就像,std_msgs.msg.String仅有一个简单的string参数,所以你可以直接:
pub.publish("hello world")
std_msgs.msg.ColorRGBA 有四个参数(r, g, b, a),所有我们可以这样:
pub.publish(255.0, 255.0, 255.0, 128.0)
关键字方式的隐式方式
在关键字方式中,你可以只给出你想要给出的参数,剩下的参数都会是默认值。对于std_msgs.msg.String,他唯一字段的名称式data,所以你可以这样调用
pub.publish(data="hello world")
std_msgs.msg.ColorRGBA 有四个字段,我们可以只设置一个b
pub.publish(b=255)
1.3 队列尺寸:publish() 性能和排队
在rospy中,publish()默认是同步的,这意味着调用时堵塞的直到:
- 消息在缓冲区排列
- 并且缓冲区被每个实时的订阅者订阅
如果任意一个连接存在连通性的问题,都会可能在一个不确定的时间导致发布堵塞。在通过无线传输订阅节点时,这是一个通用问题。
和水利发电一样,建议使用新的异步打印方式,这种方式更加符合roscpp的行为。
为了使用新的行为,关键字参数 queue_size一定要传递给订阅者,他决定了在信息被丢弃前的队列最大尺寸。
publish()仍然按照顺序发布消息,一旦订阅者有连接问题后,将不再收到新的消息。
如果你发布消息的速度大于rospy传递消息的速度,rospy将舍弃旧的消息。
1.4 选择一个合适的队列尺寸
很难提供一个具体的规则来为你的程序选择最好的队列尺寸,因为它取决你的系统的很多变量。对于新手,我们提供了一些建议。
如果你仅仅以固定速率发布一个消息,把 队列尺寸设置成发布频率一样大小就很合适。
如果你一下子发送很多消息,你必须确保队列尺寸足够容纳这么多信息,否则他很有可能丢失一些信息。
总的来说,使用大的队列尺寸将使用更多的内存空间,所以建议选择一个比他实际值大一点的值。
但是过大的队列尺寸会导致大量的消息堆积,这样会造成消息传递的延迟。
- 省略队列尺寸
。。。 - 队列尺寸设置成None
不推荐。发布会实时处理,这意味着一旦一个消息堵塞了,所有的消息都会被堵塞 - 队列尺寸设置为0
这代表一个无限大的队列,这很危险,因为这样可以导致内存被占用 - 队列尺寸设置为1,2,3
如果你的系统没过载,你可以计算出一个队列尺寸应该在0.1秒内,所以当消息发布采用10Hz是,队列大小设置为1/2/3是很合适的。
如果你想确保收到每一个新的消息, 把队列尺寸设置为1是一个很有效的方法。就像传感器,我们只需要收到最新的数据 - 队列尺寸设置成10或者更多
举个例子,如果队列尺寸设置成10或者更多,优点就是会有一个大的队列尺寸以便防止漏掉变化的值。另一个例子就是当你想记录所有打印的值,包阔那些你在高速率/低队列尺寸时会放弃掉的值
1.5 完整的例子
#!/usr/bin/python
#-*- coding: UTF-8 -*-
import rospy
from std_msgs.msg import String
def Publisher():
pub = rospy.Publisher('chatter', String, queue_size=10)
rospy.init_node('node_name')
r = rospy.Rate(1) # 1hz
while not rospy.is_shutdown():
pub.publish("Hello World")
r.sleep()
if __name__=='__main__':
Publisher()
2.0 订阅一个话题
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import rospy
from std_msgs.msg import String
def callback(data):
rospy.loginfo(rospy.get_caller_id() + "I heard %s", data.data)
def listener():
rospy.init_node('listener')
rospy.Subscriber("chatter", String, callback)
rospy.spin()
if __name__ == '__main__':
listener()
2.1 连接信息
第四部分 服务
- 服务定义,请求信息,和回应信息
ROS服务是通过srv文件定义的,它包含了一个请求信息和应答信息。这些和rostopic使用的消息一样。rospy将这些srv文件转换成Python代码并且创造成三种你需要熟悉的类:服务定义, 请求信息, 回应信息。
这些类的名字是直接来源于 srv 文件名
my_package/srv/Foo.srv → my_package.srv.Foo
my_package/srv/Foo.srv → my_package.srv.FooRequest
my_package/srv/Foo.srv → my_package.srv.FooResponse
服务定义
服务定义是一个包含请求和回应类型的容器。在你创建或者调用服务时, 你必须使用它们。你需要导入服务定义并且把它传递给恰当的服务处初始化方法。
add_two_ints = rospy.ServiceProxy('service_name', my_package.srv.Foo)
rsopy.ServiceDefinition(只能高级用户用)
所有生成的服务定义的超类
服务请求信息
请求信息被用来调用恰当的服务。你通常不需要直接使用他们,rospy的服务调用机制允许你绕过直接使用他们,但总有一些情况你需要用到这些信息
服务应答信息
应答信息包含那些从服务返回的值。服务处理必须返回正确类型的响应实例。
- 服务代理
你可以调用一个服务通过创建一个以你想要命名的名字rospy.ServiceProxy。在服务可用之前,你通常需要调用 rospy.wait_for_service()来等待。
如果面对请求,服务返回了一个错误的值,会生成一个rospy.ServiceException。这个异常将包含所有服务发送的错误信息。
rospy.wait_for_service('add_two_ints')
add_two_ints = rospy.ServiceProxy('add_two_ints', AddTwoInts)
try:
resp1 = add_two_ints(x, y)
except rospy.ServiceException as exc:
print ("Service did not process request:" + str(exc))
rospy.ServiceProxy(name, service_class, persistent=False, headers=None)
为服务创建一个可调用的代理
rospy.wait_for_service(service, timeout=None)
在服务可用之前一直等待,你可以选择定义一个等待时间,超过了这个等待时间,就会生成一个ROSException
2.1 调用服务
rospy.ServiceProxy 实例是可调用的,这意味着你可以像调用方法一样调用他们
add_two_ints rospy.ServiceProxy('add_two_ints', AddTwoInts)
add_two_ints(1, 2)
同样有三种方式来传递参数。
有三种类型的中断会被抛出:
- TypeError
- ServiceException
- ROSSerializationException
2.2 持续连接
ROS在服务中也允许持续的连接。通过一个持续的连接,一个客户端保持连接到一个服务。否则,客户机每次都需要重新查找并连接到一个服务。这可能导致一个客户端在执行一个服务调用时连接到一个不同的节点,假设请求返回一个不同的节点。
持续链接应该被谨慎使用。他们对重复请求有很好的表现,但是他们也让你的客户端更容易导致服务错误。客户端使用连续的连接需要实现他们自己的重联逻辑以防止连接失效。
rospy.ServiceProxy(name, service_class, persistent=True)
# 创建一个代理来调用服务并且支持持续连接
close()
# 关闭一个持续服务连接
- 提供服务
在rospy中,创建一个rospy.Service实例提供一个服务,在收到新的请求时,调用回调函数。每个输入的请求都是它袭击的线程处理,所以服务必须是线程安全的。
rospy.Service(name, service_class, handler, buff_size=65536)
用名字,服务类型,处理者 创建一个新的ROS服务。处理者将会被服务请求信息调用并且给出回应信息。如果处理者没有返回数据或者产生了一个错误,这个错误将被传给客户端。
def add_two_ints(req):
return rospy_tutorials.srv.AddTwoIntsResponse(req.a + req.b)
def add_two_ints_server():
rospy.init_node('add_two_ints_server')
s = rospy.Service('add_two_ints', rospy_tutorials.srv.AddTwoInts, add_two_ints)
rospy.spin()
高级选项:
buff_size(int)
缓冲区尺寸是用来读取进入的请求
你还可以把服务处理的结果连续的返回出去,这样你不需要手动创建回应实例。下面是一个处理者有效的返回数据类型:
- None
- ServiceResponse
- tuple or list
- dict
- single-argument response only: value of field
一个处理者可以返回一个参数元组或者参数列表来创建回应实例。举个例子,我们可以用以下的方式替换上面 add_two_ints 功能:
def add_two_ints(req):
return [req.a + req.b]
AddTwoIntsResponse 仅仅有一个返回参数,所以我们通过仅返回 和 值来简化它
def add_two_ints(req):
return req.a + req.b
一个处理这也能返回一个字典来作为回应单元。为了继续 add_two_ints 例子,我们可以返回:
def add_two_ints(req):
return {'sum': req.a + req.b}
这将为设置回应的 sum 字段
3.1 (等待)关闭
有两个常用的参数来关闭一个服务。你可以直接通过调用服务的shutdown()来终止服务,或者你可以使用spin()方法,他会在你关闭前一直占用线程。有两种你可以使用的spin()方法:每个服务实例都有一个spin()方法,只要服务或者节点没有关闭,他就一直存在,还有更常用的rospy.spin()方法,它是一直在等待节点关闭。
直接关闭一个服务:
s = rospy.Service('add_two_ints', rospy_tutorials.srv.AddTwoInts, add_two_ints)
...
s.shutdown('shutdown reason')
Wait for shutdown:
s = rospy.Service('add_two_ints', rospy_tutorials.srv.AddTwoInts,add_two_ints)
s.spin() # 当服务或者节点关闭时返回
- 服务连接头文件
连接头文件是ROS Topic和ROS Services的特色,它保证了在两个节点初始连接时会发送元数据。ROS使用这些头文件来传递基础信息,比如连接客户端的内容。
在服务连接实例中,这一功能可以被定制从而实现一些新的功能。服务客户端可以发送一些他们自己的额外的信息,比如他们自己的连接说明。
在客户端,ServiceProxy传递了一个附加的headers参数,该参数带有要发送的字符串键和值的字典。在服务端,可以在请求消息的_connection_header字段中访问其他报头
rospy.ServiceProxy(name, service_class, headers=header_dictionary)
用额外的连接头文件为服务创建一个新的代理。
h = { 'cookies' : 'peanut butter'}
s = rospy.ServiceProxy{'foo', Foo, headers=h}
request._connection_header
字典包含了从客户段设置的字符类型的 键/值 对
def add_two_ints(req):
who = req._connection_header['callerid']
if 'cookies' in req._connection_header:
cookies = req._connection_header['cookies']
return AddTwoIntsResponse(req.a + req.b)
def add_two_ints_server():
rospy.init_node(NAME)
s = rospy.Service('add_two_ints', AddTwoInts, add_two_ints)