零、前言
此前寫了一篇對move_base的解析,號稱全網最全,因為把整個包都加了注釋hhh。
但是那篇文章由於是以源代碼+注釋的形式呈現,存在諸多問題,比如:沒有按照邏輯順序解析(而閱讀代碼的正確步驟是 按照程序執行順序一步一步看),沒有深挖里面的函數而更多的是對本框架的理解,也沒有把各個函數之間通用的變量講清楚(比如:move_base的狀態實際上是貫穿整個規划過程的、各個標志位在不同函數之間都是有變化的),等等。
本篇博文在此前那篇的基礎上,按照程序執行順序一步一步進行講解,並且在一定程度上進行了深挖(譬如,與其他包是怎么關聯的),也在程序講解時 把頭文件中列出的類成員的含義進行了說明。
個人可能會有錯誤,歡迎朋友們批評指正,並私信我取得聯系,在此感謝。
話不多說,開始解析——
一、入口
入口為文件move_base_node.cpp,聲明了move_base::MoveBase對象move_base,傳入參數為tf2_ros::Buffer& tf 。聲明的時候,便進入了構造函數。但是在看構造函數前,我們先來看一下在move_base這個命名空間中都有哪些數據。
二、頭文件-命名空間
進入入口的命名空間,命名空間為move_base。接下來挨個介紹一下命名空間move_base的內容:
1、聲明server端,消息類型是move_base_msgs::MoveBaseAction
1 typedef actionlib::SimpleActionServer<move_base_msgs::MoveBaseAction> MoveBaseActionServer;
2、枚舉movebase狀態表示
1 enum MoveBaseState { 2 PLANNING, 3 CONTROLLING, 4 CLEARING 5 };
3、枚舉,觸發恢復模式
1 enum RecoveryTrigger 2 { 3 PLANNING_R, 4 CONTROLLING_R, 5 OSCILLATION_R 6 };
4、MoveBase類,使用actionlib::ActionServer接口,該接口將robot移動到目標位置
1 class MoveBase{ } //下面詳細展開
三、頭文件-MoveBase類
在move_base命名空間中,最重要的就是MoveBase類了,在(一)中提到的入口,實際上就是構造函數。在這里,先羅列一下類中有哪些成員,然后在(四)中再詳細解釋各個函數成員與數據成員的關系與執行順序。
1、構造函數,傳入的參數是tf
MoveBase(tf2_ros::Buffer& tf);
2、析構函數
virtual ~MoveBase();
3、控制閉環、全局規划、 到達目標返回true,沒有到達返回false
bool executeCycle(geometry_msgs::PoseStamped& goal, std::vector<geometry_msgs::PoseStamped>& global_plan);
4、清除costmap
bool clearCostmapsService(std_srvs::Empty::Request &req, std_srvs::Empty::Response &resp);
5、當action不活躍時,調用此函數,返回plan
bool planService(nav_msgs::GetPlan::Request &req, nav_msgs::GetPlan::Response &resp);
6、新的全局規划,goal 規划的目標點,plan 規划
bool makePlan(const geometry_msgs::PoseStamped& goal, std::vector<geometry_msgs::PoseStamped>& plan);
7、從參數服務器加載導航恢復行為
bool loadRecoveryBehaviors(ros::NodeHandle node);
8、加載默認導航恢復行為
void loadDefaultRecoveryBehaviors();
9、清除機器人局部規划框的障礙物,size_x 局部規划框的長x, size_y 局部規划框的寬y
void clearCostmapWindows(double size_x, double size_y);
10、發布速度為0的指令
void publishZeroVelocity();
11、重置move_base action的狀態,設置速度為0
void resetState();
12、周期性地喚醒規划器
void wakePlanner(const ros::TimerEvent& event);
13、其它函數
1 void goalCB(const geometry_msgs::PoseStamped::ConstPtr& goal); 2 3 void planThread(); 4 5 void executeCb(const move_base_msgs::MoveBaseGoalConstPtr& move_base_goal); 6 7 bool isQuaternionValid(const geometry_msgs::Quaternion& q); 8 9 bool getRobotPose(geometry_msgs::PoseStamped& global_pose, costmap_2d::Costmap2DROS* costmap); 10 11 double distance(const geometry_msgs::PoseStamped& p1, const geometry_msgs::PoseStamped& p2); 12 13 geometry_msgs::PoseStamped goalToGlobalFrame(const geometry_msgs::PoseStamped& goal_pose_msg);
14、數據成員
1 tf2_ros::Buffer& tf_; 2 3 MoveBaseActionServer* as_; //就是actionlib的server端 4 5 boost::shared_ptr<nav_core::BaseLocalPlanner> tc_;//局部規划器,加載並創建實例后的指針 6 costmap_2d::Costmap2DROS* planner_costmap_ros_, *controller_costmap_ros_;//costmap的實例化指針 7 8 boost::shared_ptr<nav_core::BaseGlobalPlanner> planner_;//全局規划器,加載並創建實例后的指針 9 std::string robot_base_frame_, global_frame_; 10 11 std::vector<boost::shared_ptr<nav_core::RecoveryBehavior> > recovery_behaviors_;//可能是出錯后的恢復 12 unsigned int recovery_index_; 13 14 geometry_msgs::PoseStamped global_pose_; 15 double planner_frequency_, controller_frequency_, inscribed_radius_, circumscribed_radius_; 16 double planner_patience_, controller_patience_; 17 int32_t max_planning_retries_; 18 uint32_t planning_retries_; 19 double conservative_reset_dist_, clearing_radius_; 20 ros::Publisher current_goal_pub_, vel_pub_, action_goal_pub_; 21 ros::Subscriber goal_sub_; 22 ros::ServiceServer make_plan_srv_, clear_costmaps_srv_; 23 bool shutdown_costmaps_, clearing_rotation_allowed_, recovery_behavior_enabled_; 24 double oscillation_timeout_, oscillation_distance_; 25 26 MoveBaseState state_; 27 RecoveryTrigger recovery_trigger_; 28 29 ros::Time last_valid_plan_, last_valid_control_, last_oscillation_reset_; 30 geometry_msgs::PoseStamped oscillation_pose_; 31 pluginlib::ClassLoader<nav_core::BaseGlobalPlanner> bgp_loader_; 32 pluginlib::ClassLoader<nav_core::BaseLocalPlanner> blp_loader_; 33 pluginlib::ClassLoader<nav_core::RecoveryBehavior> recovery_loader_; 34 35 //觸發哪種規划器 36 std::vector<geometry_msgs::PoseStamped>* planner_plan_;//保存最新規划的路徑,傳給latest_plan_ 37 std::vector<geometry_msgs::PoseStamped>* latest_plan_;//在executeCycle中傳給controller_plan_ 38 std::vector<geometry_msgs::PoseStamped>* controller_plan_; 39 40 //規划器線程 41 bool runPlanner_; 42 boost::recursive_mutex planner_mutex_; 43 boost::condition_variable_any planner_cond_; 44 geometry_msgs::PoseStamped planner_goal_; 45 boost::thread* planner_thread_; 46 47 48 boost::recursive_mutex configuration_mutex_; 49 dynamic_reconfigure::Server<move_base::MoveBaseConfig> *dsrv_; 50 51 void reconfigureCB(move_base::MoveBaseConfig &config, uint32_t level); 52 53 move_base::MoveBaseConfig last_config_; 54 move_base::MoveBaseConfig default_config_; 55 bool setup_, p_freq_change_, c_freq_change_; 56 bool new_global_plan_;
四、正文來了
文件move_base.cpp定義了上面頭文件里寫的函數,接下來挨個捋一遍:
入口是構造函數MoveBase::MoveBase。
首先,初始化了一堆參數:
1 tf_(tf), //tf2_ros::Buffer& 引用?取址? 2 as_(NULL), //MoveBaseActionServer* 指針 3 planner_costmap_ros_(NULL), controller_costmap_ros_(NULL),//costmap的實例化指針 4 bgp_loader_("nav_core", "nav_core::BaseGlobalPlanner"), //nav_core::BaseGlobalPlanner類型的插件 5 blp_loader_("nav_core", "nav_core::BaseLocalPlanner"),//nav_core::BaseLocalPlanner類型的插件 6 recovery_loader_("nav_core", "nav_core::RecoveryBehavior"), //nav_core::RecoveryBehavior類型的插件 7 planner_plan_(NULL), latest_plan_(NULL), controller_plan_(NULL),//三種規划器,看觸發哪種 8 runPlanner_(false), setup_(false), p_freq_change_(false), c_freq_change_(false), //配置參數 9 new_global_plan_(false) //配置參數
1 as_ = new MoveBaseActionServer(ros::NodeHandle(), "move_base", boost::bind(&MoveBase::executeCb, this, _1), false);
這個時候調用回調函數MoveBase::executeCb(const move_base_msgs::MoveBaseGoalConstPtr& move_base_goal),具體如下:
如果目標非法,則直接返回:
1 if(!isQuaternionValid(move_base_goal->target_pose.pose.orientation)){ 2 as_->setAborted(move_base_msgs::MoveBaseResult(), "Aborting on goal because it was sent with an invalid quaternion"); 3 return; 4 }
其中,isQuaternionValid(const geometry_msgs::Quaternion& q)函數如下,主要用於檢查四元數的合法性:
1 bool MoveBase::isQuaternionValid(const geometry_msgs::Quaternion& q){ 2 //1、首先檢查四元數是否元素完整 3 if(!std::isfinite(q.x) || !std::isfinite(q.y) || !std::isfinite(q.z) || !std::isfinite(q.w)){ 4 ROS_ERROR("Quaternion has nans or infs... discarding as a navigation goal"); 5 return false; 6 } 7 tf2::Quaternion tf_q(q.x, q.y, q.z, q.w); 8 //2、檢查四元數是否趨近於0 9 if(tf_q.length2() < 1e-6){ 10 ROS_ERROR("Quaternion has length close to zero... discarding as navigation goal"); 11 return false; 12 } 13 //3、對四元數規范化,轉化為vector 14 tf_q.normalize(); 15 tf2::Vector3 up(0, 0, 1); 16 double dot = up.dot(up.rotate(tf_q.getAxis(), tf_q.getAngle())); 17 if(fabs(dot - 1) > 1e-3){ 18 ROS_ERROR("Quaternion is invalid... for navigation the z-axis of the quaternion must be close to vertical."); 19 return false; 20 } 21 22 return true; 23 }
ok,回到回調函數繼續看——
1 geometry_msgs::PoseStamped goal = goalToGlobalFrame(move_base_goal->target_pose);
其中,函數MoveBase::goalToGlobalFrame(const geometry_msgs::PoseStamped& goal_pose_msg)如下:
1 geometry_msgs::PoseStamped MoveBase::goalToGlobalFrame(const geometry_msgs::PoseStamped& goal_pose_msg){ 2 std::string global_frame = planner_costmap_ros_->getGlobalFrameID(); 3 geometry_msgs::PoseStamped goal_pose, global_pose; 4 goal_pose = goal_pose_msg; 5 6 //tf一下 7 goal_pose.header.stamp = ros::Time(); 8 9 try{ 10 tf_.transform(goal_pose_msg, global_pose, global_frame); 11 } 12 catch(tf2::TransformException& ex){ 13 ROS_WARN("Failed to transform the goal pose from %s into the %s frame: %s", 14 goal_pose.header.frame_id.c_str(), global_frame.c_str(), ex.what()); 15 return goal_pose_msg; 16 } 17 18 return global_pose; 19 }
ok,回到回調函數繼續看——
設置目標點並喚醒路徑規划線程:
1 boost::unique_lock<boost::recursive_mutex> lock(planner_mutex_); 2 planner_goal_ = goal; 3 runPlanner_ = true; 4 planner_cond_.notify_one(); 5 lock.unlock();
然后發布goal,設置控制頻率:
1 current_goal_pub_.publish(goal); 2 std::vector<geometry_msgs::PoseStamped> global_plan; 3 ros::Rate r(controller_frequency_);
1 if(shutdown_costmaps_){ 2 ROS_DEBUG_NAMED("move_base","Starting up costmaps that were shut down previously"); 3 planner_costmap_ros_->start(); 4 controller_costmap_ros_->start(); 5 }
重置時間標志位:
1 last_valid_control_ = ros::Time::now(); 2 last_valid_plan_ = ros::Time::now(); 3 last_oscillation_reset_ = ros::Time::now(); 4 planning_retries_ = 0;
1 while(n.ok()) 2 { 3 4 //7. 修改循環頻率 5 if(c_freq_change_) 6 { 7 ROS_INFO("Setting controller frequency to %.2f", controller_frequency_); 8 r = ros::Rate(controller_frequency_); 9 c_freq_change_ = false; 10 } 11 12 //8. 如果獲得一個搶占式目標 13 if(as_->isPreemptRequested()){ //action的搶斷函數 14 if(as_->isNewGoalAvailable()){ 15 //如果有新的目標,會接受的,但不會關閉其他進程 16 move_base_msgs::MoveBaseGoal new_goal = *as_->acceptNewGoal(); 17 18 //9.如果目標無效,則返回 19 if(!isQuaternionValid(new_goal.target_pose.pose.orientation)){ 20 as_->setAborted(move_base_msgs::MoveBaseResult(), "Aborting on goal because it was sent with an invalid quaternion"); 21 return; 22 } 23 //10.將目標轉換為全局坐標系 24 goal = goalToGlobalFrame(new_goal.target_pose); 25 26 //we'll make sure that we reset our state for the next execution cycle 27 //11.設置狀態為PLANNING 28 recovery_index_ = 0; 29 state_ = PLANNING; 30 31 //we have a new goal so make sure the planner is awake 32 //12. 設置目標點並喚醒路徑規划線程 33 lock.lock(); 34 planner_goal_ = goal; 35 runPlanner_ = true; 36 planner_cond_.notify_one(); 37 lock.unlock(); 38 39 //13. 把goal發布給可視化工具 40 ROS_DEBUG_NAMED("move_base","move_base has received a goal of x: %.2f, y: %.2f", goal.pose.position.x, goal.pose.position.y); 41 current_goal_pub_.publish(goal); 42 43 //make sure to reset our timeouts and counters 44 //14. 重置規划時間 45 last_valid_control_ = ros::Time::now(); 46 last_valid_plan_ = ros::Time::now(); 47 last_oscillation_reset_ = ros::Time::now(); 48 planning_retries_ = 0; 49 } 50 else { 51 52 //14.重置狀態,設置為搶占式任務 53 //if we've been preempted explicitly we need to shut things down 54 resetState(); 55 56 //通知ActionServer已成功搶占 57 ROS_DEBUG_NAMED("move_base","Move base preempting the current goal"); 58 as_->setPreempted(); 59 60 //we'll actually return from execute after preempting 61 return; 62 } 63 } 64 65 //we also want to check if we've changed global frames because we need to transform our goal pose 66 //15.如果目標點的坐標系和全局地圖的坐標系不相同 67 if(goal.header.frame_id != planner_costmap_ros_->getGlobalFrameID()){ 68 //16,轉換目標點坐標系 69 goal = goalToGlobalFrame(goal); 70 71 //we want to go back to the planning state for the next execution cycle 72 recovery_index_ = 0; 73 state_ = PLANNING; 74 75 //17. 設置目標點並喚醒路徑規划線程 76 lock.lock(); 77 planner_goal_ = goal; 78 runPlanner_ = true; 79 planner_cond_.notify_one(); 80 lock.unlock(); 81 82 //publish the goal point to the visualizer 83 ROS_DEBUG_NAMED("move_base","The global frame for move_base has changed, new frame: %s, new goal position x: %.2f, y: %.2f", goal.header.frame_id.c_str(), goal.pose.position.x, goal.pose.position.y); 84 current_goal_pub_.publish(goal); 85 86 //18.重置規划器相關時間標志位 87 last_valid_control_ = ros::Time::now(); 88 last_valid_plan_ = ros::Time::now(); 89 last_oscillation_reset_ = ros::Time::now(); 90 planning_retries_ = 0; 91 } 92 93 //for timing that gives real time even in simulation 94 ros::WallTime start = ros::WallTime::now(); 95 96 //19. 到達目標點的真正工作,控制機器人進行跟隨 97 bool done = executeCycle(goal, global_plan); 98 99 //20.如果完成任務則返回 100 if(done) 101 return; 102 103 //check if execution of the goal has completed in some way 104 105 ros::WallDuration t_diff = ros::WallTime::now() - start; 106 ROS_DEBUG_NAMED("move_base","Full control cycle time: %.9f\n", t_diff.toSec()); 107 //21. 執行休眠動作 108 r.sleep(); 109 //make sure to sleep for the remainder of our cycle time 110 if(r.cycleTime() > ros::Duration(1 / controller_frequency_) && state_ == CONTROLLING) 111 ROS_WARN("Control loop missed its desired rate of %.4fHz... the loop actually took %.4f seconds", controller_frequency_, r.cycleTime().toSec()); 112 }
其中,done的值即為MoveBase::executeCycle()函數的返回值,這個值非常重要,直接判斷了是否到達目標點;MoveBase::executeCycle()函數是控制機器人進行跟隨的函數(重要!!!),如下:
獲取機器人當前位置:
1 geometry_msgs::PoseStamped global_pose; 2 getRobotPose(global_pose, planner_costmap_ros_); 3 const geometry_msgs::PoseStamped& current_position = global_pose; 4 5 //push the feedback out 6 move_base_msgs::MoveBaseFeedback feedback; 7 feedback.base_position = current_position; 8 as_->publishFeedback(feedback);
其中,getRobotPose()函數如下,需要對准坐標系和時間戳:
1 bool MoveBase::getRobotPose(geometry_msgs::PoseStamped& global_pose, costmap_2d::Costmap2DROS* costmap) 2 { 3 tf2::toMsg(tf2::Transform::getIdentity(), global_pose.pose); 4 geometry_msgs::PoseStamped robot_pose; 5 tf2::toMsg(tf2::Transform::getIdentity(), robot_pose.pose); 6 robot_pose.header.frame_id = robot_base_frame_; 7 robot_pose.header.stamp = ros::Time(); // latest available 8 ros::Time current_time = ros::Time::now(); // save time for checking tf delay later 9 10 // 轉換到統一的全局坐標 11 try 12 { 13 tf_.transform(robot_pose, global_pose, costmap->getGlobalFrameID()); 14 } 15 catch (tf2::LookupException& ex) 16 { 17 ROS_ERROR_THROTTLE(1.0, "No Transform available Error looking up robot pose: %s\n", ex.what()); 18 return false; 19 } 20 catch (tf2::ConnectivityException& ex) 21 { 22 ROS_ERROR_THROTTLE(1.0, "Connectivity Error looking up robot pose: %s\n", ex.what()); 23 return false; 24 } 25 catch (tf2::ExtrapolationException& ex) 26 { 27 ROS_ERROR_THROTTLE(1.0, "Extrapolation Error looking up robot pose: %s\n", ex.what()); 28 return false; 29 } 30 31 // 全局坐標時間戳是否在costmap要求下 32 if (current_time.toSec() - global_pose.header.stamp.toSec() > costmap->getTransformTolerance()) 33 { 34 ROS_WARN_THROTTLE(1.0, "Transform timeout for %s. " \ 35 "Current time: %.4f, pose stamp: %.4f, tolerance: %.4f", costmap->getName().c_str(), 36 current_time.toSec(), global_pose.header.stamp.toSec(), costmap->getTransformTolerance()); 37 return false; 38 } 39 40 return true; 41 }
ok,返回MoveBase::executeCycle()函數繼續看——
判斷當前位置和是否振盪,其中distance函數返回的是兩個位置的直線距離(歐氏距離),recovery_trigger_是枚舉RecoveryTrigger的對象:
1 if(distance(current_position, oscillation_pose_) >= oscillation_distance_) 2 { 3 last_oscillation_reset_ = ros::Time::now(); 4 oscillation_pose_ = current_position; 5 6 //如果上次的恢復是由振盪引起的,重置恢復指數 7 if(recovery_trigger_ == OSCILLATION_R) 8 recovery_index_ = 0; 9 }
1 if(!controller_costmap_ros_->isCurrent()){ 2 ROS_WARN("[%s]:Sensor data is out of date, we're not going to allow commanding of the base for safety",ros::this_node::getName().c_str()); 3 publishZeroVelocity(); 4 return false; 5 }
1 if(new_global_plan_){ 2 //make sure to set the new plan flag to false 3 new_global_plan_ = false; 4 5 ROS_DEBUG_NAMED("move_base","Got a new plan...swap pointers"); 6 7 //do a pointer swap under mutex 8 std::vector<geometry_msgs::PoseStamped>* temp_plan = controller_plan_; 9 10 boost::unique_lock<boost::recursive_mutex> lock(planner_mutex_); 11 controller_plan_ = latest_plan_; 12 latest_plan_ = temp_plan; 13 lock.unlock(); 14 ROS_DEBUG_NAMED("move_base","pointers swapped!"); 15 16 //5. 給控制器設置全局路徑 17 if(!tc_->setPlan(*controller_plan_)){ 18 //ABORT and SHUTDOWN COSTMAPS 19 ROS_ERROR("Failed to pass global plan to the controller, aborting."); 20 resetState(); 21 22 //disable the planner thread 23 lock.lock(); 24 runPlanner_ = false; 25 lock.unlock(); 26 //6.設置動作中斷,返回true 27 as_->setAborted(move_base_msgs::MoveBaseResult(), "Failed to pass global plan to the controller."); 28 return true; 29 } 30 31 //如果全局路徑有效,則不需要recovery 32 if(recovery_trigger_ == PLANNING_R) 33 recovery_index_ = 0; 34 }
其中,tc_是局部規划器的指針,setPlan是TrajectoryPlannerROS的函數(注意!!!跟外部包有關系了!!)
然后判斷move_base狀態,一般默認狀態或者接收到一個有效goal時是PLANNING,在規划出全局路徑后state_會由PLANNING變為CONTROLLING,如果規划失敗則由PLANNING變為CLEARING,架構如下:
1 switch(state_){ 2 case PLANNING: 3 case CONTROLLING: 4 case CLEARING: 5 default: 6 }
其中,詳細介紹一下各個狀態:
1 case PLANNING: 2 { 3 boost::recursive_mutex::scoped_lock lock(planner_mutex_); 4 runPlanner_ = true; 5 planner_cond_.notify_one();//還在PLANNING中則喚醒規划線程讓它干活 6 } 7 ROS_DEBUG_NAMED("move_base","Waiting for plan, in the planning state."); 8 break;
(2)機器人控制狀態,嘗試獲取一個有效的速度命令:
1 case CONTROLLING:
如果到達目標點,重置狀態,設置動作成功,返回true,其中isGoalReached()函數是TrajectoryPlannerROS的函數(注意!!!這里跟外部包有關!!):
1 if(tc_->isGoalReached()){ 2 ROS_DEBUG_NAMED("move_base","Goal reached!"); 3 resetState(); 4 5 //disable the planner thread 6 boost::unique_lock<boost::recursive_mutex> lock(planner_mutex_); 7 runPlanner_ = false; 8 lock.unlock(); 9 10 as_->setSucceeded(move_base_msgs::MoveBaseResult(), "Goal reached."); 11 return true; 12 }
其中,resetState()函數如下,配合上面的看,機器人到達目標點后把move_base狀態設置為PLANNING:
1 void MoveBase::resetState(){ 2 // Disable the planner thread 3 boost::unique_lock<boost::recursive_mutex> lock(planner_mutex_); 4 runPlanner_ = false; 5 lock.unlock(); 6 7 // Reset statemachine 8 state_ = PLANNING; 9 recovery_index_ = 0; 10 recovery_trigger_ = PLANNING_R; 11 publishZeroVelocity(); 12 13 //if we shutdown our costmaps when we're deactivated... we'll do that now 14 if(shutdown_costmaps_){ 15 ROS_DEBUG_NAMED("move_base","Stopping costmaps"); 16 planner_costmap_ros_->stop(); 17 controller_costmap_ros_->stop(); 18 } 19 }
返回去繼續看,如果超過震盪時間,停止機器人,設置清障標志位:
1 if(oscillation_timeout_ > 0.0 && 2 last_oscillation_reset_ + ros::Duration(oscillation_timeout_) < ros::Time::now()) 3 { 4 publishZeroVelocity(); 5 state_ = CLEARING; 6 recovery_trigger_ = OSCILLATION_R; 7 }
1 if(tc_->computeVelocityCommands(cmd_vel)){ 2 ROS_DEBUG_NAMED( "move_base", "Got a valid command from the local planner: %.3lf, %.3lf, %.3lf", 3 cmd_vel.linear.x, cmd_vel.linear.y, cmd_vel.angular.z ); 4 last_valid_control_ = ros::Time::now(); 5 //make sure that we send the velocity command to the base 6 vel_pub_.publish(cmd_vel); 7 if(recovery_trigger_ == CONTROLLING_R) 8 recovery_index_ = 0; 9 }
其中,computeVelocityCommands函數又是TrajectoryPlannerROS的函數(注意!!!這里跟外部包有關!!)。
如果沒有獲取有效速度,則判斷是否超過嘗試時間,如果超時,則停止機器人,進入清障模式:
1 if(ros::Time::now() > attempt_end){ 2 //we'll move into our obstacle clearing mode 3 publishZeroVelocity(); 4 state_ = CLEARING; 5 recovery_trigger_ = CONTROLLING_R; 6 }
如果沒有超時,則再全局規划一個新的路徑:
1 last_valid_plan_ = ros::Time::now(); 2 planning_retries_ = 0; 3 state_ = PLANNING; 4 publishZeroVelocity(); 5 6 //enable the planner thread in case it isn't running on a clock 7 boost::unique_lock<boost::recursive_mutex> lock(planner_mutex_); 8 runPlanner_ = true; 9 planner_cond_.notify_one(); 10 lock.unlock();
(3)機器人清障狀態,規划或者控制失敗,恢復或者進入到清障模式:
1 case CLEARING:
如果有可用恢復器,執行恢復動作,並設置狀態為PLANNING:
1 if(recovery_behavior_enabled_ && recovery_index_ < recovery_behaviors_.size()){ 2 ROS_DEBUG_NAMED("move_base_recovery","Executing behavior %u of %zu", recovery_index_, recovery_behaviors_.size()); 3 recovery_behaviors_[recovery_index_]->runBehavior(); 4 5 //we at least want to give the robot some time to stop oscillating after executing the behavior 6 last_oscillation_reset_ = ros::Time::now(); 7 8 //we'll check if the recovery behavior actually worked 9 ROS_DEBUG_NAMED("move_base_recovery","Going back to planning state"); 10 last_valid_plan_ = ros::Time::now(); 11 planning_retries_ = 0; 12 state_ = PLANNING; 13 14 //update the index of the next recovery behavior that we'll try 15 recovery_index_++; 16 }
其中,recovery_behaviors_類型為std::vector<boost::shared_ptr<nav_core::RecoveryBehavior> >,runBehavior()函數為move_slow_and_clear包里面的函數。(注意!!!鏈接到外部包!!!)。
如果沒有可用恢復器,結束動作,返回true:
1 ROS_DEBUG_NAMED("move_base_recovery","All recovery behaviors have failed, locking the planner and disabling it."); 2 //disable the planner thread 3 boost::unique_lock<boost::recursive_mutex> lock(planner_mutex_); 4 runPlanner_ = false; 5 lock.unlock(); 6 7 ROS_DEBUG_NAMED("move_base_recovery","Something should abort after this."); 8 9 if(recovery_trigger_ == CONTROLLING_R){ 10 ROS_ERROR("Aborting because a valid control could not be found. Even after executing all recovery behaviors"); 11 as_->setAborted(move_base_msgs::MoveBaseResult(), "Failed to find a valid control. Even after executing recovery behaviors."); 12 } 13 else if(recovery_trigger_ == PLANNING_R){ 14 ROS_ERROR("Aborting because a valid plan could not be found. Even after executing all recovery behaviors"); 15 as_->setAborted(move_base_msgs::MoveBaseResult(), "Failed to find a valid plan. Even after executing recovery behaviors."); 16 } 17 else if(recovery_trigger_ == OSCILLATION_R){ 18 ROS_ERROR("Aborting because the robot appears to be oscillating over and over. Even after executing all recovery behaviors"); 19 as_->setAborted(move_base_msgs::MoveBaseResult(), "Robot is oscillating. Even after executing recovery behaviors."); 20 } 21 resetState(); 22 return true;
(4)除了上述狀態以外:
1 default: 2 ROS_ERROR("This case should never be reached, something is wrong, aborting"); 3 resetState(); 4 //disable the planner thread 5 boost::unique_lock<boost::recursive_mutex> lock(planner_mutex_); 6 runPlanner_ = false; 7 lock.unlock(); 8 as_->setAborted(move_base_msgs::MoveBaseResult(), "Reached a case that should not be hit in move_base. This is a bug, please report it."); 9 return true;
ok,MoveBase::executeCycle()函數終於介紹完了,回到回調函數繼續看——
剩下的就是解放線程,反饋給action結果:
1 //22. 喚醒計划線程,以便它可以干凈地退出 2 lock.lock(); 3 runPlanner_ = true; 4 planner_cond_.notify_one(); 5 lock.unlock(); 6 7 //23. 如果節點結束就終止並返回 8 as_->setAborted(move_base_msgs::MoveBaseResult(), "Aborting on the goal because the node has been killed"); 9 return;
ok,MoveBase::executeCb()函數看完了,回到構造函數MoveBase::MoveBase——
觸發模式(三種模式:規划、控制、振盪)設置為“規划中”:
1 recovery_trigger_ = PLANNING_R;
1 std::string global_planner, local_planner; 2 private_nh.param("base_global_planner", global_planner, std::string("navfn/NavfnROS")); 3 private_nh.param("base_local_planner", local_planner, std::string("base_local_planner/TrajectoryPlannerROS")); 4 private_nh.param("global_costmap/robot_base_frame", robot_base_frame_, std::string("base_link")); 5 private_nh.param("global_costmap/global_frame", global_frame_, std::string("map")); 6 private_nh.param("planner_frequency", planner_frequency_, 0.0); 7 private_nh.param("controller_frequency", controller_frequency_, 20.0); 8 private_nh.param("planner_patience", planner_patience_, 5.0); 9 private_nh.param("controller_patience", controller_patience_, 15.0); 10 private_nh.param("max_planning_retries", max_planning_retries_, -1); // disabled by default 11 private_nh.param("oscillation_timeout", oscillation_timeout_, 0.0); 12 private_nh.param("oscillation_distance", oscillation_distance_, 0.5);
為三種規划器(planner_plan_保存最新規划的路徑,傳遞給latest_plan_,然后latest_plan_通過executeCycle中傳給controller_plan_)設置內存緩沖區:
1 planner_plan_ = new std::vector<geometry_msgs::PoseStamped>(); 2 latest_plan_ = new std::vector<geometry_msgs::PoseStamped>(); 3 controller_plan_ = new std::vector<geometry_msgs::PoseStamped>();
設置規划器線程,planner_thread_是boost::thread*類型的指針:
1 planner_thread_ = new boost::thread(boost::bind(&MoveBase::planThread, this));
其中,MoveBase::planThread()函數是planner線程的入口。這個函數需要等待actionlib服務器的cbMoveBase::executeCb來喚醒啟動。主要作用是調用全局路徑規划獲取路徑,同時保證規划的周期性以及規划超時清除goal,如下:
1 void MoveBase::planThread(){ 2 ROS_DEBUG_NAMED("move_base_plan_thread","Starting planner thread..."); 3 ros::NodeHandle n; 4 ros::Timer timer; 5 bool wait_for_wake = false; 6 //1. 創建遞歸鎖 7 boost::unique_lock<boost::recursive_mutex> lock(planner_mutex_); 8 while(n.ok()){ 9 //check if we should run the planner (the mutex is locked) 10 //2.判斷是否阻塞線程 11 while(wait_for_wake || !runPlanner_){ 12 //if we should not be running the planner then suspend this thread 13 ROS_DEBUG_NAMED("move_base_plan_thread","Planner thread is suspending"); 14 //當 std::condition_variable 對象的某個 wait 函數被調用的時候, 15 //它使用 std::unique_lock(通過 std::mutex) 來鎖住當前線程。 16 //當前線程會一直被阻塞,直到另外一個線程在相同的 std::condition_variable 對象上調用了 notification 函數來喚醒當前線程。 17 planner_cond_.wait(lock); 18 wait_for_wake = false; 19 } 20 ros::Time start_time = ros::Time::now(); 21 22 //time to plan! get a copy of the goal and unlock the mutex 23 geometry_msgs::PoseStamped temp_goal = planner_goal_; 24 lock.unlock(); 25 ROS_DEBUG_NAMED("move_base_plan_thread","Planning..."); 26 27 //run planner 28 29 //3. 獲取規划的全局路徑 30 //這里的makePlan作用是獲取機器人的位姿作為起點,然后調用全局規划器的makePlan返回規划路徑,存儲在planner_plan_ 31 planner_plan_->clear(); 32 bool gotPlan = n.ok() && makePlan(temp_goal, *planner_plan_); 33 34 35 //4.如果獲得了plan,則將其賦值給latest_plan_ 36 if(gotPlan){ 37 ROS_DEBUG_NAMED("move_base_plan_thread","Got Plan with %zu points!", planner_plan_->size()); 38 //pointer swap the plans under mutex (the controller will pull from latest_plan_) 39 std::vector<geometry_msgs::PoseStamped>* temp_plan = planner_plan_; 40 41 lock.lock(); 42 planner_plan_ = latest_plan_; 43 latest_plan_ = temp_plan; 44 last_valid_plan_ = ros::Time::now(); 45 planning_retries_ = 0; 46 new_global_plan_ = true; 47 48 ROS_DEBUG_NAMED("move_base_plan_thread","Generated a plan from the base_global_planner"); 49 50 //make sure we only start the controller if we still haven't reached the goal 51 if(runPlanner_) 52 state_ = CONTROLLING; 53 if(planner_frequency_ <= 0) 54 runPlanner_ = false; 55 lock.unlock(); 56 } 57 58 //5. 達到一定條件后停止路徑規划,進入清障模式 59 //如果沒有規划出路徑,並且處於PLANNING狀態,則判斷是否超過最大規划周期或者規划次數 60 //如果是則進入自轉模式,否則應該會等待MoveBase::executeCycle的喚醒再次規划 61 else if(state_==PLANNING){ 62 //僅在MoveBase::executeCb及其調用的MoveBase::executeCycle或者重置狀態時會被設置為PLANNING 63 //一般是剛獲得新目標,或者得到路徑但計算不出下一步控制時重新進行路徑規划 64 ROS_DEBUG_NAMED("move_base_plan_thread","No Plan..."); 65 ros::Time attempt_end = last_valid_plan_ + ros::Duration(planner_patience_); 66 67 //check if we've tried to make a plan for over our time limit or our maximum number of retries 68 //issue #496: we stop planning when one of the conditions is true, but if max_planning_retries_ 69 //is negative (the default), it is just ignored and we have the same behavior as ever 70 lock.lock(); 71 planning_retries_++; 72 if(runPlanner_ && 73 (ros::Time::now() > attempt_end || planning_retries_ > uint32_t(max_planning_retries_))){ 74 //we'll move into our obstacle clearing mode 75 state_ = CLEARING; 76 runPlanner_ = false; // proper solution for issue #523 77 publishZeroVelocity(); 78 recovery_trigger_ = PLANNING_R; 79 } 80 81 lock.unlock(); 82 } 83 84 //take the mutex for the next iteration 85 lock.lock(); 86 87 88 //6.設置睡眠模式 89 //如果還沒到規划周期則定時器睡眠,在定時器中斷中通過planner_cond_喚醒,這里規划周期為0 90 if(planner_frequency_ > 0){ 91 ros::Duration sleep_time = (start_time + ros::Duration(1.0/planner_frequency_)) - ros::Time::now(); 92 if (sleep_time > ros::Duration(0.0)){ 93 wait_for_wake = true; 94 timer = n.createTimer(sleep_time, &MoveBase::wakePlanner, this); 95 } 96 } 97 } 98 }
其中,planner_cond_專門用於開啟路徑規划的線程,在其他地方也經常遇到;這一步中,實現了從latest_plan_到planner_plan_ 的跨越;MoveBase::wakePlanner()函數中用planner_cond_開啟了路徑規划的線程。
ok,MoveBase::planThread()看完了,回到構造函數MoveBase::MoveBase繼續看——
創建發布者,話題名一個是cmd_vel,一個是cunrrent_goal,一個是goal:
1 vel_pub_ = nh.advertise<geometry_msgs::Twist>("cmd_vel", 1); 2 current_goal_pub_ = private_nh.advertise<geometry_msgs::PoseStamped>("current_goal", 0 ); 3 4 ros::NodeHandle action_nh("move_base"); 5 action_goal_pub_ = action_nh.advertise<move_base_msgs::MoveBaseActionGoal>("goal", 1);
1 ros::NodeHandle simple_nh("move_base_simple"); 2 goal_sub_ = simple_nh.subscribe<geometry_msgs::PoseStamped>("goal", 1, boost::bind(&MoveBase::goalCB, this, _1));
其中,我們來看MoveBase::goalCB()函數,傳入的是goal,主要作用是為rviz等提供調用借口,將geometry_msgs::PoseStamped形式的goal轉換成move_base_msgs::MoveBaseActionGoal,再發布到對應類型的goal話題中:
1 void MoveBase::goalCB(const geometry_msgs::PoseStamped::ConstPtr& goal){ 2 ROS_DEBUG_NAMED("move_base","In ROS goal callback, wrapping the PoseStamped in the action message and re-sending to the server."); 3 move_base_msgs::MoveBaseActionGoal action_goal; 4 action_goal.header.stamp = ros::Time::now(); 5 action_goal.goal.target_pose = *goal; 6 7 action_goal_pub_.publish(action_goal); 8 }
ok,MoveBase::goalCB()函數看完了,回到構造函數MoveBase::MoveBase繼續看——
設置costmap參數,技巧是把膨脹層設置為大於機器人的半徑:
1 private_nh.param("local_costmap/inscribed_radius", inscribed_radius_, 0.325); 2 private_nh.param("local_costmap/circumscribed_radius", circumscribed_radius_, 0.46); 3 private_nh.param("clearing_radius", clearing_radius_, circumscribed_radius_); 4 private_nh.param("conservative_reset_dist", conservative_reset_dist_, 3.0); 5 6 private_nh.param("shutdown_costmaps", shutdown_costmaps_, false); 7 private_nh.param("clearing_rotation_allowed", clearing_rotation_allowed_, true); 8 private_nh.param("recovery_behavior_enabled", recovery_behavior_enabled_, true);
設置全局路徑規划器,planner_costmap_ros_是costmap_2d::Costmap2DROS*類型的實例化指針,planner_是boost::shared_ptr<nav_core::BaseGlobalPlanner>類型:
1 planner_costmap_ros_ = new costmap_2d::Costmap2DROS("global_costmap", tf_); 2 planner_costmap_ros_->pause(); 3 try { 4 planner_ = bgp_loader_.createInstance(global_planner); 5 planner_->initialize(bgp_loader_.getName(global_planner), planner_costmap_ros_); 6 } catch (const pluginlib::PluginlibException& ex) { 7 ROS_FATAL("Failed to create the %s planner, are you sure it is properly registered and that the containing library is built? Exception: %s", global_planner.c_str(), ex.what()); 8 exit(1); 9 }
1 controller_costmap_ros_ = new costmap_2d::Costmap2DROS("local_costmap", tf_); 2 controller_costmap_ros_->pause(); 3 try { 4 tc_ = blp_loader_.createInstance(local_planner); 5 ROS_INFO("Created local_planner %s", local_planner.c_str()); 6 tc_->initialize(blp_loader_.getName(local_planner), &tf_, controller_costmap_ros_); 7 } catch (const pluginlib::PluginlibException& ex) { 8 ROS_FATAL("Failed to create the %s planner, are you sure it is properly registered and that the containing library is built? Exception: %s", local_planner.c_str(), ex.what()); 9 exit(1); 10 }
這個時候,tc_就成了局部規划器的實例對象。
開始更新costmap:
1 planner_costmap_ros_->start(); 2 controller_costmap_ros_->start();
全局規划:
1 make_plan_srv_ = private_nh.advertiseService("make_plan", &MoveBase::planService, this);
其中,MoveBase::planService()函數寫了全局規划的策略,以多少距離向外搜索路徑:
1 bool MoveBase::planService(nav_msgs::GetPlan::Request &req, nav_msgs::GetPlan::Response &resp)
獲取起始點,如果沒有起始點,那就獲取當前的全局位置為起始點:
1 geometry_msgs::PoseStamped start; 2 //如果起始點為空,設置global_pose為起始點 3 if(req.start.header.frame_id.empty()) 4 { 5 geometry_msgs::PoseStamped global_pose; 6 if(!getRobotPose(global_pose, planner_costmap_ros_)){ 7 ROS_ERROR("move_base cannot make a plan for you because it could not get the start pose of the robot"); 8 return false; 9 } 10 start = global_pose; 11 } 12 else 13 { 14 start = req.start; 15 }
制定規划策略:
1 std::vector<geometry_msgs::PoseStamped> global_plan; 2 if(!planner_->makePlan(start, req.goal, global_plan) || global_plan.empty()){ 3 ROS_DEBUG_NAMED("move_base","Failed to find a plan to exact goal of (%.2f, %.2f), searching for a feasible goal within tolerance", 4 req.goal.pose.position.x, req.goal.pose.position.y); 5 6 //在規定的公差范圍內向外尋找可行的goal 7 geometry_msgs::PoseStamped p; 8 p = req.goal; 9 bool found_legal = false; 10 float resolution = planner_costmap_ros_->getCostmap()->getResolution(); 11 float search_increment = resolution*3.0;//以3倍分辨率的增量向外尋找 12 if(req.tolerance > 0.0 && req.tolerance < search_increment) search_increment = req.tolerance; 13 for(float max_offset = search_increment; max_offset <= req.tolerance && !found_legal; max_offset += search_increment) { 14 for(float y_offset = 0; y_offset <= max_offset && !found_legal; y_offset += search_increment) { 15 for(float x_offset = 0; x_offset <= max_offset && !found_legal; x_offset += search_increment) { 16 17 //不在本位置的外側layer查找,太近的不找 18 if(x_offset < max_offset-1e-9 && y_offset < max_offset-1e-9) continue; 19 20 //從兩個方向x、y查找精確的goal 21 for(float y_mult = -1.0; y_mult <= 1.0 + 1e-9 && !found_legal; y_mult += 2.0) { 22 23 //第一次遍歷如果偏移量過小,則去除這個點或者上一點 24 if(y_offset < 1e-9 && y_mult < -1.0 + 1e-9) continue; 25 26 for(float x_mult = -1.0; x_mult <= 1.0 + 1e-9 && !found_legal; x_mult += 2.0) { 27 if(x_offset < 1e-9 && x_mult < -1.0 + 1e-9) continue; 28 29 p.pose.position.y = req.goal.pose.position.y + y_offset * y_mult; 30 p.pose.position.x = req.goal.pose.position.x + x_offset * x_mult; 31 32 if(planner_->makePlan(start, p, global_plan)){ 33 if(!global_plan.empty()){ 34 35 //adding the (unreachable) original goal to the end of the global plan, in case the local planner can get you there 36 //(the reachable goal should have been added by the global planner) 37 global_plan.push_back(req.goal); 38 39 found_legal = true; 40 ROS_DEBUG_NAMED("move_base", "Found a plan to point (%.2f, %.2f)", p.pose.position.x, p.pose.position.y); 41 break; 42 } 43 } 44 else{ 45 ROS_DEBUG_NAMED("move_base","Failed to find a plan to point (%.2f, %.2f)", p.pose.position.x, p.pose.position.y); 46 } 47 } 48 } 49 } 50 } 51 } 52 }
然后把規划后的global_plan附給resp,並且傳出去:
1 resp.plan.poses.resize(global_plan.size()); 2 for(unsigned int i = 0; i < global_plan.size(); ++i){ 3 resp.plan.poses[i] = global_plan[i]; 4 }
ok,MoveBase::planService()函數看完了,回到構造函數MoveBase::MoveBase繼續看——
開始清除地圖服務:
1 clear_costmaps_srv_ = private_nh.advertiseService("clear_costmaps", &MoveBase::clearCostmapsService, this);
其中,調用了MoveBase::clearCostmapsService()函數,提供清除一次costmap的功能:
1 bool MoveBase::clearCostmapsService(std_srvs::Empty::Request &req, std_srvs::Empty::Response &resp){ 2 //clear the costmaps 3 boost::unique_lock<costmap_2d::Costmap2D::mutex_t> lock_controller(*(controller_costmap_ros_->getCostmap()->getMutex())); 4 controller_costmap_ros_->resetLayers(); 5 6 boost::unique_lock<costmap_2d::Costmap2D::mutex_t> lock_planner(*(planner_costmap_ros_->getCostmap()->getMutex())); 7 planner_costmap_ros_->resetLayers(); 8 return true; 9 }
值得注意的是,其中有一個函數resetLayers(),調用的是costmap包(注意!!外部鏈接!!!),該函數的功能是重置地圖,內部包括重置總地圖、重置地圖各層:
1 void Costmap2DROS::resetLayers() 2 { 3 Costmap2D* top = layered_costmap_->getCostmap(); 4 top->resetMap(0, 0, top->getSizeInCellsX(), top->getSizeInCellsY()); 5 std::vector < boost::shared_ptr<Layer> > *plugins = layered_costmap_->getPlugins(); 6 for (vector<boost::shared_ptr<Layer> >::iterator plugin = plugins->begin(); plugin != plugins->end(); 7 ++plugin) 8 { 9 (*plugin)->reset(); 10 } 11 }
在該函數中,首先將總的地圖信息重置為缺省值。然后調用各層的reset函數對各層進行重置(第9行)。針對不同的reset函數,功能如下:
1 void StaticLayer::reset() 2 { 3 if (first_map_only_) 4 { 5 has_updated_data_ = true; 6 } 7 else 8 { 9 onInitialize(); 10 } 11 }
1 void ObstacleLayer::reset() 2 { 3 deactivate(); 4 resetMaps(); 5 current_ = true; 6 activate(); 7 }
ok,MoveBase::clearCostmapsService()函數看完了,回到構造函數MoveBase::MoveBase繼續看——
如果不小心關閉了costmap, 則停用:
1 if(shutdown_costmaps_){ 2 ROS_DEBUG_NAMED("move_base","Stopping costmaps initially"); 3 planner_costmap_ros_->stop(); 4 controller_costmap_ros_->stop(); 5 }
加載指定的恢復器,加載不出來則使用默認的,這里包括了找不到路自轉360:
1 if(!loadRecoveryBehaviors(private_nh)){ 2 loadDefaultRecoveryBehaviors(); 3 }
導航過程基本結束,把狀態初始化:
1 //initially, we'll need to make a plan 2 state_ = PLANNING; 3 //we'll start executing recovery behaviors at the beginning of our list 4 recovery_index_ = 0; 5 //10.開啟move_base動作器 6 as_->start();
啟動動態參數服務器:
1 dsrv_ = new dynamic_reconfigure::Server<move_base::MoveBaseConfig>(ros::NodeHandle("~")); 2 dynamic_reconfigure::Server<move_base::MoveBaseConfig>::CallbackType cb = boost::bind(&MoveBase::reconfigureCB, this, _1, _2); 3 dsrv_->setCallback(cb);
其中,回調函數MoveBase::reconfigureCB(),配置了各種參數,感興趣的可以看下,代碼如下:
1 void MoveBase::reconfigureCB(move_base::MoveBaseConfig &config, uint32_t level){ 2 boost::recursive_mutex::scoped_lock l(configuration_mutex_); 3 4 //一旦被調用,我們要確保有原始配置 5 if(!setup_) 6 { 7 last_config_ = config; 8 default_config_ = config; 9 setup_ = true; 10 return; 11 } 12 13 if(config.restore_defaults) { 14 config = default_config_; 15 //如果有人在參數服務器上設置默認值,要防止循環 16 config.restore_defaults = false; 17 } 18 19 if(planner_frequency_ != config.planner_frequency) 20 { 21 planner_frequency_ = config.planner_frequency; 22 p_freq_change_ = true; 23 } 24 25 if(controller_frequency_ != config.controller_frequency) 26 { 27 controller_frequency_ = config.controller_frequency; 28 c_freq_change_ = true; 29 } 30 31 planner_patience_ = config.planner_patience; 32 controller_patience_ = config.controller_patience; 33 max_planning_retries_ = config.max_planning_retries; 34 conservative_reset_dist_ = config.conservative_reset_dist; 35 36 recovery_behavior_enabled_ = config.recovery_behavior_enabled; 37 clearing_rotation_allowed_ = config.clearing_rotation_allowed; 38 shutdown_costmaps_ = config.shutdown_costmaps; 39 40 oscillation_timeout_ = config.oscillation_timeout; 41 oscillation_distance_ = config.oscillation_distance; 42 if(config.base_global_planner != last_config_.base_global_planner) { 43 boost::shared_ptr<nav_core::BaseGlobalPlanner> old_planner = planner_; 44 //創建全局規划 45 ROS_INFO("Loading global planner %s", config.base_global_planner.c_str()); 46 try { 47 planner_ = bgp_loader_.createInstance(config.base_global_planner); 48 49 // 等待當前規划結束 50 boost::unique_lock<boost::recursive_mutex> lock(planner_mutex_); 51 52 // 在新規划開始前clear舊的 53 planner_plan_->clear(); 54 latest_plan_->clear(); 55 controller_plan_->clear(); 56 resetState(); 57 planner_->initialize(bgp_loader_.getName(config.base_global_planner), planner_costmap_ros_); 58 59 lock.unlock(); 60 } catch (const pluginlib::PluginlibException& ex) { 61 ROS_FATAL("Failed to create the %s planner, are you sure it is properly registered and that the \ 62 containing library is built? Exception: %s", config.base_global_planner.c_str(), ex.what()); 63 planner_ = old_planner; 64 config.base_global_planner = last_config_.base_global_planner; 65 } 66 } 67 68 if(config.base_local_planner != last_config_.base_local_planner){ 69 boost::shared_ptr<nav_core::BaseLocalPlanner> old_planner = tc_; 70 //創建局部規划 71 try { 72 tc_ = blp_loader_.createInstance(config.base_local_planner); 73 // 清理舊的 74 planner_plan_->clear(); 75 latest_plan_->clear(); 76 controller_plan_->clear(); 77 resetState(); 78 tc_->initialize(blp_loader_.getName(config.base_local_planner), &tf_, controller_costmap_ros_); 79 } catch (const pluginlib::PluginlibException& ex) { 80 ROS_FATAL("Failed to create the %s planner, are you sure it is properly registered and that the \ 81 containing library is built? Exception: %s", config.base_local_planner.c_str(), ex.what()); 82 tc_ = old_planner; 83 config.base_local_planner = last_config_.base_local_planner; 84 } 85 } 86 87 last_config_ = config; 88 }
ok,構造函數MoveBase::MoveBase看完了,接着到析構函數,釋放了內存——
1 MoveBase::~MoveBase(){ 2 recovery_behaviors_.clear(); 3 4 delete dsrv_; 5 6 if(as_ != NULL) 7 delete as_; 8 9 if(planner_costmap_ros_ != NULL) 10 delete planner_costmap_ros_; 11 12 if(controller_costmap_ros_ != NULL) 13 delete controller_costmap_ros_; 14 15 planner_thread_->interrupt(); 16 planner_thread_->join(); 17 18 delete planner_thread_; 19 20 delete planner_plan_; 21 delete latest_plan_; 22 delete controller_plan_; 23 24 planner_.reset(); 25 tc_.reset(); 26 }
至此,move_base全部解析完畢,恭喜你有耐心看完,如果有錯誤的地方歡迎指出,在此感謝!
五、需要注意的地方
1、move_base與costmap_2d、全局規划器、局部規划器、恢復器緊密相連,很多地方都是調用了這幾個包的函數。
2、需要着重關注 頭文件 中寫的那些枚舉常量,以及各個標志位,很多都是正文中的判斷條件。
3、多線程、智能指針有必要深入學一下,對內存和任務管理很有幫助。