move_base
navigation的系統組成如下,move_base的地位很重要。
MoveBase的構造函數中,使用了不同的NodeHandle
,如上圖看到訂閱的是move_base_simaple/goal
是使用simaple_nh
之後分辨節點名稱之下的topic
。訂閱和發佈的消息:
ros::NodeHandle nh;
vel_pub_ = nh.advertise<geometry_msgs::Twist>("cmd_vel", 1);
current_goal_pub_ = private_nh.advertise<geometry_msgs::PoseStamped>("current_goal", 0 );
ros::NodeHandle action_nh("move_base");
action_goal_pub_ = action_nh.advertise<move_base_msgs::MoveBaseActionGoal>("goal", 1);
ros::NodeHandle simple_nh("move_base_simple");
goal_sub_ = simple_nh.subscribe<geometry_msgs::PoseStamped>("goal", 1, boost::bind(&MoveBase::goalCB, this, _1));
系統狀態根據兩個變量表示:{state_, recovery_trigger_}
enum MoveBaseState{PLANNING, CONTROLLING, CLEARING};
enum RecoveryTrigger{PLANNING_R, CONTROLLING_R,OSCILLATION_R};
初始化了用來存儲全局規劃的路徑序列,最終會把全局規劃得到的最新路徑latest_plan_
賦值給controller_plan_
傳遞給局部規劃器。
//set up plan triple buffer
planner_plan_ = new std::vector<geometry_msgs::PoseStamped>();
latest_plan_ = new std::vector<geometry_msgs::PoseStamped>();
controller_plan_ = new std::vector<geometry_msgs::PoseStamped>();
開啓costmap
的更新。
// Start actively updating costmaps based on sensor data
planner_costmap_ros_->start();
controller_costmap_ros_->start();
global_planner
在構造函數中同樣加載了global_planner
的類,以及執行全局規劃的線程。
planner_thread_ = new boost::thread(boost::bind(&MoveBase::planThread, this));
規劃線程在一個while
的循環中,需要保證wait_for_wake==false,runPlanner_==true
,同時planner_cond_
沒有被lock
的情況下通過調用makePlan
完成全局路徑的規劃。因此可以看出,在MoveBase::executeCb
中可以根據系統的狀態改變標誌位的值,控制規劃線程。在這裏有個planner_frequency_
的參數,用來表示規劃的週期,如果規劃用時少於規劃週期,則timer=n.createTime(sleep_time,$MoveBase::wakePlanner,this);
通過使用ROS
定時器來喚醒規劃線程。
MoveBaseActionServer
as_
維護MoveBase
的MoveBaseActionServer
狀態機,並且新建了一個executeCb
回調線程。
as_=new MoveBaseActionServer(ros::NodeHandle(),"move_base",boost::bind(&MoveBase::executeCb,this,_1),false);
先檢查函數參數目標輸入的合法性,主要是pose.orientation
檢查。然後調用geometry_msgs::PoseStamped goal = goalToGlobalFrame(move_base_goal->target_pose)
將goal
轉換爲在全局地圖下的goal
。通過賦值planner_goal_=goal
將目標點傳入planThread
, 然後開啓planThread:runPlanner_=true; planner_cond_.notify_one()
。
然後進入while(n.ok())
循環:首先判斷as_
是否是可搶佔的,以及檢查是否有新的goal
,如果有則處理新的goal
。繼續往下,判斷goal
的座標系和planner_costmap_ros_
座標系是否是一致的,否則調用goal = goalToGlobalFrame(goal);
轉換到planner_costmap_ros_
的座標系下,然後重新開啓planThread
。最終,我們拿到了全局規劃的路徑latest_plan_
, 接下來調用核心代碼:
首先會鎖住MoveBase::reconfigureCB(……)
,然後會處理震盪問題。以及controller_costmap_ros_
是否是最新的局部規劃地圖,然後通過new_global_plan_
判斷是否全局規劃得到可以執行的全局路徑(會通過標誌位runPlanner_=false;等使得
planThread暫時不再執行
makePlan`)
然後通過系統狀態判斷執行的代碼,如果正在規劃則繼續規劃……。 如果系統狀態處於CONTROLLING
狀態,首先檢查是否到達目的地if(tc_->isGoalReached())
,如果到達目標點,則resetState()
, 並暫停planThread
: runPlanner_ = false
; 如果滿足震盪條件,則:機器人運動停止publishZeroVelocity()
;系統狀態轉入state_ = CLEARING; recovery_trigger_ = OSCILLATION_R;
系統的速度發佈計算:tc_->computeVelocityCommands(cmd_vel)
//check to see if we've reached our goal
if(tc_->isGoalReached()){
ROS_DEBUG_NAMED("move_base","Goal reached!");
resetState();
//disable the planner thread
boost::unique_lock<boost::recursive_mutex> lock(planner_mutex_);
runPlanner_ = false;
lock.unlock();
as_->setSucceeded(move_base_msgs::MoveBaseResult(), "Goal reached.");
return true;
}
//check for an oscillation condition
if(oscillation_timeout_ > 0.0 &&
last_oscillation_reset_ + ros::Duration(oscillation_timeout_) < ros::Time::now())
{
publishZeroVelocity();
state_ = CLEARING;
recovery_trigger_ = OSCILLATION_R;
}
{
boost::unique_lock<costmap_2d::Costmap2D::mutex_t> lock(*(controller_costmap_ros_->getCostmap()->getMutex()));
if(tc_->computeVelocityCommands(cmd_vel)){
ROS_DEBUG_NAMED( "move_base", "Got a valid command from the local planner: %.3lf, %.3lf, %.3lf",
cmd_vel.linear.x, cmd_vel.linear.y, cmd_vel.angular.z );
last_valid_control_ = ros::Time::now();
//make sure that we send the velocity command to the base
vel_pub_.publish(cmd_vel);
if(recovery_trigger_ == CONTROLLING_R)
recovery_index_ = 0;
}
else {
ROS_DEBUG_NAMED("move_base", "The local planner could not find a valid plan.");
ros::Time attempt_end = last_valid_control_ + ros::Duration(controller_patience_);
//check if we've tried to find a valid control for longer than our time limit
if(ros::Time::now() > attempt_end){
//we'll move into our obstacle clearing mode
publishZeroVelocity();
state_ = CLEARING;
recovery_trigger_ = CONTROLLING_R;
}
else{
//otherwise, if we can't find a valid control, we'll go back to planning
last_valid_plan_ = ros::Time::now();
planning_retries_ = 0;
state_ = PLANNING;
publishZeroVelocity();
//enable the planner thread in case it isn't running on a clock
boost::unique_lock<boost::recursive_mutex> lock(planner_mutex_);
runPlanner_ = true;
planner_cond_.notify_one();
lock.unlock();
}
}
}