class StatusManager: def __init__(self, config): # X. Initialize announcer. self.announcer = TextToSpeech() self.announcer.add_speech_text( "Initializing system. Please wait for a moment.") # X. Tf self.broadcaster = tf.TransformBroadcaster() self.listener = tf.TransformListener() # Prepare shared object. self.data = SharedData() # Publishers & Subscribers self.pubs = Publishers() self.subs = Subscribers(self.announcer) # Store tf if (not config.pose_init): while (self.subs.odom.get_object() is None): time.sleep(0.5) odom = self.subs.odom.get_object() odom_xyzrpy = create_xyzrpy_from_pose(odom.pose.pose) pose_stamped = PoseStamped() pose_stamped.header.stamp = rospy.Time.now() pose_stamped.header.frame_id = 'map' pose_stamped.pose = create_pose_from_xyzrpy(config.xyzrpy) self.data.last_valid_ndt_pose = pose_stamped self.data.last_valid_tf_odom_to_map = create_transform_from_pose( config.xyzrpy, odom_xyzrpy) # Initilize Pose self.initialize_pose(config) # Start checking thread self.checking_thread = threading.Thread(target=self.run_checking) self.checking_thread.start() # Start control self.run_control() # Terminate thread. self.announcer.terminate() self.checking_thread.join() def initialize_pose(self, config): init_xyzrpy = config.xyzrpy if (config.via_gnss): # X. Wait update from gnss. while (self.subs.gnss_pose.get_object() is None and not rospy.is_shutdown()): rospy.logwarn("Waiting for GNSS data to be received.") time.sleep(0.5) init_xyzrpy = create_xyzrpy_from_pose( self.subs.gnss_pose.get_object().pose) init_xyzrpy.roll = 0 init_xyzrpy.pitch = 0 init_xyzrpy.yaw = 0 # X. Start pose initializer if necessary. if (config.pose_init): self.announcer.add_speech_text( "Start pose initializer. This will take about twenty seconds.") # X. Switch to low resolution. self.pubs.cfg_voxel.publish(CONFIG_VOXEL_FILT_SEARCH()) req = FULL_INIT_REQUEST(init_xyzrpy) init_xyzrpy = call_pose_initialize_service(req) # X. Set high resolution mode. self.pubs.cfg_voxel.publish(CONFIG_VOXEL_FILT_RUN()) # X. Send for ndt localizer. self.pubs.cfg_ntd.publish(DEFAULT_CONFIG_NDT(init_xyzrpy)) # X. Send for amcl localizer. rospy.logwarn("amcl : {}".format( create_pose_stamped_with_cov(init_xyzrpy))) self.pubs.amcl_init.publish(create_pose_stamped_with_cov(init_xyzrpy)) # X. Notify self.announcer.add_speech_text( "Pose initialization is done. Start control.") def run_pose_initialize_if_necessary(self): if (self.data.last_pose_init_time is None \ or rospy.Time.now() - self.data.last_pose_init_time > rospy.Duration(LOCALIZATION_REINIT_MINIMUM_PERIOD)): if (LOCALIZATION_CNT_REINIT <= self.data.localization_not_reliable_cnt): self.data.pose_initializing = True INIT_POSE_STANDSTILL_COUNT = 10 wait_cnt = 0 while (self.data.standstill_count < INIT_POSE_STANDSTILL_COUNT): wait_cnt += 1 if (wait_cnt % 3): self.announcer.add_speech_text("Wait robot to stop.") time.sleep(1.0) if (self.data.last_valid_ndt_pose is not None \ and rospy.Time.now() - self.data.last_valid_ndt_pose.header.stamp < rospy.Duration(LOCALIZATION_ODOM_TRUST_PERIOD)): self.announcer.add_speech_text( "Reinitialize localization based on odometry. Robot will stop." ) config = StatusManagerConfig( True, False, create_xyzrpy_from_pose( self.data.last_cur_pose.get_object().pose)) else: self.announcer.add_speech_text( "Reinitialize localization based on GNSS. Robot will stop." ) config = StatusManagerConfig( True, True, XYZRPY(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)) # X. This function takes time. self.initialize_pose(config) self.data.last_pose_init_time = rospy.Time.now() self.data.pose_initializing = False self.data.localization_not_reliable_cnt \ = max(0, self.data.localization_not_reliable_cnt - LOCALIZATION_CNT_REINIT / 2) def check_robot_stop_reason(self): obs_idx = self.subs.obst_idx.get_object() if (obs_idx is None or obs_idx.data == -1): self.data.stopped_due_to_obstable_cnt = 0 return twist_raw = self.subs.twist_raw.get_object() if (twist_raw is None or \ not twist_is_almost_zero(twist_raw.twist, STANDSTILL_VX_THR, STANDSTILL_WX_THR)): self.data.stopped_due_to_obstable_cnt = 0 return fin_wps = self.subs.final_wps.get_object() #ndt_pose = self.subs.ndt_pose.get_object() last_cur_pose = self.data.last_cur_pose.get_object() if (fin_wps is None or last_cur_pose is None): self.data.stopped_due_to_obstable_cnt = 0 return print("Distance to obstacle : {}".format( compute_distance_to_obstacle_on_waypoint(obs_idx.data, fin_wps, last_cur_pose.pose))) if (compute_distance_to_obstacle_on_waypoint( obs_idx.data, fin_wps, last_cur_pose.pose) < OBSTACLE_DIST): if (self.data.stopped_due_to_obstable_cnt % 3 == 0): self.announcer.add_speech_text( "Robot stops due to obstacle in front.") self.data.stopped_due_to_obstable_cnt += 1 if (WAIT_BEFORE_AVOID_OBST_SEC < \ self.data.stopped_due_to_obstable_cnt / STATUS_CHECK_HZ): if (is_avoidance_ok_waypoint(obs_idx.data, fin_wps)): self.announcer.add_speech_text( "Rerouting for obstacle avoidance.") header = Header() header.stamp = rospy.Time.now() self.pubs.astar_avoid.publish(header) self.data.stopped_due_to_obstable_cnt = 0 else: self.announcer.add_speech_text( "Rerouting is not allowed here. Keep waiting.") self.data.stopped_due_to_obstable_cnt = 0 def run_checking(self): rate = rospy.Rate(STATUS_CHECK_HZ) while not rospy.is_shutdown(): # X. Robot stuck reason. self.check_robot_stop_reason() # X. Localization status. self.run_pose_initialize_if_necessary() rate.sleep() def run_control(self): rate = rospy.Rate(CONTROL_LOOP_HZ) while not rospy.is_shutdown(): cur_time = rospy.Time.now() # X. Control command. twist_st = self.subs.twist_raw.get_object() if (twist_st is not None and not self.data.pose_initializing): self.pubs.cmd_vel.publish( self.subs.twist_raw.get_object().twist) else: self.pubs.cmd_vel.publish( create_twist(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)) # X. Velocity routing. odom = self.subs.odom.get_object() if (odom is not None): twist_stamped = TwistStamped() twist_stamped.header.stamp = cur_time twist_stamped.twist = odom.twist.twist self.pubs.cur_vel.publish(twist_stamped) if (twist_is_almost_zero(odom.twist.twist, STANDSTILL_VX_THR, STANDSTILL_WX_THR)): self.data.standstill_count += 1 else: self.data.standstill_count = 0 # X. Check pose validity if (not self.data.pose_initializing): self.check_pose_validity() # X. Current pose self.publish_curpose(cur_time) rate.sleep() def check_pose_validity(self): # X. When map pose is unstable. ndt_pose = self.subs.ndt_pose.get_object() amcl_pose = self.subs.amcl_pose.get_object() if (ndt_pose is not None and amcl_pose is not None and \ not pose_is_almost_same(ndt_pose, amcl_pose.pose, \ LOCALIZATION_TRANS_THRESH, LOCALIZATION_YAW_THRESH)): self.data.localization_not_reliable_cnt = \ min(self.data.localization_not_reliable_cnt + 1, LOCALIZATION_CNT_REINIT) # X. When map pose is stable. else: # X. Extract last valid pose. if (ndt_pose is not None and amcl_pose is not None and \ pose_is_almost_same(ndt_pose, amcl_pose.pose, \ LOCALIZATION_TRANS_RELIABLE_THRESH, LOCALIZATION_YAW_RELIABLE_THRESH)): try: transform = self.listener.lookupTransform( 'map', 'odom', ndt_pose.header.stamp) self.data.last_valid_ndt_pose_queue.append( [ndt_pose, transform]) except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException): pass # If reaches stable count, treat the first one as valid pose. if (len(self.data.last_valid_ndt_pose_queue) == LOCALIZATION_RELIABLE_CNT): self.data.last_valid_ndt_pose = self.data.last_valid_ndt_pose_queue[ 0][0] self.data.last_valid_tf_odom_to_map = self.data.last_valid_ndt_pose_queue[ 0][1] else: self.data.last_valid_ndt_pose_queue.clear() self.data.localization_not_reliable_cnt \ = max(0, self.data.localization_not_reliable_cnt - 1) def publish_curpose(self, cur_time): odom = self.subs.odom.get_object() if (odom is not None): pose_stamped = PoseStamped() pose_stamped.header.stamp = cur_time pose_stamped.header.frame_id = "map" pose_st = self.subs.ndt_pose.get_object() if (pose_st is not None and \ self.data.localization_not_reliable_cnt == 0 and \ not self.data.pose_initializing): # X. Use ndt pose as cur_pose pose_stamped.pose = pose_st.pose transform = create_tf_transform_from_pose( pose_st.pose, odom.pose.pose) # X. Send tf self.broadcaster.sendTransform(transform[0], transform[1], cur_time, 'odom', 'map') self.pubs.cur_pose.publish(pose_stamped) self.data.last_cur_pose.set_object(pose_stamped) self.data.ndt_pose_unreliable_cnt = 0 else: # X. Use odometry as cur_pose if (self.data.last_valid_tf_odom_to_map is not None): trans = self.data.last_valid_tf_odom_to_map[0] rot = self.data.last_valid_tf_odom_to_map[1] # X. Send tf self.broadcaster.sendTransform(trans, rot, cur_time, 'odom', 'map') trans_pose = transform_pose(odom.pose.pose, trans, rot) pose_stamped.pose = trans_pose self.pubs.cur_pose.publish(pose_stamped) self.data.last_cur_pose.set_object(pose_stamped) self.data.ndt_pose_unreliable_cnt += 1 if (self.data.ndt_pose_unreliable_cnt % (5 * CONTROL_LOOP_HZ) == 0): self.announcer.add_speech_text( "Ndt pose is unstable. ")