针对移动机器人导航navigation中的move_base部分,详细剖析其实现过程。
旁边专栏还在更新ROS-Navigation 包其他源码分析,便于继续深入了解
目录
- 前言
- 1. 相关源码文件
- 2. 源码剖析
- 2.1 move_base类内部结构
- (1)reconfigureCB()
- (2)goalCB()将 goal 转换为 move_base_msgs::MoveBaseActionGoal 类型
- (3)clearCostmapWindows()清空机器人周围的正方形空间
- (4)clearCostmapsService()将 costmap 重置为 unknow
- (5)planService()服务 make_plan 的回调函数。接受 Service 的消息后,开始路径规划,得到 global_plan 的路径,跟 as_ 得到的 action 一样,都会 make_plan ,但是不做局部路径规划(只是提供了个全局路径规划的接口)
- (6)~MoveBase()析构函数
- (7)makePlan()获取机器人的位姿作为起点,然后调用全局规划器的 makePlan 返回规划路径,存储在 planner_plan_
- (8)publishZeroVelocity()发布速度为 0 的 cmd_vel
- (9)isQuaternionValid()验证是否是四元数
- (10)goalToGlobalFrame()将 goal 转化为 geometry_msgs::PoseStamped 格式的 goal_pose_msg
前言
本文在移动机器人导航navigation中的move_base功能包深度剖析(1): 点这里.的基础上,继续剖析move_base功能包实现过程。附move_base功能包下载地址:点这里。
提示:以下是本篇文章正文内容
1. 相关源码文件
1.1 主要头文件(move_base/include)
- move_base.h
1.2 主要源代码文件(move_base/src)
- move_base_node.cpp
- move_base.cpp
2. 源码剖析
2.1 move_base类内部结构
(1)reconfigureCB()
void MoveBase::reconfigureCB(move_base::MoveBaseConfig &config, uint32_t level){
boost::recursive_mutex::scoped_lock l(configuration_mutex_);
//The first time we're called, we just want to make sure we have the
//original configuration
if(!setup_)
{
last_config_ = config;
default_config_ = config;
setup_ = true;
return;
}
if(config.restore_defaults) {
config = default_config_;
//if someone sets restore defaults on the parameter server, prevent looping
config.restore_defaults = false;
}
if(planner_frequency_ != config.planner_frequency)
{
planner_frequency_ = config.planner_frequency;
p_freq_change_ = true;
}
if(controller_frequency_ != config.controller_frequency)
{
controller_frequency_ = config.controller_frequency;
c_freq_change_ = true;
}
planner_patience_ = config.planner_patience;
controller_patience_ = config.controller_patience;
max_planning_retries_ = config.max_planning_retries;
conservative_reset_dist_ = config.conservative_reset_dist;
recovery_behavior_enabled_ = config.recovery_behavior_enabled;
clearing_rotation_allowed_ = config.clearing_rotation_allowed;
shutdown_costmaps_ = config.shutdown_costmaps;
oscillation_timeout_ = config.oscillation_timeout;
oscillation_distance_ = config.oscillation_distance;
if(config.base_global_planner != last_config_.base_global_planner) {
boost::shared_ptr<nav_core::BaseGlobalPlanner> old_planner = planner_;
//initialize the global planner
ROS_INFO("Loading global planner %s", config.base_global_planner.c_str());
try {
planner_ = bgp_loader_.createInstance(config.base_global_planner);
// wait for the current planner to finish planning
boost::unique_lock<boost::recursive_mutex> lock(planner_mutex_);
// Clean up before initializing the new planner
planner_plan_->clear();
latest_plan_->clear();
controller_plan_->clear();
resetState();
planner_->initialize(bgp_loader_.getName(config.base_global_planner), planner_costmap_ros_);
lock.unlock();
} catch (const pluginlib::PluginlibException& ex) {
ROS_FATAL("Failed to create the %s planner, are you sure it is properly registered and that the \
containing library is built? Exception: %s", config.base_global_planner.c_str(), ex.what());
planner_ = old_planner;
config.base_global_planner = last_config_.base_global_planner;
}
}
if(config.base_local_planner != last_config_.base_local_planner){
boost::shared_ptr<nav_core::BaseLocalPlanner> old_planner = tc_;
//create a local planner
try {
tc_ = blp_loader_.createInstance(config.base_local_planner);
// Clean up before initializing the new planner
planner_plan_->clear();
latest_plan_->clear();
controller_plan_->clear();
resetState();
tc_->initialize(blp_loader_.getName(config.base_local_planner), &tf_, controller_costmap_ros_);
} catch (const pluginlib::PluginlibException& ex) {
ROS_FATAL("Failed to create the %s planner, are you sure it is properly registered and that the \
containing library is built? Exception: %s", config.base_local_planner.c_str(), ex.what());
tc_ = old_planner;
config.base_local_planner = last_config_.base_local_planner;
}
}
last_config_ = config;
}
(2)goalCB()将 goal 转换为 move_base_msgs::MoveBaseActionGoal 类型
void MoveBase::goalCB(const geometry_msgs::PoseStamped::ConstPtr& goal){
ROS_DEBUG_NAMED("move_base","In ROS goal callback, wrapping the PoseStamped in the action message and re-sending to the server.");
move_base_msgs::MoveBaseActionGoal action_goal;
action_goal.header.stamp = ros::Time::now();
action_goal.goal.target_pose = *goal;
action_goal_pub_.publish(action_goal);
}
(3)clearCostmapWindows()清空机器人周围的正方形空间
void MoveBase::clearCostmapWindows(double size_x, double size_y){
tf::Stamped<tf::Pose> global_pose;
//clear the planner's costmap
planner_costmap_ros_->getRobotPose(global_pose);
std::vector<geometry_msgs::Point> clear_poly;
double x = global_pose.getOrigin().x();
double y = global_pose.getOrigin().y();
geometry_msgs::Point pt;
pt.x = x - size_x / 2;
pt.y = y - size_y / 2;
clear_poly.push_back(pt);
pt.x = x + size_x / 2;
pt.y = y - size_y / 2;
clear_poly.push_back(pt);
pt.x = x + size_x / 2;
pt.y = y + size_y / 2;
clear_poly.push_back(pt);
pt.x = x - size_x / 2;
pt.y = y + size_y / 2;
clear_poly.push_back(pt);
planner_costmap_ros_->getCostmap()->setConvexPolygonCost(clear_poly, costmap_2d::FREE_SPACE);
//clear the controller's costmap
controller_costmap_ros_->getRobotPose(global_pose);
clear_poly.clear();
x = global_pose.getOrigin().x();
y = global_pose.getOrigin().y();
pt.x = x - size_x / 2;
pt.y = y - size_y / 2;
clear_poly.push_back(pt);
pt.x = x + size_x / 2;
pt.y = y - size_y / 2;
clear_poly.push_back(pt);
pt.x = x + size_x / 2;
pt.y = y + size_y / 2;
clear_poly.push_back(pt);
pt.x = x - size_x / 2;
pt.y = y + size_y / 2;
clear_poly.push_back(pt);
controller_costmap_ros_->getCostmap()->setConvexPolygonCost(clear_poly, costmap_2d::FREE_SPACE);
}
(4)clearCostmapsService()将 costmap 重置为 unknow
bool MoveBase::clearCostmapsService(std_srvs::Empty::Request &req, std_srvs::Empty::Response &resp){
//clear the costmaps
boost::unique_lock<costmap_2d::Costmap2D::mutex_t> lock_controller(*(controller_costmap_ros_->getCostmap()->getMutex()));
controller_costmap_ros_->resetLayers();
boost::unique_lock<costmap_2d::Costmap2D::mutex_t> lock_planner(*(planner_costmap_ros_->getCostmap()->getMutex()));
planner_costmap_ros_->resetLayers();
return true;
}
(5)planService()服务 make_plan 的回调函数。接受 Service 的消息后,开始路径规划,得到 global_plan 的路径,跟 as_ 得到的 action 一样,都会 make_plan ,但是不做局部路径规划(只是提供了个全局路径规划的接口)
bool MoveBase::planService(nav_msgs::GetPlan::Request &req, nav_msgs::GetPlan::Response &resp){
if(as_->isActive()){
ROS_ERROR("move_base must be in an inactive state to make a plan for an external user");
return false;
}
//make sure we have a costmap for our planner
if(planner_costmap_ros_ == NULL){
ROS_ERROR("move_base cannot make a plan for you because it doesn't have a costmap");
return false;
}
geometry_msgs::PoseStamped start;
//if the user does not specify a start pose, identified by an empty frame id, then use the robot's pose
if(req.start.header.frame_id.empty())
{
tf::Stamped<tf::Pose> global_pose;
if(!planner_costmap_ros_->getRobotPose(global_pose)){
ROS_ERROR("move_base cannot make a plan for you because it could not get the start pose of the robot");
return false;
}
tf::poseStampedTFToMsg(global_pose, start);
}
else
{
start = req.start;
}
//update the copy of the costmap the planner uses
clearCostmapWindows(2 * clearing_radius_, 2 * clearing_radius_);
//first try to make a plan to the exact desired goal
std::vector<geometry_msgs::PoseStamped> global_plan;
if(!planner_->makePlan(start, req.goal, global_plan) || global_plan.empty()){
ROS_DEBUG_NAMED("move_base","Failed to find a plan to exact goal of (%.2f, %.2f), searching for a feasible goal within tolerance",
req.goal.pose.position.x, req.goal.pose.position.y);
//search outwards for a feasible goal within the specified tolerance
geometry_msgs::PoseStamped p;
p = req.goal;
bool found_legal = false;
float resolution = planner_costmap_ros_->getCostmap()->getResolution();
float search_increment = resolution*3.0;
if(req.tolerance > 0.0 && req.tolerance < search_increment) search_increment = req.tolerance;
for(float max_offset = search_increment; max_offset <= req.tolerance && !found_legal; max_offset += search_increment) {
for(float y_offset = 0; y_offset <= max_offset && !found_legal; y_offset += search_increment) {
for(float x_offset = 0; x_offset <= max_offset && !found_legal; x_offset += search_increment) {
//don't search again inside the current outer layer
if(x_offset < max_offset-1e-9 && y_offset < max_offset-1e-9) continue;
//search to both sides of the desired goal
for(float y_mult = -1.0; y_mult <= 1.0 + 1e-9 && !found_legal; y_mult += 2.0) {
//if one of the offsets is 0, -1*0 is still 0 (so get rid of one of the two)
if(y_offset < 1e-9 && y_mult < -1.0 + 1e-9) continue;
for(float x_mult = -1.0; x_mult <= 1.0 + 1e-9 && !found_legal; x_mult += 2.0) {
if(x_offset < 1e-9 && x_mult < -1.0 + 1e-9) continue;
p.pose.position.y = req.goal.pose.position.y + y_offset * y_mult;
p.pose.position.x = req.goal.pose.position.x + x_offset * x_mult;
if(planner_->makePlan(start, p, global_plan)){
if(!global_plan.empty()){
//adding the (unreachable) original goal to the end of the global plan, in case the local planner can get you there
//(the reachable goal should have been added by the global planner)
global_plan.push_back(req.goal);
found_legal = true;
ROS_DEBUG_NAMED("move_base", "Found a plan to point (%.2f, %.2f)", p.pose.position.x, p.pose.position.y);
break;
}
}
else{
ROS_DEBUG_NAMED("move_base","Failed to find a plan to point (%.2f, %.2f)", p.pose.position.x, p.pose.position.y);
}
}
}
}
}
}
}
//copy the plan into a message to send out
resp.plan.poses.resize(global_plan.size());
for(unsigned int i = 0; i < global_plan.size(); ++i){
resp.plan.poses[i] = global_plan[i];
}
return true;
}
(6)~MoveBase()析构函数
MoveBase::~MoveBase(){
recovery_behaviors_.clear();
delete dsrv_;
if(as_ != NULL)
delete as_;
if(planner_costmap_ros_ != NULL)
delete planner_costmap_ros_;
if(controller_costmap_ros_ != NULL)
delete controller_costmap_ros_;
planner_thread_->interrupt();
planner_thread_->join();
delete planner_thread_;
delete planner_plan_;
delete latest_plan_;
delete controller_plan_;
planner_.reset();
tc_.reset();
}
(7)makePlan()获取机器人的位姿作为起点,然后调用全局规划器的 makePlan 返回规划路径,存储在 planner_plan_
bool MoveBase::makePlan(const geometry_msgs::PoseStamped& goal, std::vector<geometry_msgs::PoseStamped>& plan){
boost::unique_lock<costmap_2d::Costmap2D::mutex_t> lock(*(planner_costmap_ros_->getCostmap()->getMutex()));
//make sure to set the plan to be empty initially
plan.clear();
//since this gets called on handle activate
if(planner_costmap_ros_ == NULL) {
ROS_ERROR("Planner costmap ROS is NULL, unable to create global plan");
return false;
}
//get the starting pose of the robot
tf::Stamped<tf::Pose> global_pose;
if(!planner_costmap_ros_->getRobotPose(global_pose)) {
ROS_WARN("Unable to get starting pose of robot, unable to create global plan");
return false;
}
geometry_msgs::PoseStamped start;
tf::poseStampedTFToMsg(global_pose, start);
//if the planner fails or returns a zero length plan, planning failed
if(!planner_->makePlan(start, goal, plan) || plan.empty()){
ROS_DEBUG_NAMED("move_base","Failed to find a plan to point (%.2f, %.2f)", goal.pose.position.x, goal.pose.position.y);
return false;
}
return true;
}
(8)publishZeroVelocity()发布速度为 0 的 cmd_vel
void MoveBase::publishZeroVelocity(){
geometry_msgs::Twist cmd_vel;
cmd_vel.linear.x = 0.0;
cmd_vel.linear.y = 0.0;
cmd_vel.angular.z = 0.0;
vel_pub_.publish(cmd_vel);
}
(9)isQuaternionValid()验证是否是四元数
bool MoveBase::isQuaternionValid(const geometry_msgs::Quaternion& q){
//first we need to check if the quaternion has nan's or infs
if(!std::isfinite(q.x) || !std::isfinite(q.y) || !std::isfinite(q.z) || !std::isfinite(q.w)){
ROS_ERROR("Quaternion has nans or infs... discarding as a navigation goal");
return false;
}
tf::Quaternion tf_q(q.x, q.y, q.z, q.w);
//next, we need to check if the length of the quaternion is close to zero
if(tf_q.length2() < 1e-6){
ROS_ERROR("Quaternion has length close to zero... discarding as navigation goal");
return false;
}
//next, we'll normalize the quaternion and check that it transforms the vertical vector correctly
tf_q.normalize();
tf::Vector3 up(0, 0, 1);
double dot = up.dot(up.rotate(tf_q.getAxis(), tf_q.getAngle()));
if(fabs(dot - 1) > 1e-3){
ROS_ERROR("Quaternion is invalid... for navigation the z-axis of the quaternion must be close to vertical.");
return false;
}
return true;
}
(10)goalToGlobalFrame()将 goal 转化为 geometry_msgs::PoseStamped 格式的 goal_pose_msg
geometry_msgs::PoseStamped MoveBase::goalToGlobalFrame(const geometry_msgs::PoseStamped& goal_pose_msg){
std::string global_frame = planner_costmap_ros_->getGlobalFrameID();
tf::Stamped<tf::Pose> goal_pose, global_pose;
poseStampedMsgToTF(goal_pose_msg, goal_pose);
//just get the latest available transform... for accuracy they should send
//goals in the frame of the planner
goal_pose.stamp_ = ros::Time();
try{
tf_.transformPose(global_frame, goal_pose, global_pose);
}
catch(tf::TransformException& ex){
ROS_WARN("Failed to transform the goal pose from %s into the %s frame: %s",
goal_pose.frame_id_.c_str(), global_frame.c_str(), ex.what());
return goal_pose_msg;
}
geometry_msgs::PoseStamped global_pose_msg;
tf::poseStampedTFToMsg(global_pose, global_pose_msg);
return global_pose_msg;
}
参考:
https://github.com/ros-planning/navigation/tree/kinetic-devel/move_base
move_base部分内容就是如上所述,内容较多,可以收藏一下,方便以后查看噢~~~