pathPlanner.cpp
Go to the documentation of this file.
00001 
00008 #include <youbot_overhead_localization/pathPlanner.h>
00009 
00010 using namespace std;
00011 using namespace costmap_2d;
00012 using namespace navfn;
00013 
00014 pathPlanner::pathPlanner(std::string name)
00015   :as(n, name, boost::bind(&pathPlanner::executePathPlanning, this, _1), false), actionName(name), cmObj(NULL), navFnObj(NULL)
00016 {
00017   ROS_INFO("Starting pathPlanner");
00018   string moveCmdTopicName;
00019 
00020   cmInitialized = false;
00021   navFnInitialized = false;
00022 
00023   n.getParam("/planner_map_server/map_file_location", mapFileName);
00024   n.getParam("/planner_map_server/map_save_debug_location", mapDebugLocation);
00025   if(!n.getParam("/path_planner/moveCommandTopic", moveCmdTopicName))
00026     baseCommandPublisher = n.advertise<geometry_msgs::Twist>("/cmd_vel", -1);
00027   else
00028     baseCommandPublisher = n.advertise<geometry_msgs::Twist>(moveCmdTopicName, -1);
00029   //initialize publishers and subscribers
00030   mapSubscriber = n.subscribe("/map", 0, &pathPlanner::mapserverCallback, this);
00031   localizationSubscriber = n.subscribe("base_localizer/pose_2d", 1, &pathPlanner::localizationCallback, this);
00032   coordConversionClient = n.serviceClient<youbot_overhead_vision::CoordConversion>("coord_conversion");
00033   costPublisher = n.advertise<youbot_overhead_localization::CostmapCost>("base_localizer/cost", 1);\
00034   costServer = n.advertiseService("/path_planner/get_cost",  &pathPlanner::getCost, this);
00035 
00036   //action lib
00037   as.start();
00038 
00039   //localization data
00040   currentPose.x = 0.0;
00041   currentPose.y = 0.0;
00042   currentPose.theta = 0.0;
00043   n.getParam("/path_planner/run_from_simulation", useSim);
00044 
00045   //plan
00046   vector<float> xVec;
00047   vector<float> yVec;
00048   vector<float> dst;
00049   plan.pathLength = 0;
00050   plan.pathx = xVec;
00051   plan.pathy = yVec;
00052   plan.distances = dst;
00053   plan.remainingDistance = 0;
00054   goalx = 2.0;
00055   goaly = 1.4;
00056 
00057   //costmap parameters
00058   inRadius = 0.24; //inner circle radius of robot base
00059   circumRadius = 0.4; //outer circle radius of robot base (not including arm)
00060   inflationRadius = 0.4;
00061   costScalingFactor = 1.0;
00062   obstacleRange = 2.5;
00063   raytraceRange = 3.0;
00064   maxObstacleHeight = .8;  //youbot height with arm fully extended
00065   lethalCostThreshold = 100;
00066   trackUnknownSpace = false;
00067   unknownCostValue = 0;
00068 
00069   //NavFn parameters
00070   allowUnknown = true;  //allow planning through unknown occupancy spaces
00071 
00072   //default position initialization, should be updated by localizer
00073   currentPose.x = 0;
00074   currentPose.y = 0;
00075   currentPose.theta = 0;
00076 
00077   //Trajectory error tracking
00078   prevTurnError = 0.0;
00079   prevError = 0.0;
00080   totalTurnError = 0.0;
00081   totalError = 0.0;
00082 }
00083 
00084 pathPlanner::~pathPlanner()
00085 {
00086   if (cmObj)
00087     delete cmObj;
00088 
00089   if (navFnObj)
00090     delete navFnObj;
00091 }
00092 
00093 void pathPlanner::executePathPlanning(const youbot_overhead_localization::PlanPathGoalConstPtr& goal)
00094 {
00095   ros::Rate r(30);
00096   bool success = true;
00097   youbot_overhead_vision::CoordConversion srv;
00098 
00099   if (goal->topic_name.compare("/logitech_9000_camera1/image_raw") == 0)
00100   {
00101     if (useSim)
00102     {
00103       srv.request.x = goal->x + SIM_TOP_CAMERA_OFFSET;
00104     }
00105     else
00106     {
00107       srv.request.x = goal->x + REAL_TOP_CAMERA_OFFSET;
00108     }
00109   }
00110   else
00111   {
00112     srv.request.x = goal->x;
00113   }
00114   srv.request.y = goal->y;
00115   srv.request.type = 1;  //convert from pixels to meters
00116   coordConversionClient.call(srv);
00117   goalx = srv.response.xConverted;
00118   goaly = srv.response.yConverted;
00119 
00120   int returnValue = makePlanAstar();
00121 
00122   //path found successfully
00123   if (returnValue == 1)
00124   {
00125     asFeedback.pathLength = plan.remainingDistance;
00126     as.publishFeedback(asFeedback);
00127 
00128     //Set initial time for path following
00129     clock_gettime(CLOCK_REALTIME, &prevPointTime);
00130 
00131     while (plan.pathLength > 0)
00132     {
00133       //Check for timeout
00134       timespec currentTime;
00135       clock_gettime(CLOCK_REALTIME, &currentTime);
00136       float elapsedTime = (currentTime.tv_sec + currentTime.tv_nsec / 1000000000.0) - (prevPointTime.tv_sec + prevPointTime.tv_nsec / 1000000000.0);
00137       //ROS_INFO("Time since last point was reached: %f", elapsedTime);
00138 
00139       if (as.isPreemptRequested() || !ros::ok() || elapsedTime > PATH_TIMEOUT)
00140       {
00141         ROS_INFO("%s: Preempted", actionName.c_str());
00142         baseCommand.linear.x = 0.0;
00143         baseCommand.linear.y = 0.0;
00144         baseCommand.angular.z = 0.0;
00145         baseCommandPublisher.publish(baseCommand);
00146         as.setPreempted();
00147         success = false;
00148         break;
00149       }
00150 
00151       followPath();
00152       
00153       r.sleep();
00154     }
00155 
00156     if (success)
00157     {
00158       asResult.posx = currentPose.x;
00159       asResult.posy = currentPose.y;
00160       ROS_INFO("%s: Succeeded", actionName.c_str());
00161       as.setSucceeded(asResult);
00162     }
00163   }
00164   else
00165   {
00166     ROS_INFO("%s: Preempted, no path found...", actionName.c_str());
00167     as.setPreempted();
00168   }
00169 }
00170 
00171 void pathPlanner::followPath()
00172 {
00173   float currentx = currentPose.x;
00174   float currenty = currentPose.y;
00175   float currentHeading = currentPose.theta;
00176 
00177   float targetx = plan.pathx.front();
00178   float targety = plan.pathy.front();
00179   float targetHeading = atan2(targety - currenty, targetx - currentx);
00180 
00181   float forwardVel = 0.0;
00182   float leftVel = 0.0;
00183   float turnVel = 0.0;
00184 
00185   float turnError = targetHeading - currentHeading;
00186   while (turnError < -PI)
00187   {
00188     turnError += 2*PI;
00189   }
00190   while (turnError > PI)
00191   {
00192     turnError -= 2*PI;
00193   }
00194 
00195   float forwardError = sqrt(pow(targetx - currentx, 2) + pow(targety - currenty, 2));
00196 
00197   //Goal point reached
00198   if (forwardError < .1)
00199   {
00200     totalTurnError = 0;
00201     totalError = 0;
00202     prevTurnError = 0;
00203     turnError = 0;
00204 
00205     //shift to the next point on the path
00206     plan.pathx.erase(plan.pathx.begin());
00207     plan.pathy.erase(plan.pathy.begin());
00208     if (plan.distances.size() > 0)
00209       plan.distances.erase(plan.distances.begin());
00210     plan.pathLength --;
00211     if (plan.pathLength > 0)
00212       plan.remainingDistance = sqrt(pow(plan.pathx.front() - currentx, 2) + pow(plan.pathy.front() - currenty, 2));
00213 
00214     //Reset time for timeout check
00215     clock_gettime(CLOCK_REALTIME, &prevPointTime);
00216   }
00217   //Goal point close
00218   else if (forwardError < .5)
00219   {
00220     float angleToGoal = atan2(targety - currenty, targetx - currentx)
00221       - (currentHeading - PI/2.0);
00222 
00223     //Adjust position
00224     forwardVel = KP_LIN*forwardError + KI_LIN*totalError
00225       + KD_LIN*(forwardError - prevError);
00226     leftVel = -1 * forwardVel * cos(angleToGoal);
00227     forwardVel = forwardVel * sin(angleToGoal);
00228     totalError += forwardError;
00229     prevError = forwardError;
00230 
00231     plan.remainingDistance = forwardError;
00232   }
00233   //Goal point far
00234   else
00235   {
00236     //turn towards next point
00237     turnVel = KP_ANG*turnError + KI_ANG*totalTurnError
00238       + KD_ANG*(turnError - prevTurnError);
00239     totalTurnError += turnError;
00240     prevTurnError = turnError;
00241 
00242     if (abs(turnError) < .08727)  //angle is within 5 degrees, move towards next point
00243     {
00244       totalTurnError = 0;
00245 
00246       forwardVel = KP_LIN*forwardError + KI_LIN*totalError
00247         + KD_LIN*(forwardError - prevError);
00248       totalError += forwardError;
00249       prevError = forwardError;
00250     }
00251 
00252     plan.remainingDistance = forwardError;
00253   }
00254 
00255   //calculate remaining path distance
00256   for (unsigned int i = 0; i < plan.distances.size(); i++)
00257   {
00258     plan.remainingDistance += plan.distances[i];
00259   }
00260 
00261   //reduce the speed
00262   if (forwardVel > 1)
00263     forwardVel = 1;
00264   else if (forwardVel < -1)
00265     forwardVel = -1;
00266   if (leftVel > 1)
00267     leftVel = 1;
00268   else if (leftVel < -1)
00269     leftVel = -1;
00270   if (turnVel > 1)
00271     turnVel = 1;
00272   else if (turnVel < -1)
00273     turnVel = -1;
00274 
00275 
00276 
00277   forwardVel =  forwardVel * MAX_SPEED;
00278   leftVel =   leftVel * MAX_SPEED;
00279   turnVel = turnVel * MAX_SPEED;
00280   
00281   ROS_INFO("Forward vel %f, Left %f, turn %f", forwardVel, leftVel, turnVel);
00282  
00283   
00284   //move command for the robot base
00285   baseCommand.linear.x = forwardVel;
00286   baseCommand.linear.y = leftVel;
00287   baseCommand.angular.z = turnVel;
00288   baseCommandPublisher.publish(baseCommand);
00289 
00290   asFeedback.pathLength = plan.remainingDistance;
00291   as.publishFeedback(asFeedback);
00292 }
00293 
00294 void pathPlanner::localizationCallback(const youbot_overhead_localization::Pose2d& newPose)
00295 {
00296   currentPose.x = newPose.x;
00297   currentPose.y = newPose.y;
00298   currentPose.theta = newPose.theta;
00299 
00300   if (cmInitialized)
00301   {
00302     unsigned int costmapx, costmapy;
00303     youbot_overhead_localization::CostmapCost currentCost;
00304     if (!cmObj->worldToMap(currentPose.y, currentPose.x, costmapx, costmapy))
00305       currentCost.cost = 255;  //set cost high if robot is off map
00306     else
00307       currentCost.cost = cmObj->getCost(costmapx, costmapy);
00308     costPublisher.publish(currentCost);
00309   }
00310 }
00311 
00312 void pathPlanner::mapserverCallback(const nav_msgs::OccupancyGridConstPtr& newMap)
00313 {
00314   mapWidth = newMap->info.width;
00315   mapHeight = newMap->info.height;
00316   mapResolution = newMap->info.resolution;
00317   mapOriginx = newMap->info.origin.position.x;
00318   mapOriginy = newMap->info.origin.position.y;
00319   unsigned int mapCellCount = mapWidth * mapHeight;
00320   mapData.clear();
00321   mapData.reserve(mapCellCount);
00322   for(unsigned int i = 0; i < mapCellCount; i++)
00323     mapData.push_back((unsigned char)newMap->data[i]);
00324   if (!cmInitialized)
00325   {
00326     cmObj = new Costmap2D(mapWidth, mapHeight,
00327       mapResolution, mapOriginx, mapOriginy, inRadius, circumRadius,
00328       inflationRadius, obstacleRange, maxObstacleHeight, raytraceRange,
00329       costScalingFactor, mapData, lethalCostThreshold, trackUnknownSpace,
00330       unknownCostValue);
00331     cmInitialized = true;
00332   }
00333   else
00334   {
00335     cmObj->replaceFullMap(mapOriginx, mapOriginy, mapWidth, mapHeight, mapData);
00336   }
00337   unsigned int costmapx, costmapy;
00338   youbot_overhead_localization::CostmapCost currentCost;
00339   //x and y must be switched going in and out of the planning algorithm because
00340   //the planner map image uses a (row, col) coordinate system instead of (x, y)
00341   if (!cmObj->worldToMap(currentPose.y, currentPose.x, costmapx, costmapy))
00342     currentCost.cost = 255; //set cost high if robot is off map
00343   else
00344     currentCost.cost = cmObj->getCost(costmapx, costmapy);
00345   costPublisher.publish(currentCost);
00346 
00347   //TODO: Debugging stuff
00348   //printOccupancyGrid();
00349   //printCostmap();
00350   //ROS_INFO("Position: %d, %d", costmapx, costmapy);
00351   //ROS_INFO("Cost: %d", cmObj->getCost(costmapx, costmapy));
00352   //TODO: End of debugging stuff
00353 
00354   //initialize navfn
00355   if (!navFnInitialized)
00356   {
00357     navFnObj = new NavFn(cmObj->getSizeInCellsX(), cmObj->getSizeInCellsY());
00358     navFnInitialized = true;
00359   }
00360     navFnObj->setCostmap(cmObj->getCharMap(), true, allowUnknown);
00361 }
00362 
00363 int pathPlanner::makePlanAstar()
00364 {
00365   if (!(cmInitialized && navFnInitialized))
00366   {
00367     ROS_INFO("Costmap or planner not initialized. Unable to plan.");
00368     return -1;
00369   }
00370 
00371   //x and y must be switched going in and out of the planning algorithm because
00372   //the planner map image uses a (row, col) coordinate system instead of (x, y)
00373   unsigned int mapStartx, mapStarty;
00374   if (!cmObj->worldToMap(currentPose.y, currentPose.x, mapStartx, mapStarty))
00375   {
00376     ROS_INFO("Robot start position is outside map area.");
00377     return -1;
00378   }
00379 
00380   unsigned int mapGoalx, mapGoaly;
00381   if (!cmObj->worldToMap(goaly, goalx, mapGoalx, mapGoaly))
00382   {
00383     ROS_INFO("Robot goal position is outside map area.");
00384     return -1;
00385   }
00386 
00387   plan.pathx.clear();
00388   plan.pathy.clear();
00389   plan.distances.clear();
00390 
00391   int mapStart[2];
00392   mapStart[0] = mapStartx;
00393   mapStart[1] = mapStarty;
00394 
00395   int mapGoal[2];
00396   mapGoal[0] = mapGoalx;
00397   mapGoal[1] = mapGoaly;
00398 
00399   navFnObj->setStart(mapStart);
00400   navFnObj->setGoal(mapGoal);
00401 
00402   //calculate plan
00403   navFnObj->calcNavFnAstar();
00404   navFnObj->calcPath(cmObj->getSizeInCellsX() * 4);
00405 
00406   //extract the plan
00407   int len = navFnObj->getPathLen();
00408   float *tempPathy = navFnObj->getPathX();
00409   float *tempPathx = navFnObj->getPathY();
00410 
00411   if (len <= 1)
00412   {
00413     ROS_INFO("Goal is unreachable");
00414     return -1;
00415   }
00416 
00417   //filter path
00418   float xfilt[len];
00419   float yfilt[len];
00420 
00421   for(int i=0; i < len; i++)
00422   {
00423     xfilt[i] = tempPathx[i];
00424     yfilt[i] = tempPathy[i];
00425   }
00426 
00427   //First order Butterworth filter
00428   if (len > 1)
00429   {
00430     for (int i=1; i < len; i++)
00431     {
00432       xfilt[i] = (B1*tempPathx[i] + B2*tempPathx[(i-1)] - A*xfilt[(i-1)]);
00433       yfilt[i] = (B1*tempPathy[i] + B2*tempPathy[(i-1)] - A*yfilt[(i-1)]);
00434     }
00435   }
00436 
00437   //Significant point extraction
00438   int i;
00439   int k = 5;  //test interval
00440   float c[len];
00441   for (i=0; i < len; i++)
00442   {
00443     float ax, ay, bx, by;
00444     if (i+k < len)
00445     {
00446       ax = xfilt[i] - xfilt[i+k];
00447       ay = yfilt[i] - yfilt[i+k];
00448     }
00449     else
00450     {
00451       ax = xfilt[i] - xfilt[len-1];
00452       ay = yfilt[i] - yfilt[len-1];
00453     }
00454     if (i-k > 0)
00455     {
00456       bx = xfilt[i] - xfilt[i-k];
00457       by = yfilt[i] - yfilt[i-k];
00458     }
00459     else
00460     {
00461       bx = xfilt[i] - xfilt[0];
00462       by = yfilt[i] - yfilt[0];
00463     }
00464     c[i] = (ax*bx + ay*by) / (sqrt(ax*ax+ay*ay)*sqrt(bx*bx+by*by));
00465   }
00466 
00467   float sigx[len];
00468   float sigy[len];
00469   int numSig = 0;
00470   for (i=1; i<len-1; i++)
00471   {
00472     if (c[i] > c[i-1] && c[i] > c[i+1] && c[i] > SIG_THRESH)
00473     {
00474       sigx[numSig] = xfilt[i];
00475       sigy[numSig] = yfilt[i];
00476       numSig ++;
00477     }
00478   }
00479 
00480   //Add endpoint as a significant path point
00481   sigx[numSig] = xfilt[len-1];
00482   sigy[numSig] = yfilt[len-1];
00483   numSig ++;
00484 
00485   //Convert path from map coordinates to world coordinates
00486   for (i=0; i < numSig; i++)
00487   {
00488     youbot_overhead_vision::CoordConversion srv;
00489     srv.request.x = sigx[i];
00490     srv.request.y = sigy[i];
00491     srv.request.type = 3;  //convert from occupancy grid pixels to meters
00492     coordConversionClient.call(srv);
00493 
00494     plan.pathx.push_back(srv.response.xConverted);
00495     plan.pathy.push_back(srv.response.yConverted);
00496   }
00497 
00498   plan.pathLength = numSig;
00499 
00500   //Fill in distances for remaining path distance calculation
00501   for (i=0; i < plan.pathLength - 1; i++)
00502   {
00503     plan.distances.push_back(sqrt(pow(plan.pathx[i+1] - plan.pathx[i], 2) + pow(plan.pathy[i+1] - plan.pathy[i], 2)));
00504   }
00505   if (plan.pathLength > 0)
00506   {
00507     plan.remainingDistance = sqrt(pow(plan.pathx[0] - currentPose.x, 2) + pow(plan.pathy[0] - currentPose.y, 2));
00508     for (unsigned int j=0; j < plan.distances.size(); j++)
00509     {
00510       plan.remainingDistance += plan.distances[j];
00511     }
00512   }
00513   else
00514     plan.remainingDistance = 0;
00515 
00516   return 1;
00517 }
00518 
00519 bool pathPlanner::getCost(youbot_overhead_localization::GetCost::Request &req, youbot_overhead_localization::GetCost::Response &res)
00520 {
00521   unsigned int mapPosx, mapPosy;
00522   //x and y must be switched going in and out of the planning algorithm because
00523   //the planner map image uses a (row, col) coordinate system instead of (x, y)
00524   if (!cmObj->worldToMap(req.y, req.x, mapPosx, mapPosy))
00525   {
00526     ROS_INFO("Requested cost position is outside map area.");
00527     return false;
00528   }
00529 
00530   res.cost = cmObj->getCost(mapPosx, mapPosy);
00531 
00532   return true;
00533 }
00534 
00535 void pathPlanner::printOccupancyGrid()
00536 {
00537   FILE *fp = fopen(mapDebugLocation.c_str(), "w");
00538 
00539   if(!fp)
00540   {
00541     ROS_INFO("Can't open file %s.", mapDebugLocation.c_str());
00542   }
00543   else
00544   {
00545     fprintf(fp, "P2\n%d\n%d\n%d\n", cmObj->getSizeInCellsX(), cmObj->getSizeInCellsY(), 0xff);
00546     for(unsigned int iy = 0; iy < cmObj->getSizeInCellsY(); iy++) {
00547       for(unsigned int ix = 0; ix < cmObj->getSizeInCellsX(); ix++) {
00548         unsigned char cost = cmObj->getCost(ix,iy);
00549         if (cost == LETHAL_OBSTACLE) {
00550           fprintf(fp, "255 ");
00551         }
00552         else if (cost == NO_INFORMATION){
00553           fprintf(fp, "180 ");
00554         }
00555         else if (cost == INSCRIBED_INFLATED_OBSTACLE ) {
00556           fprintf(fp, "128 ");
00557         }
00558         else if (cost > 0){
00559           fprintf(fp, "50 ");
00560         }
00561         else {
00562           fprintf(fp, "0 ");
00563         }
00564       }
00565       fprintf(fp, "\n");
00566     }
00567     fclose(fp);
00568   }
00569 }
00570 
00571 void pathPlanner::printCostmap()
00572 {
00573   FILE *fp = fopen(mapDebugLocation.c_str(), "w");
00574 
00575   if(!fp)
00576   {
00577     ROS_INFO("Can't open file %s.", mapDebugLocation.c_str());
00578   }
00579   else
00580   {
00581     fprintf(fp, "P2\n%d\n%d\n%d\n", cmObj->getSizeInCellsX(), cmObj->getSizeInCellsY(), 0xff);
00582     for(unsigned int iy = 0; iy < cmObj->getSizeInCellsY(); iy++) {
00583       for(unsigned int ix = 0; ix < cmObj->getSizeInCellsX(); ix++) {
00584         unsigned char cost = cmObj->getCost(ix,iy);
00585         fprintf(fp, "%d ", cost);
00586       }
00587       fprintf(fp, "\n");
00588     }
00589     fclose(fp);
00590   }
00591 }
00592 
00596 int main(int argc, char **argv)
00597 {
00598   ros::init(argc, argv, "path_planner");
00599   pathPlanner planner2d(ros::this_node::getName());
00600   ros::spin();
00601 
00602   return 0;
00603 }


youbot_overhead_localization
Author(s): David Kent
autogenerated on Thu Jan 2 2014 12:14:20