00001
00002
00003
00004
00005
00006
00007
00008
00009 import rospy
00010 import std_msgs.msg as std_msgs
00011 import geometry_msgs.msg as geometry_msgs
00012 import threading
00013 import tf
00014 import dynamic_reconfigure.client
00015
00016
00017
00018 from .rotate import Rotate
00019
00020
00021 class Node(object):
00022 '''
00023 Manages connectivity information provided by services and provides this
00024 for human interactive agent (aka remocon) connections.
00025 '''
00026 __slots__ = [
00027 '_publishers',
00028 '_subscribers',
00029 '_parameters',
00030 '_spotted_markers',
00031 '_target_frame',
00032 '_target_base_t',
00033 '_target_base_o',
00034 '_child_frame_id',
00035 '_parent_frame_id',
00036 '_thread',
00037 '_tf_thread',
00038 '_tf_broadcaster',
00039 '_rotate',
00040 '_rate',
00041 '_listener',
00042 '_running',
00043 '_controller_finished',
00044 '_stop_requested',
00045 '_dynamic_reconfigure_client'
00046 ]
00047 SPOTTED_NONE = 'none'
00048 SPOTTED_LEFT = 'left'
00049 SPOTTED_RIGHT = 'right'
00050 SPOTTED_BOTH = 'both'
00051
00052
00053
00054
00055
00056 def __init__(self):
00057 self._rotate = Rotate('~cmd_vel')
00058 self._publishers, self._subscribers = self._setup_ros_api()
00059 self._parameters = self._setup_parameters()
00060 self._spotted_markers = Node.SPOTTED_NONE
00061 self._thread = None
00062 self._tf_thread = None
00063 self._running = False
00064 self._rate = 0.36
00065 self._listener = tf.TransformListener()
00066 self._tf_broadcaster = tf.TransformBroadcaster()
00067 self._controller_finished = False
00068 self._stop_requested = False
00069 self._dynamic_reconfigure_client = dynamic_reconfigure.client.Client(rospy.get_param('~ar_tracker', 'ar_track_alvar'))
00070
00071 def _setup_parameters(self):
00072 parameters = {}
00073 parameters['search_only'] = rospy.get_param('~search_only', False)
00074 parameters['base_postfix'] = rospy.get_param('~base_postfix', 'base')
00075 parameters['base_frame'] = rospy.get_param('~base_frame', 'base_footprint')
00076 parameters['odom_frame'] = rospy.get_param('~odom_frame', 'odom')
00077 return parameters
00078
00079 def _setup_ros_api(self):
00080 '''
00081 These are all public topics. Typically that will drop them into the /concert
00082 namespace.
00083 '''
00084 publishers = {}
00085 publishers['result'] = rospy.Publisher('~result', std_msgs.Bool, queue_size=5)
00086 publishers['initial_pose_trigger'] = rospy.Publisher('~initialise', std_msgs.Empty, queue_size=5)
00087 publishers['enable_approach_controller'] = rospy.Publisher('~enable_approach_controller', std_msgs.String, queue_size=5)
00088 publishers['disable_approach_controller'] = rospy.Publisher('~disable_approach_controller', std_msgs.Empty, queue_size=5)
00089 subscribers = {}
00090 subscribers['enable'] = rospy.Subscriber('~enable', std_msgs.String, self._ros_enable_subscriber)
00091 subscribers['spotted_markers'] = rospy.Subscriber('~spotted_markers', std_msgs.String, self._ros_spotted_subscriber)
00092 subscribers['relative_target_pose'] = rospy.Subscriber('~relative_target_pose', geometry_msgs.PoseStamped, self._ros_relative_target_pose_subscriber)
00093
00094 subscribers['approach_controller_result'] = rospy.Subscriber('~approach_pose_reached', std_msgs.Bool, self._ros_controller_result_callback)
00095 return (publishers, subscribers)
00096
00097 def _is_running(self):
00098 return self._running
00099
00100
00101
00102
00103
00104 def _ros_enable_subscriber(self, msg):
00105 if msg.data:
00106 rospy.loginfo('enable ar pair approach')
00107 if not self._is_running():
00108 self._running = True
00109 self._set_target_base_transform(msg.data)
00110 self._tf_thread =threading.Thread(target=self._broadcast_tfs)
00111 self._tf_thread.start()
00112 self._thread = threading.Thread(target=self.execute)
00113 self._thread.start()
00114 else:
00115 rospy.loginfo('disable ar pair approach')
00116 if self._is_running():
00117 self._publishers['result'].publish(std_msgs.Bool(False))
00118 self._stop()
00119 if self._thread is not None:
00120 self._thread.join()
00121 self._thread = None
00122 self._stop_requested = False
00123
00124 def _ros_relative_target_pose_subscriber(self, msg):
00125 self._relative_target_pose = msg
00126
00127 def _ros_spotted_subscriber(self, msg):
00128 self._spotted_markers = msg.data
00129 if self._spotted_markers == Node.SPOTTED_BOTH and self._rotate.is_running():
00130 self._rotate.stop()
00131
00132 def _ros_controller_result_callback(self, msg):
00133 rospy.loginfo("AR Pair Approach : received result from the approach controller.")
00134 if msg.data:
00135 rospy.loginfo("AR Pair Approach : Controller reached the goal.")
00136 self._controller_finished = True
00137 else:
00138 rospy.loginfo("AR Pair Approach : Controller failed to reach the goal.")
00139
00140
00141
00142
00143
00144
00145 def _stop(self):
00146 if not self._rotate.is_stopped():
00147 self._rotate.stop()
00148 self._stop_requested = True
00149
00150 def _post_execute(self, result):
00151 self._stop_requested = False
00152 if not rospy.is_shutdown():
00153 self._dynamic_reconfigure_client.update_configuration({'enabled': 'False'})
00154 self._publishers['result'].publish(std_msgs.Bool(result))
00155 self._running = False
00156 self._tf_thread.join()
00157
00158 def execute(self):
00159 self._dynamic_reconfigure_client.update_configuration({'enabled': 'True'})
00160 found_markers = self._initialise_rotation()
00161 if not found_markers:
00162 result = self._rotate.execute()
00163 if not result or self._stop_requested:
00164 self._post_execute(False)
00165 return
00166 rospy.loginfo("AR Pair Approach : found both ar pair markers.")
00167 if self._parameters['search_only']:
00168 rospy.loginfo("AR Pair Approach : aborting initialisation and approach as requested.")
00169 return
00170 rospy.loginfo("AR Pair Approach : setting an initial pose from the global ar pair reference.")
00171 self._publishers['initial_pose_trigger'].publish(std_msgs.Empty())
00172 rospy.loginfo("AR Pair Approach : enabling the approach controller")
00173
00174
00175 base_target_frame = self._target_frame + '_' + self._parameters['base_postfix']
00176 self._publishers['enable_approach_controller'].publish(base_target_frame)
00177 while not rospy.is_shutdown() and not self._stop_requested:
00178 if self._controller_finished:
00179 self._controller_finished = False
00180 break
00181 rospy.sleep(0.1)
00182 rospy.loginfo("AR Pair Approach : disabling the approach controller")
00183 self._publishers['disable_approach_controller'].publish(std_msgs.Empty())
00184 if rospy.is_shutdown() or self._stop_requested:
00185 self._post_execute(False)
00186 else:
00187 self._post_execute(True)
00188
00189 def _set_target_base_transform(self, target_frame):
00190 self._target_frame = target_frame
00191 rospy.sleep(2.0)
00192 try:
00193
00194 (t, o) = self._listener.lookupTransform(self._parameters['odom_frame'], target_frame, rospy.Time(0))
00195 except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException) as unused_e:
00196 self.logwarn("couldn't get transfrom between %s and %s"%(self._parameters['odom_frame'], target_frame))
00197 return
00198
00199
00200 p = (0.0, 0.36, 0.0)
00201 q = tf.transformations.quaternion_from_euler(1.57, -1.57, 0.0)
00202
00203 self._target_base_t = (t[0], t[1], 0.0)
00204 self._target_base_o = tf.transformations.quaternion_multiply(o, q)
00205
00206
00207 base_postfix = self._parameters['base_postfix']
00208 self._parent_frame_id = self._parameters['odom_frame']
00209 self._child_frame_id = target_frame + '_' + base_postfix
00210
00211 def _broadcast_tfs(self):
00212 r = rospy.Rate(5)
00213
00214 while self._running and not rospy.is_shutdown():
00215 self._publish_tf()
00216 r.sleep()
00217
00218
00219 def _publish_tf(self):
00220 self._tf_broadcaster.sendTransform(self._target_base_t, self._target_base_o, rospy.Time.now(), self._child_frame_id, self._parent_frame_id)
00221
00222
00223
00224
00225
00226 def _initialise_rotation(self):
00227 '''
00228 Do not call this if already running, you will cause self._rotate to become volatile.
00229
00230 @return : True or false depending on if we can skip this step or not.
00231 '''
00232 direction = Rotate.CLOCKWISE
00233 rospy.loginfo("Markers : %s"%self._spotted_markers)
00234 if self._spotted_markers == Node.SPOTTED_BOTH:
00235 rospy.loginfo("AR Pair Approach : received an enable command, both spotted markers already in view!")
00236 return True
00237 elif self._spotted_markers == Node.SPOTTED_LEFT:
00238 rospy.loginfo("AR Pair Approach : received an enable command, only left in view.")
00239 elif self._spotted_markers == Node.SPOTTED_RIGHT:
00240 rospy.loginfo("AR Pair Approach : received an enable command, only right in view.")
00241 direction = Rotate.COUNTER_CLOCKWISE
00242 else:
00243 try:
00244 rospy.logerr("AR Pair Approach : this should not happen")
00245
00246 (unused_t, orientation) = self._listener.lookupTransform(self._target_frame, self._parameters['base_frame'], rospy.Time(0))
00247 unused_roll, unused_pitch, yaw = tf.transformations.euler_from_quaternion(orientation)
00248 rospy.loginfo("AR Pair Search : current yaw = %s" % str(yaw))
00249 direction = Rotate.COUNTER_CLOCKWISE if yaw > 0 else Rotate.CLOCKWISE
00250 except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException) as unused_e:
00251 direction = Rotate.COUNTER_CLOCKWISE
00252 rospy.loginfo("AR Pair Search: received an enable command, none in view.")
00253 self._rotate.init(yaw_absolute_rate=self._rate, yaw_direction=direction)
00254 return False
00255
00256 def logwarn(self, msg):
00257 rospy.logwarn('AR Pair Approach : %s'%msg)
00258
00259 def spin(self):
00260 '''
00261 Parse the set of /remocons/<name>_<uuid> connections.
00262 '''
00263
00264
00265 rospy.spin()
00266 if self._thread is not None:
00267 self._thread.join()