fake_localization包提供了一个单一ROS node,fake_localization, 用来替代定位系统,它也提供了amcl定位算法ROS API的子集。该node在仿真中被频繁使用,是一种不需要大量计算资源就能进行定位的方式。具体来说,fake_localization是将odometry数据转换成位姿,粒子云以及amcl用的那种tf格式的tf数据。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import roslib;
#这个库和rospy差不多,都是提供API接口的
import rospy
import actionlib
#actionlib是ROS中一个很重要的功能包集合
#尽管在ROS中已经提供了srevice机制来满足请求—响应式的使用场景
#但是假如某个请求执行时间很长,在此期间用户想查看执行的进度或者取消这个请求的话,service机制就不能满足,但是actionlib可满足用户这种需求。
from actionlib_msgs.msg import *
from geometry_msgs.msg import Pose, PoseWithCovarianceStamped, Point, Quaternion, Twist
from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal
from random import sample
from math import pow, sqrt
class NavTest():
def __init__(self):
rospy.init_node('random_navigation', anonymous=True)
rospy.on_shutdown(self.shutdown)
# 在每个目标位置暂停的时间
self.rest_time = rospy.get_param("~rest_time", 2)
#这个函数的作用就是得到设置后面的参数的值
# 到达目标的状态
goal_states = ['PENDING', 'ACTIVE', 'PREEMPTED',
'SUCCEEDED', 'ABORTED', 'REJECTED',
'PREEMPTING', 'RECALLING', 'RECALLED',
'LOST']
# 设置目标点的位置
# 如果想要获得某一点的坐标,在rviz中点击 2D Nav Goal 按键,然后单机地图中一点(或者可以直接在rviz中用publish)
# 在终端中就会看到坐标信息
locations = dict()
#后面的四元数应该表示的到点之后的位姿
locations['p1'] = Pose(Point(5.61, -4.4, -0.00143), Quaternion(0.000, 0.000, -0.013, 1.000))
locations['p2'] = Pose(Point(-4.53, 5.04, -0.00143), Quaternion(0.000, 0.000, 0.063, 0.998))
locations['p3'] = Pose(Point(2.98, 0.975, -0.00143), Quaternion(0.000, 0.000, 0.946, -0.324))
locations['p4'] = Pose(Point(0.708, -5.75, -0.00143), Quaternion(0.000, 0.000, 0.946, -0.324))
locations['p5'] = Pose(Point(6.142, -3.75, -0.00143), Quaternion(0.000, 0.000, 0.946, -0.324))
# 发布控制机器人的消息
self.cmd_vel_pub = rospy.Publisher('cmd_vel', Twist, queue_size=5)
# 订阅move_base服务器的消息,其实和subscriber差不多
self.move_base = actionlib.SimpleActionClient("move_base", MoveBaseAction)
rospy.loginfo("Waiting for move_base action server...")
# 60s等待时间限制
self.move_base.wait_for_server(rospy.Duration(60))
rospy.loginfo("Connected to move base server")
# 保存机器人的在rviz中的初始位置
initial_pose = PoseWithCovarianceStamped()
# 保存成功率、运行时间、和距离的变量
n_locations = len(locations) #在这个点的条件下,为5,表示一共有5个位置
n_goals = 0
n_successes = 0 #成功导航的次数,后面成功一次就加1
i = n_locations #表示第i个位置
distance_traveled = 0 #设置穿过的路程,计算所有导航次数以来的总路程
start_time = rospy.Time.now() #设置开始运动的初试时间
running_time = 0 #设置每次开始到结束的时间差
location = "" #设置当前位置
last_location = "" #设置最后一个位置
# 确保有初始位置
while initial_pose.header.stamp == "":
rospy.sleep(1)
rospy.loginfo("Starting navigation test")
# 开始主循环,随机导航
while not rospy.is_shutdown():
# 如果已经走完了所有点,再重新开始排序,就是说如果现在是的位置i就是5
if i == n_locations:
i = 0 #这个时候就把i再变成0,重新开始
sequence = sample(locations, n_locations) #这个是将五个位置随机排成好一个次序
# 如果最后一个点(上一个点)和第一个点相同,则跳过,从后一个点开始
if sequence[0] == last_location:
i = 1
# 在当前的排序中获取下一个目标点
location = sequence[i]
# 跟踪行驶距离
# 使用更新的初始位置
if initial_pose.header.stamp == "":
#这里通过三角形来计算斜边的边长(路程距离)
distance = sqrt(pow(locations[location].position.x -
locations[last_location].position.x, 2) +
pow(locations[location].position.y -
locations[last_location].position.y, 2))
else:
rospy.loginfo("Updating current pose.")
distance = sqrt(pow(locations[location].position.x -
initial_pose.pose.pose.position.x, 2) +
pow(locations[location].position.y -
initial_pose.pose.pose.position.y, 2))
initial_pose.header.stamp = ""
# 存储上一次的位置,计算距离
last_location = location
# 计数器加1
i += 1
n_goals += 1
# 设定下一个目标点
self.goal = MoveBaseGoal()
self.goal.target_pose.pose = locations[location]
self.goal.target_pose.header.frame_id = 'map'
self.goal.target_pose.header.stamp = rospy.Time.now()
# 让用户知道下一个位置
rospy.loginfo("Going to: " + str(location))
# 向下一个位置进发
self.move_base.send_goal(self.goal)
# 五分钟时间限制 (就是说这个导航的过程有没有在5分钟内完成)
finished_within_time = self.move_base.wait_for_result(rospy.Duration(300)) #300秒
# 查看是否成功到达
if not finished_within_time: #如果5分钟内没有完成完成
self.move_base.cancel_goal() #那么就取消这个目标点
rospy.loginfo("Timed out achieving goal") #发布超时的指令
else:
state = self.move_base.get_state() #如果已经成功到达,状态就得到get_state(),这个是上面的状态的队列
if state == GoalStatus.SUCCEEDED: #如果状态为“SUCCCEEDED”,就执行下面一系列操作
rospy.loginfo("Goal succeeded!")
n_successes += 1
distance_traveled += distance
rospy.loginfo("State:" + str(state))
else:
rospy.loginfo("Goal failed with error code: " + str(goal_states[state])) #否则输出error
# 运行所用时间
running_time = rospy.Time.now() - start_time
running_time = running_time.secs / 60.0
# 输出本次导航的所有信息
rospy.loginfo("Success so far: " + str(n_successes) + "/" +
str(n_goals) + " = " +
str(100 * n_successes/n_goals) + "%")
rospy.loginfo("Running time: " + str(trunc(running_time, 1)) +
" min Distance: " + str(trunc(distance_traveled, 1)) + " m")
rospy.sleep(self.rest_time)
def update_initial_pose(self, initial_pose):
self.initial_pose = initial_pose
def shutdown(self):
rospy.loginfo("Stopping the robot...")
self.move_base.cancel_goal()
rospy.sleep(2)
self.cmd_vel_pub.publish(Twist())
rospy.sleep(1)
def trunc(f, n):
slen = len('%.*f' % (n, f))
return float(str(f)[:slen])
if __name__ == '__main__':
try:
NavTest()
rospy.spin()
except rospy.ROSInterruptException:
rospy.loginfo("Random navigation finished.")
Move_base节点提供SimpleActionServer(见action documentation)的一个实现,需要含有geometry_msgs/PoseStamped消息的目标。可以通过ROS直接与move_base节点通信,但如果考虑使用SimpleActionClient跟踪他们的状态,推荐发送目标到move_base的方法。详见actionlib documentation来获取更多信息。