// we do not reduce if the goal is already selected , because the orientation // might change -> can introduce oscillations // no_infeasible_plans_ 改成 no_feasible_plans_ 比较合适 if (cfg_.recovery.shrink_horizon_backup && goal_idx < (int)transformed_plan.size()-1 && (no_infeasible_plans_>0 || (current_time - time_last_infeasible_plan_).toSec() < // keep short horizon for at least a few seconds cfg_.recovery.shrink_horizon_min_duration ) ) { ROS_INFO_COND(no_infeasible_plans_==1, "Activating reduced horizon backup mode for at least %.2f sec (infeasible trajectory detected).", cfg_.recovery.shrink_horizon_min_duration);
// Shorten horizon if requested reduce to 50 percent: int horizon_reduction = goal_idx/2; if (no_infeasible_plans_ > 9) { ROS_INFO_COND(no_infeasible_plans_==10, "Infeasible trajectory detected 10 times in a row: further reducing horizon..."); horizon_reduction /= 2; } // we have a small overhead (过头) here, since we already transformed 50% // more of the trajectory. But that's ok for now, since we do not // need to make transformGlobalPlan more complex // reduced horizon 应当很少发生 int new_goal_idx_transformed_plan = int(transformed_plan.size()) - horizon_reduction - 1; goal_idx -= horizon_reduction;
// 从 transformed_plan 的 new_goal_idx 开始,一直删除到尾部 if (new_goal_idx_transformed_plan >0 && goal_idx >= 0) transformed_plan.erase(transformed_plan.begin() + new_goal_idx_transformed_plan, transformed_plan.end()); else goal_idx += horizon_reduction; // this should not happen, but safety first } // ......第二部分...... }
shrink_horizon_backup: true. 在自动检测到问题(plan not feasible)的情况下,允许planner临时缩小范围(50%). TEB将以更近的点作为规划目标,尝试重新规划出可行路径。调试时可关闭,以在可视化界面上观察原出错路径。另请参阅参数shrink_horizon_min_duration
failure_detector_.update(last_cmd_, cfg_.robot.max_vel_x, cfg_.robot.max_vel_x_backwards, max_vel_theta, cfg_.recovery.oscillation_v_eps, cfg_.recovery.oscillation_omega_eps); bool oscillating = failure_detector_.isOscillating(); // 距离上次振荡过了多久,若小于配置值(10秒),说明最近也振荡了 bool recently_oscillated = (ros::Time::now()-time_last_oscillation_).toSec() < cfg_.recovery.oscillation_recovery_min_duration; // 如果现在是振荡状态 if (oscillating) { if (!recently_oscillated) // 最近的状态不是振荡 { // save current turning direction // 以下其实都是对 last_preferred_rotdir_ 赋值 if (robot_vel_.angular.z > 0) last_preferred_rotdir_ = RotType::left; else last_preferred_rotdir_ = RotType::right; ROS_WARN("TebLocalPlannerROS: possible oscillation (of the robot or its local plan) detected. Activating recovery strategy (prefer current turning direction during optimization) "); } time_last_oscillation_ = ros::Time::now(); // 对 prefer_rotdir_ 赋值,最终影响 AddEdgesPreferRotDir planner_->setPreferredTurningDir(last_preferred_rotdir_); } // 现在和最近都没有振荡 clear recovery behavior elseif (!recently_oscillated && last_preferred_rotdir_ != RotType::none) { last_preferred_rotdir_ = RotType::none; planner_->setPreferredTurningDir(last_preferred_rotdir_); ROS_INFO("TebLocalPlannerROS: oscillation recovery disabled/expired."); } }
但是如果机器人确实stucked,最终会在move_base里Abort
setPreferredTurningDir
设置期望的初始转弯方向 (也就是惩罚相反的方向)。A desired (initial) turning direction might be specified in case the planned trajectory oscillates between two solutions (in the same equivalence class!) with similar cost. 配置参数以调整惩罚的权重,Initial 说明惩罚只适用于trajectory的前几个位姿
voidHomotopyClassPlanner::setPreferredTurningDir(RotType dir) { // set preferred turning dir for all TEBs for (TebOptPlannerContainer::const_iterator it_teb = tebs_.begin(); it_teb != tebs_.end(); ++it_teb) { (*it_teb)->setPreferredTurningDir(dir); } }
voidTebOptimalPlanner::AddEdgesPreferRotDir() { /* roesmann 注释: these edges can result in odd predictions, in particular we can observe a substantional mismatch between open- and closed-loop planning leading to a poor control performance. 目前这个功能只用于振荡恢复,短期使用这个约束不会有重大影响 可能让机器人脱离振荡 */ if (prefer_rotdir_ == RotType::none || cfg_->optim.weight_prefer_rotdir == 0) return; // 必须定义好转向 if (prefer_rotdir_ != RotType::right && prefer_rotdir_ != RotType::left) { ROS_WARN("TebOptimalPlanner::AddEdgesPreferRotDir(): unsupported RotType selected. Skipping edge creation."); return; } // create edge for satisfiying kinematic constraints Eigen::Matrix<double,1,1> information_rotdir; information_rotdir.fill(cfg_->optim.weight_prefer_rotdir); // 目前只使用前三个旋转 for (int i=0; i < teb_.sizePoses()-1 && i < 3; ++i) { EdgePreferRotDir* rotdir_edge = new EdgePreferRotDir; rotdir_edge->setVertex(0, teb_.PoseVertex(i)); rotdir_edge->setVertex(1, teb_.PoseVertex(i+1)); rotdir_edge->setInformation(information_rotdir); if (prefer_rotdir_ == RotType::left) rotdir_edge->preferLeft(); elseif (prefer_rotdir_ == RotType::right) rotdir_edge->preferRight(); optimizer_->addEdge(rotdir_edge); } }
// Set buffer length (measurement history) // length: number of measurements to be kept voidsetBufferLength(int length){ buffer_.set_capacity(length); }
voidTebOptimalPlanner::clearGraph() { // clear optimizer states if (optimizer_) { // neccessary, because optimizer->clear deletes pointer-targets // therefore it deletes TEB states optimizer_->vertices().clear(); optimizer_->clear(); } }
// Do not allow config changes during the following optimization step boost::mutex::scoped_lock cfg_lock(cfg_.configMutex()); bool success = planner_->plan(robot_pose_, robot_goal_, &robot_vel_, cfg_.goal_tolerance.free_goal_vel); // straight line init if (!success) { planner_->clearPlanner(); // force reinitialization for next time ROS_WARN("not able to obtain a local plan for the current setting"); ++no_infeasible_plans_; // increase number of infeasible solutions in a row time_last_infeasible_plan_ = ros::Time::now(); last_cmd_ = cmd_vel; returnfalse; } // Check feasibility (but within the first few states only) if(cfg_.robot.is_footprint_dynamic) { // Update footprint of the robot and minimum and maximum distance // from the center of the robot to its footprint vertices. footprint_spec_ = costmap_ros_->getRobotFootprint(); costmap_2d::calculateMinAndMaxDistances(footprint_spec_, robot_inscribed_radius_, robot_circumscribed_radius); // is a circular footprint if( robot_circumscribed_radius / robot_inscribed_radius_ < 1.02 ) cfg_.obstacles.min_obstacle_dist = robot_circumscribed_radius; // fit dynamic footprint cfg_.robot.robot_inscribed_radius = robot_inscribed_radius_; cfg_.robot.robot_circumscribed_radius = robot_circumscribed_radius; }
//Internal container storing the sequence of optimzable pose vertices PoseSequence pose_vec_; // Internal container storing the sequence of optimzable timediff vertices TimeDiffSequence timediff_vec_;
// Container of poses that represent the spatial(空间的) part of the trajectory typedef std::vector<VertexPose*> PoseSequence; // Container of time differences that define the temporal(时间的) of the trajectory typedef std::vector<VertexTimeDiff*> TimeDiffSequence;
类VertexPose是g2o类型,在g2o_types\vertex_pose.h,保存和封装SE2 pose (position and orientation) into a vertex(图顶点),用于g2o的图优化。 类VertexTimeDiff是g2o类型,在g2o_types\vertex_timediff.h,保存和封装时间差deltaT为一个vertex,用于g2o的图优化.
bool backwards = false; // 参数 guess_backwards_motion 在这里 // check if the goal is behind the start pose (w.r.t. start orientation) if ( guess_backwards_motion && ( goal.position()-start.position()).dot(start.orientationUnitVec()) < 0) backwards = true; // TODO: dt ~ max_vel_x_backwards for backwards motions // 取自全局路径点 for (int i=1; i<(int)plan.size()-1; ++i) { double yaw; if (estimate_orient) { // get yaw from the orientation of the distance vector // between pose_{i+1} and pose_{i} double dx = plan[i+1].pose.position.x - plan[i].pose.position.x; double dy = plan[i+1].pose.position.y - plan[i].pose.position.y; yaw = std::atan2(dy,dx); if (backwards) yaw = g2o::normalize_theta(yaw+M_PI); } else { yaw = tf::getYaw(plan[i].pose.orientation); } PoseSE2 intermediate_pose(plan[i].pose.position.x, plan[i].pose.position.y, yaw); /* 根据最大线速度和角速度,估计从 BackPose 到 intermediate_pose所花的时间, 假设匀速运动。也就是说这是估算了一个最小值 */ double dt = estimateDeltaT(BackPose(), intermediate_pose, max_vel_x, max_vel_theta); // intermediate_pose添加到 pose_vec_, 约束false, 它成为BackPose() // dt 添加到 timediff_vec_ , 约束false addPoseAndTimeDiff(intermediate_pose, dt); } // number of samples 小于参数 min_samples, insert manually // -1 的意思是不包含goal point,它在之后添加 if ( sizePoses() < min_samples-1 ) { ROS_DEBUG("initTEBtoGoal(): number of generated samples is less than specified by min_samples. Forcing the insertion of more samples ..."); while (sizePoses() < min_samples-1) { // 策略: interpolate between the current pose and the goal // 只处理了 BackPose() 到 goal 这一段,而且是逐步二分 PoseSE2 intermediate_pose = PoseSE2::average(BackPose(), goal); double dt = estimateDeltaT(BackPose(), intermediate_pose, max_vel_x, max_vel_theta); // let the optimier correct the timestep (TODO: better initialization addPoseAndTimeDiff( intermediate_pose, dt ); } } // Now add final state with given orientation double dt = estimateDeltaT(BackPose(), goal, max_vel_x, max_vel_theta); // goal 也插入 pose_vec_, 成为 BackPose() addPoseAndTimeDiff(goal, dt); // GoalConf 也成为了固定约束 setPoseVertexFixed(sizePoses()-1,true); } else// size!=0 { ROS_WARN("Cannot init TEB between given configuration and goal, because TEB vectors are not empty or TEB is already initialized (call this function before adding states yourself)!"); ROS_WARN("Number of TEB configurations: %d, Number of TEB timediffs: %d", (unsignedint) sizePoses(), (unsignedint) sizeTimeDiffs()); returnfalse; } returntrue; }
// new_start new_goal 的根源都在 transformed_plan voidTimedElasticBand::updateAndPruneTEB(boost::optional<const PoseSE2&> new_start, boost::optional<const PoseSE2&> new_goal, int min_samples) { // first and simple approach: change only start confs(and virtual start conf for inital velocity) // TEST if optimizer can handle this "hard" placement if ( new_start && sizePoses()>0 ) { // find nearest state (using L2-norm) in order to prune the trajectory // (remove already passed states) double dist_cache = (new_start->position()- Pose(0).position()).norm(); double dist; // satisfy min_samples, otherwise max 10 samples int lookahead = std::min<int>( sizePoses()-min_samples, 10);
int nearest_idx = 0; for (int i = 1; i<=lookahead; ++i) { dist = (new_start->position()- Pose(i).position() ).norm(); if (dist < dist_cache) { dist_cache = dist; nearest_idx = i; } elsebreak; } // prune trajectory at the beginning (and extrapolate //sequences at the end if the horizon is fixed ) if (nearest_idx>0) { // nearest_idx is equal to the number of samples to be removed (since it counts from 0 ) // WARNING delete starting at pose 1, and overwrite the original pose(0) with // new_start, since Pose(0) is fixed during optimization!
// delete first states such that the closest state is the new first one deletePoses(1, nearest_idx); deleteTimeDiffs(1, nearest_idx); // delete corresponding time differences } // update start Pose(0) = *new_start; } if (new_goal && sizePoses()>0) { BackPose() = *new_goal; } };
found_legal is 0 [ERROR] Failed to get a plan. [ INFO] Robot in controlling, but can't get new global plan [ERROR] 17:51:42.074 (): Aborting because a valid plan could not be found. Even after executing all recovery behaviors
Received move_base goal (x: 62.766, y: -1.474, yaw deg: -5, yaw rad: -0.09) [ERROR] Failed to get a plan. [ INFO] Robot in controlling, but can't get new global plan [INFO] move_base_recovery,state machine is CLEARING, Executing behavior 0 of 2 [ WARN] Clearing costmap to unstuck robot (0.000000m). [ERROR] Failed to get a plan. [ INFO] Robot in controlling, but can't get new global plan [ INFO] move_base_recovery,state machine is CLEARING, Executing behavior 0 of 2 [ WARN] Clearing costmap to unstuck robot (0.000000m). [ INFO] 到达目标
// 禁止配置参数的更改 during the following optimization step boost::mutex::scoped_lock cfg_lock(cfg_.configMutex() ); bool success = planner_->plan(transformed_plan, &robot_vel_, cfg_.goal_tolerance.free_goal_vel); if (!success) { planner_->clearPlanner(); // force reinitialization for next time ROS_WARN("teb_local_planner was not able to obtain a local plan for the current setting."); ++no_infeasible_plans_; // increase number of infeasible solutions in a row time_last_infeasible_plan_ = ros::Time::now(); last_cmd_ = cmd_vel; returnfalse; }
// Get robot pose in global frame(map坐标系),转为PoseSE2类型 tf::Stamped<tf::Pose> robot_pose; costmap_ros_->getRobotPose(robot_pose); robot_pose_ = PoseSE2(robot_pose);
// Get robot velocity from odom tf::Stamped<tf::Pose> robot_vel_tf; odom_helper_.getRobotVel(robot_vel_tf); robot_vel_.linear.x = robot_vel_tf.getOrigin().getX(); robot_vel_.linear.y = robot_vel_tf.getOrigin().getY(); robot_vel_.angular.z = tf::getYaw(robot_vel_tf.getRotation());
// prune global plan to cut off parts of the past (spatially before the robot) pruneGlobalPlan(*tf_, robot_pose, global_plan_);
// Transform global plan to the frame of interest (w.r.t. the local costmap) std::vector<geometry_msgs::PoseStamped> transformed_plan; int goal_idx; tf::StampedTransform tf_plan_to_global; if (!transformGlobalPlan(*tf_, global_plan_, robot_pose, *costmap_, global_frame_, cfg_.trajectory.max_global_plan_lookahead_dist, transformed_plan, &goal_idx, &tf_plan_to_global)) { ROS_WARN("Could not transform the global plan to the frame of the controller"); returnfalse; }
// update via-points container 这里是false,不用看了 if (!custom_via_points_active_) updateViaPointsContainer(transformed_plan, cfg_.trajectory.global_plan_viapoint_sep);
if(!goal_reached_) { if(fabs(std::sqrt(dx*dx+dy*dy)) < cfg_.goal_tolerance.xy_goal_tolerance && fabs(delta_orient) < cfg_.goal_tolerance.yaw_goal_tolerance && (!cfg_.goal_tolerance.complete_global_plan || via_points_.size() == 0)) { goal_reached_ = true; returntrue; } } // check if we should enter any backup mode and apply settings configureBackupModes(transformed_plan, goal_idx);
// Return false if the transformed global plan is empty if (transformed_plan.empty()) { ROS_WARN("Transformed plan is empty. Cannot determine a local plan."); returnfalse; }