00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
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);}
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);}
00080 return ret;
00081 }
00082
00083 }
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
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
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
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;
00130 }
00131
00132
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
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
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
00180
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
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));
00193 planning_data_.push_back(PlanningData(move_group));
00194 }
00195 }
00196
00197
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
00205
00206
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
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_)
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
00236 traj_out = traj_in;
00237
00238
00239
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
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
00257 if (approach.points.empty())
00258 {
00259 traj_out = traj_in;
00260 ROS_INFO("Approach motion not needed.");
00261 }
00262 else
00263 {
00264
00265 combineTrajectories(joint_names,
00266 current_pos,
00267 traj_in,
00268 approach,
00269 traj_out);
00270 }
00271 }
00272
00273
00274
00275 const double eps_time = 1e-3;
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
00285
00286
00287
00288 if (traj_out.front().time_from_start.isZero())
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
00316
00317 JointNames max_planning_group;
00318
00319
00320 vector<double> max_planning_values;
00321
00322
00323
00324
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
00338 if (min_planning_group.empty()) {return true;}
00339
00340
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
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
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
00441
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
00461 if (1 == traj_in.size()) {return;}
00462
00463
00464
00465
00466 const ros::Duration offset = traj_out.back().time_from_start;
00467
00468
00469 traj_out.pop_back();
00470
00471
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
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
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;
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 }