snaking_room_explore.py
Go to the documentation of this file.
00001 #!/usr/bin/python
00002 
00003 import roslib
00004 roslib.load_manifest('rfid_explore_room')
00005 roslib.load_manifest('explore_hrl')
00006 import rospy
00007 
00008 import tf
00009 import tf.transformations as tft
00010 import actionlib
00011 import rfid_explore_room.util as ut
00012 from actionlib.simple_action_client import SimpleGoalState
00013 
00014 from move_base_msgs.msg import MoveBaseGoal, MoveBaseAction
00015 from geometry_msgs.msg import PoseStamped, Quaternion
00016 from nav_msgs.srv import GetPlan, GetPlanRequest
00017 from rfid_explore_room.srv import ExploreRoomSrv, ExploreRoomSrvResponse
00018 from explore_hrl.msg import ExploreAction, ExploreResult, ExploreGoal
00019 
00020 import yaml
00021 import numpy as np, math
00022 import string
00023 import os
00024 
00025 
00026 def retfalse():
00027     return False
00028 
00029 
00030 class SnakingExplore( ):
00031     def __init__( self, df, room, start_rad = 1.5, markers_update = lambda x: True ):
00032         try:
00033             rospy.init_node( 'SnakingExplore' )
00034         except:
00035             pass
00036 
00037         self.frame = '/' + room
00038 
00039         self.markers_update = markers_update
00040         
00041         self.length = df[room]['length']
00042         self.width = df[room]['width']
00043 
00044         rospy.logout( 'snaking_explore: Initializing' )
00045 
00046         self.listener = tf.TransformListener()
00047         self.listener.waitForTransform( '/base_link', '/map', rospy.Time(0), timeout = rospy.Duration(100))
00048         self.listener.waitForTransform( self.frame, '/map', rospy.Time(0), timeout = rospy.Duration(100))
00049 
00050         self.setup_poses( radius = start_rad )
00051 
00052         self.client = actionlib.SimpleActionClient( 'move_base', MoveBaseAction )
00053         self.client.wait_for_server()
00054 
00055         self._service = rospy.Service( '/explore/explore' , ExploreRoomSrv, self.service_request )
00056 
00057         self._as = actionlib.SimpleActionServer( '/explore', ExploreAction, execute_cb = self.action_request )
00058         self._as.start()
00059 
00060         rospy.logout( 'snaking_explore: Ready and waiting.' )
00061 
00062     def service_request( self, req ):
00063         res = self.begin_explore( req.radius )
00064         rospy.logout( 'snaking_explore: Returning result \'%s\'' % res )
00065         return res        
00066 
00067     def action_request( self, goal ):
00068         rospy.logout( 'snaking_explore: action_request received for radius: \'%2.2f\'' % goal.radius )
00069         
00070         def preempt_func():
00071             # self._as should be in scope and bound @ function def. (I think for python...)
00072             check = self._as.is_preempt_requested()
00073             if check:
00074                 rospy.logout( 'snaking_explore: action_request preempted!' )
00075             return check
00076 
00077         rv = self.begin_explore( goal.radius, preempt_func = preempt_func )
00078         rospy.logout( 'snaking_explore: action_request received result %d' % int(rv) )
00079 
00080         if rv == 'succeeded':
00081             rospy.logout( 'snaking_explore: returning \'succeeded\'' )
00082             self._as.set_succeeded( ExploreResult() )
00083         elif rv == 'preempted':
00084             rospy.logout( 'snaking_explore: returning \'preempted\'' )
00085             self._as.set_preempted( )
00086         else: # rv == 'aborted'
00087             rospy.logout( 'snaking_explore: returning \'aborted\'' )
00088             self._as.set_aborted()
00089 
00090     def robot_in_map( self ):
00091         ps_base = PoseStamped()
00092         ps_base.header.frame_id = '/base_link'
00093         ps_base.header.stamp = rospy.Time(0)
00094         ps_base.pose.orientation.w = 1.0
00095         return self.ps_in_map( ps_base )
00096 
00097     def ps_in_map( self, ps ):
00098         # ps is pose_stamped
00099         try:
00100             ps_map = self.listener.transformPose( '/map', ps )
00101         except:
00102             rospy.logout( 'snaking_explore: %s -> map Transform failed.' % ps.header.frame_id )
00103             ps_map = None
00104         return ps_map
00105 
00106     def range_to_waypoint( self, r, w ):
00107         # r => robot in map, w => waypoint in map
00108         return np.sqrt( (r.pose.position.x - w.pose.position.x)**2.0 +
00109                         (r.pose.position.y - w.pose.position.y)**2.0 )
00110 
00111     def begin_explore( self, radius, preempt_func = retfalse ):
00112         rospy.logout( 'snaking_explore: setting radius' )
00113         
00114         waypoints = self.setup_poses( radius ) # PoseStamped in self.frame
00115 
00116         local_planner = rospy.get_param('/move_base_node/base_local_planner') # : dwa_local_planner/DWAPlannerROS
00117         local_planner = local_planner[string.find(local_planner,'/')+1:]
00118         obs_range = rospy.get_param('/move_base_node/global_costmap/obstacle_range')
00119         move_tol = rospy.get_param('/move_base_node/'+local_planner+'/xy_goal_tolerance')
00120         no_progress = rospy.get_param( rospy.get_name() + '/no_progress_move', 5)
00121         unreached_time = rospy.get_param( rospy.get_name() + '/not_reached_move', 30)
00122 
00123         # I'm not entirely sure which service to use.  I do know that
00124         # non-NavfnROS make_plan sometimes fails for an unknown
00125         # reason... and thus NavfnROS/make_plan is more robust.
00126 
00127         # srv_name = '/move_base_node/make_plan'
00128         srv_name = '/move_base_node/NavfnROS/make_plan'
00129         get_plan = rospy.ServiceProxy( srv_name, GetPlan )
00130 
00131         # Clear costmap...?  Do this here or in smach...?
00132         
00133         if preempt_func(): # immediately exit if overall action preempted
00134             return 'preempted'
00135 
00136         for i,w in enumerate( waypoints ):
00137             if preempt_func(): # immediately exit if overall action preempted
00138                 return 'preempted'
00139 
00140             rospy.logout( 'snaking_explore: Seeking waypoint %d of %d' % (i+1,len(waypoints)))
00141             # rospy.logout( 'snaking_explore: %2.2f %2.2f in frame %s' % (w.pose.position.x, w.pose.position.y, w.header.frame_id))
00142 
00143             rim = self.robot_in_map()
00144             w.header.stamp = rospy.Time(0) # transform it _now_
00145             wim = self.ps_in_map( w )      # waypoint in map
00146             
00147             if not rim or not wim: # if transforms failed
00148                 rospy.logout( 'snaking_explore: Bad transforms. Aborting explore' )
00149                 return 'aborted'
00150 
00151             if self.range_to_waypoint( rim, wim ) < 0.9 * obs_range:
00152                 # We're nearby the waypoint, so we'll just trust that the planner
00153                 # has a good view of its surroundings to determine reachability.
00154 
00155                 req = GetPlanRequest()
00156                 req.tolerance = 0.1
00157                 req.start = rim
00158                 req.goal = wim
00159                 resp = get_plan( req )
00160                 found_plan = bool( resp.plan.poses != [] )
00161 
00162                 if not found_plan:
00163                     rospy.logout( 'snaking_explore: No plan to nearby waypoint. Proceeding to next waypoint' )
00164                     # Perhaps its worth pulling the waypoint closer until a path _is_ found?
00165                     continue
00166 
00167                 # We now know that a path exists.  Send the waypoint to the client.
00168                 rospy.logout( 'snaking_explore: Near by with good plan.  Calling move_base action.' )
00169 
00170             else: 
00171                 # Any nav plan at beyond obstacle range will require the
00172                 # nav stack to get a better vantage.  Send the waypoint to
00173                 # the client.
00174                 rospy.logout( 'snaking_explore: Far away.  Calling move_base action.' )
00175 
00176 
00177             # If we made it this far, it's time to call move_base action.
00178             # Cancel any previous goals
00179             self.client.cancel_goals_at_and_before_time( rospy.Time.now() )
00180             if os.environ.has_key('ROBOT') and os.environ['ROBOT'] == 'sim':
00181                 rospy.logout( 'running in sim: give the system a little time to respond' )
00182                 rospy.sleep( 1 )
00183 
00184             # Send our new goal
00185             rospy.logout( 'snaking_explore: sending movebase action goal.' )
00186             move_goal = MoveBaseGoal()
00187             move_goal.target_pose = wim
00188             self.client.send_goal( move_goal )
00189             if os.environ.has_key('ROBOT') and os.environ['ROBOT'] == 'sim':
00190                 rospy.logout( 'running in sim: give the system a little time to respond' )
00191                 rospy.sleep( 1 )
00192 
00193             # We'll monitor the state ourselves, and allow ourselves
00194             # the opportunity to cancel the goal for various reasons
00195             # (eg. if we haven't moved for a while or if new plans
00196             # aren't found after getting within obstacle distance)
00197 
00198             # When this loop is broken, we'll go to the next goal.  We
00199             # don't need to cancel the goals here -- they will be
00200             # cancelled before a new one is sent.
00201             r = rospy.Rate( 10 )
00202             xytt_old = None # Checks for movement
00203             stime = rospy.Time.now().to_sec()
00204             while True:
00205                 if self.client.simple_state == SimpleGoalState.DONE:
00206                     res = self.client.get_result()
00207                     rospy.logout('snaking_explore: Movebase actionlib completed with result.  Proceeding to next waypoint')
00208                     break
00209 
00210                 rim = self.robot_in_map()
00211                 w.header.stamp = rospy.Time( 0 )
00212                 wim = self.ps_in_map( w )
00213 
00214                 # Proceed when close enough to goal (within two tolerance factors)
00215                 if rim and wim: # only do this check if transform succeeds
00216                     if self.range_to_waypoint( rim, wim ) < move_tol * 2.0:
00217                         rospy.logout( 'snaking_explore: Close enough to waypoint.  Proceeding to next waypoint' )
00218                         break # We're near the waypoint, so let's must move on to next
00219 
00220                 # Proceed when there has been no movement (x,y, or theta) for X sec.
00221                 if rim: # only do this check if transform succeeds
00222                     xytt = self.xythetatime( rim )
00223                     if not xytt_old:
00224                         xytt_old = xytt
00225 
00226                     if np.abs( xytt[0] - xytt_old[0] ) > 0.05 or \
00227                        np.abs( xytt[1] - xytt_old[1] ) > 0.05 or \
00228                        np.abs( xytt[2] - xytt_old[2] ) > math.radians(10):
00229                         xytt_old = xytt
00230 
00231                     if xytt[3] - xytt_old[3] > no_progress:
00232                         rospy.logout( 'snaking_explore: No movement in %2.1f seconds.  Proceeding to next waypoint' % no_progress )
00233                         break
00234 
00235                 if rospy.Time.now().to_sec() - stime > unreached_time:
00236                         rospy.logout( 'snaking_explore: Goal unreached in %2.1f seconds.  Proceeding to next waypoint' % unreached_time )
00237                         break
00238                 
00239                 r.sleep()
00240                 
00241         self.client.cancel_all_goals()
00242         rospy.logout( 'snaking_explore: Finished with exploration.' )
00243 
00244         return 'succeeded'
00245 
00246     def xythetatime( self, ps ):
00247         o = ps.pose.orientation
00248         r,p,y = tft.euler_from_quaternion(( o.x, o.y, o.z, o.w ))
00249         return ps.pose.position.x, ps.pose.position.y, y, rospy.Time.now().to_sec()
00250                         
00251     def setup_poses( self, radius ):
00252         # poses = self.setup_poses_xonly( radius )
00253         poses = self.setup_poses_snaking( radius )
00254         self.markers_update( poses )
00255         return poses
00256 
00257     def setup_poses_snaking( self, radius ):
00258         # tt are the min's and maxes of the snake along x-axis
00259         cycles = np.ceil( self.length / radius )
00260         tt = np.linspace( 0.0, self.length, 2 * cycles + 1 )
00261 
00262         # Determine waypoints at crest and valleys (lengthwise)
00263         crests = []
00264         atmin = False
00265         for xx in tt:
00266             atmin = not atmin # alternate between ys = 0 and ys = width
00267             yy = float( atmin ) * self.width
00268             crests.append( (xx, yy) )
00269             
00270         # Determine intermediate waypoints (widthwise)
00271         xyinter = []
00272         inter = np.ceil( self.width / radius )
00273         x_next, y_next = 0.0, 0.0
00274         for i in xrange( len(crests) - 1 ):
00275             x_prev = crests[i][0]
00276             y_prev = crests[i][1]
00277             x_next = crests[i+1][0]
00278             y_next = crests[i+1][1]
00279             
00280             x_inter = np.linspace( x_prev, x_next, inter * 2, endpoint = False )
00281             y_inter = np.linspace( y_prev, y_next, inter * 2, endpoint = False )
00282 
00283             xyinter += zip( x_inter, y_inter )
00284         xyinter += [ (x_next, y_next) ]
00285 
00286         # Determine headings (in self.frame)
00287         xytheta = []
00288         def add_xytheta( x, y, t ):
00289             ps = PoseStamped()
00290             ps.header.frame_id = self.frame
00291             ps.header.stamp = rospy.Time(0)
00292 
00293             ps.pose.position.x = x
00294             ps.pose.position.y = y
00295             ps.pose.orientation = Quaternion( *tft.quaternion_from_euler( 0.0, 0.0, t ))
00296 
00297             xytheta.append( ps )
00298 
00299         theta = 0.0
00300         for i in xrange( len(xyinter) - 1 ):
00301             theta = np.arctan2( xyinter[i+1][1] - xyinter[i][1],
00302                                 xyinter[i+1][0] - xyinter[i][0] )
00303             add_xytheta( xyinter[i][0], xyinter[i][1], theta )
00304         add_xytheta( xyinter[-1][0], xyinter[-1][1], theta )
00305 
00306         return xytheta            
00307 
00308     def setup_poses_xonly( self, radius ):
00309         poses = []
00310         xpos = 0.0
00311         
00312         def ps_x( x ):
00313             ps = PoseStamped()
00314             ps.header.frame_id = self.frame
00315             ps.pose.position.x = x
00316             ps.pose.orientation.w = 1.0
00317             return ps
00318             
00319         for i in xrange(3):
00320             poses.append( ps_x( xpos ))
00321             xpos += 0.5
00322         xpos += 3.0
00323         for i in xrange(3):
00324             poses.append( ps_x( xpos ))
00325             xpos += 0.5
00326 
00327         return poses
00328 
00329 
00330 
00331 if __name__ == '__main__':
00332     import optparse
00333     p = optparse.OptionParser()
00334     p.add_option('--yaml', action='store', type='string', dest='yaml',
00335                  help='Room definition file (required)', default='')
00336     p.add_option('--room', action='store', type='string', dest='room',
00337                  help='Specific room [default \'hrl\']', default='hrl')
00338     opt, args = p.parse_args()
00339 
00340     if opt.yaml == '':
00341         print 'room_explore: YAML FILE REQUIRED.'
00342     else:
00343         print 'Using YAML file: ', opt.yaml
00344     
00345     f = open( opt.yaml )
00346     dat = yaml.load( f )
00347     f.close()
00348     
00349     room_transforms = ut.TFthread( dat )
00350     pm = ut.PosesMarkers()
00351     
00352     re = SnakingExplore( df = dat,
00353                          room = opt.room,
00354                          start_rad = 1.5,
00355                          markers_update = pm.update_poses )
00356     # rospy.sleep( 4 )
00357     # re.publish_markers()
00358     
00359     rospy.spin()
00360     
00361     # r = rospy.Rate( 2 )
00362     # while not rospy.is_shutdown():
00363     #     re.publish_markers()
00364     #     r.sleep()
00365 
00366     #re.begin_explore( 0.8 )
00367     # rosservice call /explore 0.8
00368     
00369     # explore_act = actionlib.SimpleActionClient('explore', ExploreAction)
00370     # rospy.logout( 'Waiting for explore.' )
00371     # explore_act.wait_for_server()
00372     # rospy.logout( 'Done waiting.' )
00373 
00374     # goal = ExploreGoal( radius = 0.8 )
00375     # explore_act.send_goal(goal)
00376     # rospy.sleep( 0.5 )
00377     # explore_act.wait_for_result()
00378         
00379     
00380         


rfid_explore_room
Author(s): Travis Deyle
autogenerated on Wed Nov 27 2013 12:04:36