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