approach_planner.cpp
Go to the documentation of this file.
00001 /*
00002  * Software License Agreement (Modified BSD License)
00003  *
00004  *  Copyright (c) 2013, PAL Robotics, S.L.
00005  *  All rights reserved.
00006  *
00007  *  Redistribution and use in source and binary forms, with or without
00008  *  modification, are permitted provided that the following conditions
00009  *  are met:
00010  *
00011  *   * Redistributions of source code must retain the above copyright
00012  *     notice, this list of conditions and the following disclaimer.
00013  *   * Redistributions in binary form must reproduce the above
00014  *     copyright notice, this list of conditions and the following
00015  *     disclaimer in the documentation and/or other materials provided
00016  *     with the distribution.
00017  *   * Neither the name of PAL Robotics, S.L. nor the names of its
00018  *     contributors may be used to endorse or promote products derived
00019  *     from this software without specific prior written permission.
00020  *
00021  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022  *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023  *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00024  *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00025  *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00026  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00027  *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00028  *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00029  *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00030  *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00031  *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00032  *  POSSIBILITY OF SUCH DAMAGE.
00033  */
00034 
00037 #include <algorithm>
00038 #include <cassert>
00039 #include <cmath>
00040 #include <sstream>
00041 #include <string>
00042 
00043 #include <boost/foreach.hpp>
00044 
00045 #include <ros/ros.h>
00046 #include <ros/callback_queue.h>
00047 #include <trajectory_msgs/JointTrajectory.h>
00048 #include <moveit/move_group_interface/move_group.h>
00049 
00050 #include <play_motion/approach_planner.h>
00051 #include <play_motion/xmlrpc_helpers.h>
00052 
00053 #define foreach BOOST_FOREACH
00054 using std::string;
00055 using std::vector;
00056 
00057 namespace
00058 {
00059 
00061 template <class T>
00062 string enumerateElementsStr(const T& val)
00063 {
00064   std::stringstream ss;
00065   std::copy(val.begin(), val.end(), std::ostream_iterator<typename T::value_type>(ss, ", "));
00066   string ret = ss.str();
00067   if (!ret.empty()) {ret.erase(ret.size() - 2);} // Remove last ", "
00068   return ret;
00069 }
00070 
00071 typedef moveit::planning_interface::MoveGroup MoveGroup;
00072 typedef boost::shared_ptr<MoveGroup> MoveGroupPtr;
00073 
00075 string enumeratePlanningGroups(const std::vector<MoveGroupPtr>& move_groups)
00076 {
00077   string ret;
00078   foreach(MoveGroupPtr group, move_groups) {ret += group->getName() + ", ";}
00079   if (!ret.empty()) {ret.erase(ret.size() - 2);} // Remove last ", "
00080   return ret;
00081 }
00082 
00083 } // namespace
00084 
00085 namespace play_motion
00086 {
00087 
00088 ApproachPlanner::PlanningData::PlanningData(MoveGroupPtr move_group_ptr)
00089   : move_group(move_group_ptr),
00090     sorted_joint_names(move_group_ptr->getActiveJoints())
00091 {
00092   std::sort(sorted_joint_names.begin(), sorted_joint_names.end());
00093 }
00094 
00095 ApproachPlanner::ApproachPlanner(const ros::NodeHandle& nh)
00096   : joint_tol_(1e-3),
00097     skip_planning_vel_(0.5),
00098     skip_planning_min_dur_(0.0),
00099     planning_disabled_(false)
00100 {
00101   ros::NodeHandle ap_nh(nh, "approach_planner");
00102 
00103   const string JOINT_TOL_STR             = "joint_tolerance";
00104   const string PLANNING_GROUPS_STR       = "planning_groups";
00105   const string NO_PLANNING_JOINTS_STR    = "exclude_from_planning_joints";
00106   const string SKIP_PLANNING_VEL_STR     = "skip_planning_approach_vel";
00107   const string SKIP_PLANNING_MIN_DUR_STR = "skip_planning_approach_min_dur";
00108 
00109   // Velocity used in non-planned approaches
00110   const bool skip_planning_vel_ok = ap_nh.getParam(SKIP_PLANNING_VEL_STR, skip_planning_vel_);
00111   if (skip_planning_vel_ok) {ROS_DEBUG_STREAM("Using a max velocity of " << skip_planning_vel_ <<
00112                                               " for unplanned approaches.");}
00113   else                      {ROS_DEBUG_STREAM("Max velocity for unplanned approaches not specified. " <<
00114                                               "Using default value of " << skip_planning_vel_);}
00115 
00116   // Minimum duration used in non-planned approaches
00117   const bool skip_planning_min_dur_ok = ap_nh.getParam(SKIP_PLANNING_MIN_DUR_STR, skip_planning_min_dur_);
00118   if (skip_planning_min_dur_ok) {ROS_DEBUG_STREAM("Using a min duration of " << skip_planning_min_dur_ <<
00119                                               " for unplanned approaches.");}
00120   else                      {ROS_DEBUG_STREAM("Min duration for unplanned approaches not specified. " <<
00121                                               "Using default value of " << skip_planning_min_dur_);}
00122 
00123   // Initialize motion planning capability, unless explicitly disabled
00124   nh.getParam("disable_motion_planning", planning_disabled_);
00125   if (planning_disabled_)
00126   {
00127     ROS_WARN_STREAM("Motion planning capability disabled. Goals requesting planning (the default) will be rejected.\n"
00128                     << "To disable planning in goal requests set 'skip_planning=true'");
00129     return; // Skip initialization of planning-related members
00130   }
00131 
00132   // Joint tolerance
00133   const bool joint_tol_ok = ap_nh.getParam(JOINT_TOL_STR, joint_tol_);
00134   if (joint_tol_ok) {ROS_DEBUG_STREAM("Using joint tolerance of " << joint_tol_);}
00135   else              {ROS_DEBUG_STREAM("Joint tolerance not specified. Using default value of " << joint_tol_);}
00136 
00137   // Joints excluded from motion planning
00138   using namespace XmlRpc;
00139   XmlRpcValue xml_no_plan_joints;
00140   const bool xml_no_plan_joints_ok = ap_nh.getParam(NO_PLANNING_JOINTS_STR, xml_no_plan_joints);
00141   if (xml_no_plan_joints_ok)
00142   {
00143     if (xml_no_plan_joints.getType() != XmlRpcValue::TypeArray)
00144     {
00145       const string what = "The '" + NO_PLANNING_JOINTS_STR + "' parameter is not an array (namespace: " +
00146                                ap_nh.getNamespace() + ").";
00147       throw ros::Exception(what);
00148     }
00149     no_plan_joints_.resize(xml_no_plan_joints.size());
00150     try
00151     {
00152       for (int i = 0; i < xml_no_plan_joints.size(); ++i) {xh::getArrayItem(xml_no_plan_joints, i, no_plan_joints_[i]);}
00153     }
00154     catch(const xh::XmlrpcHelperException& ex) {throw ros::Exception(ex.what());}
00155   }
00156 
00157   // Planning group names
00158   using namespace XmlRpc;
00159   XmlRpcValue xml_planning_groups;
00160   if (!ap_nh.getParam(PLANNING_GROUPS_STR, xml_planning_groups))
00161   {
00162     const string what = "Unspecified planning groups for computing approach trajectories. Please set the '" +
00163                                PLANNING_GROUPS_STR + "' parameter (namespace: " + ap_nh.getNamespace() +  ").";
00164     throw ros::Exception(what);
00165   }
00166   if (xml_planning_groups.getType() != XmlRpcValue::TypeArray)
00167   {
00168     const string what = "The '" + PLANNING_GROUPS_STR + "' parameter is not an array (namespace: " +
00169                              ap_nh.getNamespace() + ").";
00170     throw ros::Exception(what);
00171   }
00172   vector<string> planning_groups(xml_planning_groups.size());
00173   try
00174   {
00175     for (int i = 0; i < xml_planning_groups.size(); ++i) {xh::getArrayItem(xml_planning_groups, i, planning_groups[i]);}
00176   }
00177   catch(const xh::XmlrpcHelperException& ex) {throw ros::Exception(ex.what());}
00178 
00179   // Move group instances require their own spinner thread. To isolate this asynchronous spinner from the rest of the
00180   // node, it is set up in a node handle with a custom callback queue
00181   ros::NodeHandle as_nh;
00182   cb_queue_.reset(new ros::CallbackQueue());
00183   as_nh.setCallbackQueue(cb_queue_.get());
00184   spinner_.reset(new ros::AsyncSpinner(1, cb_queue_.get()));
00185   spinner_->start();
00186 
00187   // Populate planning data
00188   foreach (const string& planning_group, planning_groups)
00189   {
00190     MoveGroup::Options opts(planning_group);
00191     opts.node_handle_ = as_nh;
00192     MoveGroupPtr move_group(new MoveGroup(opts)); // TODO: Timeout and retry, log feedback. Throw on failure
00193     planning_data_.push_back(PlanningData(move_group));
00194   }
00195 }
00196 
00197 // TODO: Work directly with JointStates and JointTrajector messages?
00198 bool ApproachPlanner::prependApproach(const JointNames&        joint_names,
00199                                       const vector<double>&    current_pos,
00200                                       bool                     skip_planning,
00201                                       const vector<TrajPoint>& traj_in,
00202                                             vector<TrajPoint>& traj_out)
00203 {
00204   // TODO: Instead of returning false, raise exceptions, so error message can be forwarded to goal result
00205 
00206   // Empty trajectory. Nothing to do
00207   if (traj_in.empty())
00208   {
00209     ROS_DEBUG("Approach motion not needed: Input trajectory is empty.");
00210     traj_out = traj_in;
00211     return true;
00212   }
00213 
00214   const unsigned int joint_dim = traj_in.front().positions.size();
00215 
00216   // Preconditions
00217   if (joint_dim != joint_names.size())
00218   {
00219     ROS_ERROR("Can't compute approach trajectory: Size mismatch between joint names and input trajectory.");
00220     return false;
00221   }
00222   if (joint_dim != current_pos.size())
00223   {
00224     ROS_ERROR("Can't compute approach trajectory: Size mismatch between current joint positions and input trajectory.");
00225     return false;
00226   }
00227   if (!skip_planning && planning_disabled_) // Reject goal if plannign is disabled, but goal requests it
00228   {
00229     ROS_ERROR("Motion planning capability disabled. To disable planning in goal requests, set 'skip_planning=true'");
00230     return false;
00231   }
00232 
00233   if (skip_planning)
00234   {
00235     // Skip motion planning altogether
00236     traj_out = traj_in;
00237 
00238     // If the first waypoint specifies zero time from start, set a duration that does not exceed a specified
00239     // max avg velocity
00240     if (traj_out.front().time_from_start.isZero())
00241     {
00242       const double reach_time = noPlanningReachTime(current_pos, traj_out.front().positions);
00243       foreach(TrajPoint& point, traj_out) {point.time_from_start += ros::Duration(reach_time);}
00244     }
00245   }
00246   else
00247   {
00248     // Compute approach trajectory using motion planning
00249     trajectory_msgs::JointTrajectory approach;
00250     const bool approach_ok = computeApproach(joint_names,
00251                                              current_pos,
00252                                              traj_in.front().positions,
00253                                              approach);
00254     if (!approach_ok) {return false;}
00255 
00256     // No approach is required
00257     if (approach.points.empty())
00258     {
00259       traj_out = traj_in;
00260       ROS_INFO("Approach motion not needed.");
00261     }
00262     else
00263     {
00264       // Combine approach and input motion trajectories
00265       combineTrajectories(joint_names,
00266                           current_pos,
00267                           traj_in,
00268                           approach,
00269                           traj_out);
00270     }
00271   }
00272 
00273   // Deal with first waypoints specifying zero time from start. Two cases can happen:
00274   // 1. If at least one joint is not at its destination, compute an appropriate reach time
00275   const double eps_time = 1e-3; // NOTE: Magic number
00276   if (traj_out.front().time_from_start.isZero())
00277   {
00278     const double reach_time = noPlanningReachTime(current_pos, traj_out.front().positions);
00279     if (reach_time > eps_time)
00280     {
00281       foreach(TrajPoint& point, traj_out) {point.time_from_start += ros::Duration(reach_time);}
00282     }
00283   }
00284   // 2 . First waypoint corresponds to current state: Make the first time_from_start a small nonzero value.
00285   // Rationale: Sending a waypoint with zero time from start will make the controllers complain with a warning, and
00286   // rightly so, because in general it's impossible to reach a point in zero time.
00287   // This avoids unsavory warnings that might confuse users.
00288   if (traj_out.front().time_from_start.isZero()) // If still zero it's because previous step yield zero time
00289   {
00290     traj_out.front().time_from_start = ros::Duration(eps_time);
00291   }
00292 
00293   return true;
00294 }
00295 
00296 bool ApproachPlanner::needsApproach(const std::vector<double>& current_pos,
00297                                     const std::vector<double>& goal_pos)
00298 {
00299   assert(current_pos.size() == goal_pos.size());
00300   for (unsigned int i = 0; i < current_pos.size(); ++i)
00301   {
00302     if (std::abs(current_pos[i] - goal_pos[i]) > joint_tol_) return true;
00303   }
00304   return false;
00305 }
00306 
00307 bool ApproachPlanner::computeApproach(const vector<string>&             joint_names,
00308                                       const vector<double>&             current_pos,
00309                                       const vector<double>&             goal_pos,
00310                                       trajectory_msgs::JointTrajectory& traj)
00311 {
00312   traj.joint_names.clear();
00313   traj.points.clear();
00314 
00315   // Maximum set of joints that a planning group can have. Corresponds to the original motion joints minus the joints
00316   // excluded from planning. Planning groups eligible to compute the approach can't contain joints outside this set.
00317   JointNames max_planning_group;
00318 
00319   // Joint positions associated to the maximum set
00320   vector<double> max_planning_values;
00321 
00322   // Minimum set of joints that a planning group can have. Corresponds to the maximum set minus the joints that are
00323   // already at their goal configuration. If this set is empty, no approach is required, i.e. all motion joints are
00324   // either excluded from planning or already at the goal.
00325   JointNames min_planning_group;
00326 
00327   for (unsigned int i = 0; i < joint_names.size(); ++i)
00328   {
00329     if (isPlanningJoint(joint_names[i]))
00330     {
00331       max_planning_group.push_back(joint_names[i]);
00332       max_planning_values.push_back(goal_pos[i]);
00333       if (std::abs(current_pos[i] - goal_pos[i]) > joint_tol_) {min_planning_group.push_back(joint_names[i]);}
00334     }
00335   }
00336 
00337   // No planning is required, return empty trajectory
00338   if (min_planning_group.empty()) {return true;}
00339 
00340   // Find planning groups that are eligible for computing this particular approach trajectory
00341   vector<MoveGroupPtr> valid_move_groups = getValidMoveGroups(min_planning_group, max_planning_group);
00342   if (valid_move_groups.empty())
00343   {
00344     ROS_ERROR_STREAM("Can't compute approach trajectory. There are no planning groups that span at least these joints:"
00345                      << "\n[" << enumerateElementsStr(min_planning_group) << "]\n" << "and at most these joints:"
00346                      << "\n[" << enumerateElementsStr(max_planning_group) << "].");
00347     return false;
00348   }
00349   else
00350   {
00351     ROS_INFO_STREAM("Approach motion can be computed by the following groups: "
00352                      << enumeratePlanningGroups(valid_move_groups) << ".");
00353   }
00354 
00355   // Call motion planners
00356   bool approach_ok = false;
00357   foreach(MoveGroupPtr move_group, valid_move_groups)
00358   {
00359     approach_ok = planApproach(max_planning_group, max_planning_values, move_group, traj);
00360     if (approach_ok) {break;}
00361   }
00362 
00363   if (!approach_ok)
00364   {
00365     ROS_ERROR_STREAM("Failed to compute approach trajectory with planning groups: [" <<
00366                      enumeratePlanningGroups(valid_move_groups) << "].");
00367     return false;
00368   }
00369 
00370   return true;
00371 }
00372 
00373 bool ApproachPlanner::planApproach(const JointNames&                 joint_names,
00374                                    const std::vector<double>&        joint_values,
00375                                    MoveGroupPtr                      move_group,
00376                                    trajectory_msgs::JointTrajectory& traj)
00377 {
00378   move_group->setStartStateToCurrentState();
00379   for (unsigned int i = 0; i < joint_names.size(); ++i)
00380   {
00381     const bool set_goal_ok = move_group->setJointValueTarget(joint_names[i], joint_values[i]);
00382     if (!set_goal_ok)
00383     {
00384       ROS_ERROR_STREAM("Failed attempt to set planning goal for joint '" << joint_names[i] << "' on group '" <<
00385                        move_group->getName() << "'.");
00386       return false;
00387     }
00388   }
00389   move_group_interface::MoveGroup::Plan plan;
00390   const bool planning_ok = move_group->plan(plan);
00391   if (!planning_ok)
00392   {
00393     ROS_DEBUG_STREAM("Could not compute approach trajectory with planning group '" << move_group->getName() << "'.");
00394     return false;
00395   }
00396   if (plan.trajectory_.joint_trajectory.points.empty())
00397   {
00398     ROS_ERROR_STREAM("Unexpected error: Approach trajectory computed by group '" << move_group->getName() <<
00399                      "' is empty.");
00400     return false;
00401   }
00402 
00403   traj = plan.trajectory_.joint_trajectory;
00404   ROS_INFO_STREAM("Successfully computed approach with planning group '" << move_group->getName() << "'.");
00405   return true;
00406 }
00407 
00408 void ApproachPlanner::combineTrajectories(const JointNames&                  joint_names,
00409                                           const std::vector<double>&         current_pos,
00410                                           const std::vector<TrajPoint>&      traj_in,
00411                                           trajectory_msgs::JointTrajectory&  approach,
00412                                           std::vector<TrajPoint>&            traj_out)
00413 {
00414   const unsigned int joint_dim = traj_in.front().positions.size();
00415 
00416   foreach(const TrajPoint& point_appr, approach.points)
00417   {
00418     const bool has_velocities    = !point_appr.velocities.empty();
00419     const bool has_accelerations = !point_appr.accelerations.empty();
00420     TrajPoint point;
00421     point.positions.resize(joint_dim, 0.0);
00422     if (has_velocities)    {point.velocities.resize(joint_dim, 0.0);}
00423     if (has_accelerations) {point.accelerations.resize(joint_dim, 0.0);}
00424     point.time_from_start = point_appr.time_from_start;
00425 
00426     for (unsigned int i = 0; i < joint_dim; ++i)
00427     {
00428       const JointNames& plan_joints = approach.joint_names;
00429       JointNames::const_iterator approach_joints_it = find(plan_joints.begin(), plan_joints.end(), joint_names[i]);
00430       if (approach_joints_it != plan_joints.end())
00431       {
00432         // Joint is part of the planned approach
00433         const unsigned int approach_id = std::distance(plan_joints.begin(), approach_joints_it);
00434         point.positions[i] = point_appr.positions[approach_id];
00435         if (has_velocities)    {point.velocities[i]    = point_appr.velocities[approach_id];}
00436         if (has_accelerations) {point.accelerations[i] = point_appr.accelerations[approach_id];}
00437       }
00438       else
00439       {
00440         // Joint is not part of the planning group, and hence not contained in the approach plan
00441         // Default to linear interpolation TODO: Use spline interpolator
00442         const double t_min = 0.0;
00443         const double t_max = approach.points.back().time_from_start.toSec();
00444         const double t     = point_appr.time_from_start.toSec();
00445 
00446         const double p_min = current_pos[i];
00447         const double p_max = traj_in.front().positions[i];
00448 
00449         const double vel = (p_max - p_min) / (t_max - t_min);
00450 
00451         point.positions[i] = p_min + vel * t;
00452         if (has_velocities)    {point.velocities[i]    = vel;}
00453         if (has_accelerations) {point.accelerations[i] = 0.0;}
00454       }
00455     }
00456 
00457     traj_out.push_back(point);
00458   }
00459 
00460   // If input trajectory is a single point, the approach trajectory is all there is to execute...
00461   if (1 == traj_in.size()) {return;}
00462 
00463   // ...otherwise, append input_trajectory after approach:
00464 
00465   // Time offset to apply to input trajectory (approach duration)
00466   const ros::Duration offset = traj_out.back().time_from_start;
00467 
00468   // Remove duplicate waypoint: Position of last approach point coincides with the input's first point
00469   traj_out.pop_back();
00470 
00471   // Append input trajectory to approach
00472   foreach(const TrajPoint& point, traj_in)
00473   {
00474     traj_out.push_back(point);
00475     traj_out.back().time_from_start += offset;
00476   }
00477 }
00478 
00479 vector<ApproachPlanner::MoveGroupPtr> ApproachPlanner::getValidMoveGroups(const JointNames& min_group,
00480                                                                           const JointNames& max_group)
00481 {
00482   vector<MoveGroupPtr> valid_groups;
00483 
00484   // Create sorted ranges of min/max planning groups
00485   JointNames min_group_s = min_group;
00486   JointNames max_group_s = max_group;
00487   std::sort(min_group_s.begin(), min_group_s.end());
00488   std::sort(max_group_s.begin(), max_group_s.end());
00489 
00490   foreach(const PlanningData& data, planning_data_)
00491   {
00492     const JointNames& group_s = data.sorted_joint_names;
00493 
00494     // A valid planning group is one that has the minimum group as a subset, and is a subset of the maximum group
00495     if (std::includes(group_s.begin(), group_s.end(), min_group_s.begin(), min_group_s.end()) &&
00496         std::includes(max_group_s.begin(), max_group_s.end(), group_s.begin(), group_s.end()))
00497     {
00498       valid_groups.push_back(data.move_group);
00499     }
00500   }
00501   return valid_groups;
00502 }
00503 
00504 bool ApproachPlanner::isPlanningJoint(const string& joint_name) const
00505 {
00506   return std::find(no_plan_joints_.begin(), no_plan_joints_.end(), joint_name) == no_plan_joints_.end();
00507 }
00508 
00509 double ApproachPlanner::noPlanningReachTime(const std::vector<double>& curr_pos,
00510                                             const std::vector<double>& goal_pos)
00511 {
00512   double dmax = 0.0; // Maximum joint displacement
00513   for (unsigned int i = 0; i < curr_pos.size(); ++i)
00514   {
00515     const double d = std::abs(goal_pos[i] - curr_pos[i]);
00516     if (d > dmax)
00517       dmax = d;
00518   }
00519   return std::max(dmax / skip_planning_vel_, skip_planning_min_dur_);
00520 }
00521 
00522 } // namesapce


play_motion
Author(s): Paul Mathieu
autogenerated on Wed Aug 26 2015 15:29:25