follow_waypoints.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 import threading
4 import rospy
5 import actionlib
6 from smach import State,StateMachine
7 from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal
8 from geometry_msgs.msg import PoseWithCovarianceStamped, PoseArray ,PointStamped
9 from std_msgs.msg import Empty
10 from tf import TransformListener
11 import tf
12 import math
13 import rospkg
14 import csv
15 import time
16 from geometry_msgs.msg import PoseStamped
17 
18 # change Pose to the correct frame
19 def changePose(waypoint,target_frame):
20  if waypoint.header.frame_id == target_frame:
21  # already in correct frame
22  return waypoint
23  if not hasattr(changePose, 'listener'):
24  changePose.listener = tf.TransformListener()
25  tmp = PoseStamped()
26  tmp.header.frame_id = waypoint.header.frame_id
27  tmp.pose = waypoint.pose.pose
28  try:
29  changePose.listener.waitForTransform(
30  target_frame, tmp.header.frame_id, rospy.Time(0), rospy.Duration(3.0))
31  pose = changePose.listener.transformPose(target_frame, tmp)
32  ret = PoseWithCovarianceStamped()
33  ret.header.frame_id = target_frame
34  ret.pose.pose = pose.pose
35  return ret
36  except:
37  rospy.loginfo("CAN'T TRANSFORM POSE TO {} FRAME".format(target_frame))
38  exit()
39 
40 
41 #Path for saving and retreiving the pose.csv file
42 output_file_path = rospkg.RosPack().get_path('follow_waypoints')+"/saved_path/pose.csv"
43 waypoints = []
44 
45 class FollowPath(State):
46  def __init__(self):
47  State.__init__(self, outcomes=['success'], input_keys=['waypoints'])
48  self.frame_id = rospy.get_param('~goal_frame_id','map')
49  self.odom_frame_id = rospy.get_param('~odom_frame_id','odom')
50  self.base_frame_id = rospy.get_param('~base_frame_id','base_footprint')
51  self.duration = rospy.get_param('~wait_duration', 0.0)
52  # Get a move_base action client
53  self.client = actionlib.SimpleActionClient('move_base', MoveBaseAction)
54  rospy.loginfo('Connecting to move_base...')
55  self.client.wait_for_server()
56  rospy.loginfo('Connected to move_base.')
57  rospy.loginfo('Starting a tf listner.')
58  self.tf = TransformListener()
59  self.listener = tf.TransformListener()
60  self.distance_tolerance = rospy.get_param('waypoint_distance_tolerance', 0.0)
61 
62  def execute(self, userdata):
63  global waypoints
64  # Execute waypoints each in sequence
65  for waypoint in waypoints:
66  # Break if preempted
67  if waypoints == []:
68  rospy.loginfo('The waypoint queue has been reset.')
69  break
70  # Otherwise publish next waypoint as goal
71  goal = MoveBaseGoal()
72  goal.target_pose.header.frame_id = self.frame_id
73  goal.target_pose.pose.position = waypoint.pose.pose.position
74  goal.target_pose.pose.orientation = waypoint.pose.pose.orientation
75  rospy.loginfo('Executing move_base goal to position (x,y): %s, %s' %
76  (waypoint.pose.pose.position.x, waypoint.pose.pose.position.y))
77  rospy.loginfo("To cancel the goal: 'rostopic pub -1 /move_base/cancel actionlib_msgs/GoalID -- {}'")
78  self.client.send_goal(goal)
79  if not self.distance_tolerance > 0.0:
80  self.client.wait_for_result()
81  rospy.loginfo("Waiting for %f sec..." % self.duration)
82  time.sleep(self.duration)
83  else:
84  #This is the loop which exist when the robot is near a certain GOAL point.
85  distance = 10
86  while(distance > self.distance_tolerance):
87  now = rospy.Time.now()
88  self.listener.waitForTransform(self.odom_frame_id, self.base_frame_id, now, rospy.Duration(4.0))
89  trans,rot = self.listener.lookupTransform(self.odom_frame_id,self.base_frame_id, now)
90  distance = math.sqrt(pow(waypoint.pose.pose.position.x-trans[0],2)+pow(waypoint.pose.pose.position.y-trans[1],2))
91  return 'success'
92 
94  """Used to publish waypoints as pose array so that you can see them in rviz, etc."""
95  poses = PoseArray()
96  poses.header.frame_id = rospy.get_param('~goal_frame_id','map')
97  poses.poses = [pose.pose.pose for pose in waypoints]
98  return poses
99 
100 class GetPath(State):
101  def __init__(self):
102  State.__init__(self, outcomes=['success'], input_keys=['waypoints'], output_keys=['waypoints'])
103  # Subscribe to pose message to get new waypoints
104  self.addpose_topic = rospy.get_param('~addpose_topic','/initialpose')
105  # Create publsher to publish waypoints as pose array so that you can see them in rviz, etc.
106  self.posearray_topic = rospy.get_param('~posearray_topic','/waypoints')
107  self.poseArray_publisher = rospy.Publisher(self.posearray_topic, PoseArray, queue_size=1)
108 
109  # Start thread to listen for reset messages to clear the waypoint queue
110  def wait_for_path_reset():
111  """thread worker function"""
112  global waypoints
113  while not rospy.is_shutdown():
114  data = rospy.wait_for_message('/path_reset', Empty)
115  rospy.loginfo('Recieved path RESET message')
116  self.initialize_path_queue()
117  rospy.sleep(3) # Wait 3 seconds because `rostopic echo` latches
118  # for three seconds and wait_for_message() in a
119  # loop will see it again.
120  reset_thread = threading.Thread(target=wait_for_path_reset)
121  reset_thread.start()
122 
124  global waypoints
125  waypoints = [] # the waypoint queue
126  # publish empty waypoint queue as pose array so that you can see them the change in rviz, etc.
127  self.poseArray_publisher.publish(convert_PoseWithCovArray_to_PoseArray(waypoints))
128 
129  def execute(self, userdata):
130  global waypoints
131  self.initialize_path_queue()
132  self.path_ready = False
133 
134  # Start thread to listen for when the path is ready (this function will end then)
135  # Also will save the clicked path to pose.csv file
136  def wait_for_path_ready():
137  """thread worker function"""
138  data = rospy.wait_for_message('/path_ready', Empty)
139  rospy.loginfo('Recieved path READY message')
140  self.path_ready = True
141  with open(output_file_path, 'w') as file:
142  for current_pose in waypoints:
143  file.write(str(current_pose.pose.pose.position.x) + ',' + str(current_pose.pose.pose.position.y) + ',' + str(current_pose.pose.pose.position.z) + ',' + str(current_pose.pose.pose.orientation.x) + ',' + str(current_pose.pose.pose.orientation.y) + ',' + str(current_pose.pose.pose.orientation.z) + ',' + str(current_pose.pose.pose.orientation.w)+ '\n')
144  rospy.loginfo('poses written to '+ output_file_path)
145  ready_thread = threading.Thread(target=wait_for_path_ready)
146  ready_thread.start()
147 
148  self.start_journey_bool = False
149 
150  # Start thread to listen start_jorney
151  # for loading the saved poses from follow_waypoints/saved_path/poses.csv
152  def wait_for_start_journey():
153  """thread worker function"""
154  data_from_start_journey = rospy.wait_for_message('start_journey', Empty)
155  rospy.loginfo('Recieved path READY start_journey')
156  with open(output_file_path, 'r') as file:
157  reader = csv.reader(file, delimiter = ',')
158  for row in reader:
159  print row
160  current_pose = PoseWithCovarianceStamped()
161  current_pose.pose.pose.position.x = float(row[0])
162  current_pose.pose.pose.position.y = float(row[1])
163  current_pose.pose.pose.position.z = float(row[2])
164  current_pose.pose.pose.orientation.x = float(row[3])
165  current_pose.pose.pose.orientation.y = float(row[4])
166  current_pose.pose.pose.orientation.z = float(row[5])
167  current_pose.pose.pose.orientation.w = float(row[6])
168  waypoints.append(current_pose)
169  self.poseArray_publisher.publish(convert_PoseWithCovArray_to_PoseArray(waypoints))
170  self.start_journey_bool = True
171 
172 
173  start_journey_thread = threading.Thread(target=wait_for_start_journey)
174  start_journey_thread.start()
175 
176  topic = self.addpose_topic;
177  rospy.loginfo("Waiting to recieve waypoints via Pose msg on topic %s" % topic)
178  rospy.loginfo("To start following waypoints: 'rostopic pub /path_ready std_msgs/Empty -1'")
179  rospy.loginfo("OR")
180  rospy.loginfo("To start following saved waypoints: 'rostopic pub /start_journey std_msgs/Empty -1'")
181 
182 
183  # Wait for published waypoints or saved path loaded
184  while (not self.path_ready and not self.start_journey_bool):
185  try:
186  pose = rospy.wait_for_message(topic, PoseWithCovarianceStamped, timeout=1)
187  except rospy.ROSException as e:
188  if 'timeout exceeded' in e.message:
189  continue # no new waypoint within timeout, looping...
190  else:
191  raise e
192  rospy.loginfo("Recieved new waypoint")
193  waypoints.append(changePose(pose, "map"))
194  # publish waypoint queue as pose array so that you can see them in rviz, etc.
195  self.poseArray_publisher.publish(convert_PoseWithCovArray_to_PoseArray(waypoints))
196 
197  # Path is ready! return success and move on to the next state (FOLLOW_PATH)
198  return 'success'
199 
200 class PathComplete(State):
201  def __init__(self):
202  State.__init__(self, outcomes=['success'])
203 
204  def execute(self, userdata):
205  rospy.loginfo('###############################')
206  rospy.loginfo('##### REACHED FINISH GATE #####')
207  rospy.loginfo('###############################')
208  return 'success'
209 
210 def main():
211  rospy.init_node('follow_waypoints')
212 
213  sm = StateMachine(outcomes=['success'])
214 
215  with sm:
216  StateMachine.add('GET_PATH', GetPath(),
217  transitions={'success':'FOLLOW_PATH'},
218  remapping={'waypoints':'waypoints'})
219  StateMachine.add('FOLLOW_PATH', FollowPath(),
220  transitions={'success':'PATH_COMPLETE'},
221  remapping={'waypoints':'waypoints'})
222  StateMachine.add('PATH_COMPLETE', PathComplete(),
223  transitions={'success':'GET_PATH'})
224 
225  outcome = sm.execute()
def convert_PoseWithCovArray_to_PoseArray(waypoints)
def changePose(waypoint, target_frame)


follow_waypoints
Author(s): Daniel Snider
autogenerated on Mon Jan 18 2021 03:30:46