00001 
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             
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: 
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         
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         
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 ) 
00115 
00116         local_planner = rospy.get_param('/move_base_node/base_local_planner') 
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         
00124         
00125         
00126 
00127         
00128         srv_name = '/move_base_node/NavfnROS/make_plan'
00129         get_plan = rospy.ServiceProxy( srv_name, GetPlan )
00130 
00131         
00132         
00133         if preempt_func(): 
00134             return 'preempted'
00135 
00136         for i,w in enumerate( waypoints ):
00137             if preempt_func(): 
00138                 return 'preempted'
00139 
00140             rospy.logout( 'snaking_explore: Seeking waypoint %d of %d' % (i+1,len(waypoints)))
00141             
00142 
00143             rim = self.robot_in_map()
00144             w.header.stamp = rospy.Time(0) 
00145             wim = self.ps_in_map( w )      
00146             
00147             if not rim or not wim: 
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                 
00153                 
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                     
00165                     continue
00166 
00167                 
00168                 rospy.logout( 'snaking_explore: Near by with good plan.  Calling move_base action.' )
00169 
00170             else: 
00171                 
00172                 
00173                 
00174                 rospy.logout( 'snaking_explore: Far away.  Calling move_base action.' )
00175 
00176 
00177             
00178             
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             
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             
00194             
00195             
00196             
00197 
00198             
00199             
00200             
00201             r = rospy.Rate( 10 )
00202             xytt_old = None 
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                 
00215                 if rim and wim: 
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 
00219 
00220                 
00221                 if rim: 
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         
00253         poses = self.setup_poses_snaking( radius )
00254         self.markers_update( poses )
00255         return poses
00256 
00257     def setup_poses_snaking( self, radius ):
00258         
00259         cycles = np.ceil( self.length / radius )
00260         tt = np.linspace( 0.0, self.length, 2 * cycles + 1 )
00261 
00262         
00263         crests = []
00264         atmin = False
00265         for xx in tt:
00266             atmin = not atmin 
00267             yy = float( atmin ) * self.width
00268             crests.append( (xx, yy) )
00269             
00270         
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         
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     
00357     
00358     
00359     rospy.spin()
00360     
00361     
00362     
00363     
00364     
00365 
00366     
00367     
00368     
00369     
00370     
00371     
00372     
00373 
00374     
00375     
00376     
00377     
00378         
00379     
00380