- mgi:moveit::planning_interface::MoveGroupInterface
- mgii:moveit::planning_interface::MoveGroupInterface::MoveGroupInterfaceImpl
抓取指机器人夹起工作空间内某个物体,放置指机器人把夹着的物体放到目标位置。处理它们的MoveGroupCapability是MoveGroupPickPlaceAction,在后者,对应实现了两个action server来进行处理,分别是pickup_action_server_、place_action_server_。
| 抓取 | 放置 | |
| actionlib server | pickup_action_server_ | place_action_server_ |
| 字符串标识 | PICKUP_ACTION = "pickup" | PLACE_ACTION = "place" |
| 触发回调 | executePickupCallback | executePlaceCallback |
| actionlib client | pick_action_client_ | place_action_client_ |
| mgi的api | pick(const moveit_msgs::PickupGoal& goal) | place(const moveit_msgs::PlaceGoal& goal) |
| mgii | pick(const moveit_msgs::PickupGoal& goal)(注1) | place(const moveit_msgs::PlaceGoal& goal) |
- 注1:mgi还提供其它参数简的api,像pick(const std::string& object, bool plan_only),这时mgi会根据object、plan_only,用填充默认参数生成PickupGoal。
上面所说的放置、抓取是相互独立的两个操作,实际使用时往往是把它们合在一起认为一个动作,那mgii是否提供了认为是一个操作api?——没有。
moveit::core::MoveItErrorCode planGraspsAndPick(const moveit_msgs::CollisionObject& object, bool plan_only);
它处理的应该是是抓取。调用这个api时,caller需实现一个字符串标识是“plan_grasps”的actionlib Server。在抓取前,mgi会先调用plan_grasps server的一个自定义名称(名称存储在support_surface_变量)的服务,成功后再调用pick。
更具体说下抓放是怎么个过程。urdf定义两个判划组:robot_arm、robot_claw。robot_arm是手臂部分,需定义运动学求解器。robot_claw则是爪子部分,没有运动学求解器,但须以它为group值定义一个未端执行器effetor,此个effector的parent_link需要是robot_arm的末端link,假设这个effetor的名称是robot_eff。物体要放到的位姿记为。
- 在工作空间定义一个物体,放在world树。物体标识记为box1,它现在在base_link坐标系的位姿
。
- [pick]。在robot_arm规划组,规划出一条从当前位置到能夹起box1的路径,并执行。
- 控制robot_arm规划组,夹起box1。把box1加到机器人树,同时从world树中移除。
- [place]。在robot_arm规划组,规定出一条从当前位置到
的路径,并执行。
- 控制robot_arm规划组,放下box1。
一、client
class MoveGroupTestFixture
{
public:
...
void ModifyPlanningSceneAsyncInterfaces()
{
////////////////////////////////////////////////////////////////////
// Define a collision object ROS message.
moveit_msgs::CollisionObject collision_object;
collision_object.header.frame_id = move_group_.getPlanningFrame();
// The id of the object is used to identify it.
collision_object.id = "box1";
// Define a box to add to the world.
shape_msgs::SolidPrimitive primitive;
primitive.type = primitive.BOX;
primitive.dimensions.resize(3);
primitive.dimensions[0] = 0.1;
primitive.dimensions[1] = 1.0;
primitive.dimensions[2] = 1.0;
// Define a pose for the box (specified relative to frame_id)
geometry_msgs::Pose box_pose;
box_pose.orientation.w = 1.0;
box_pose.position.x = 0.5;
box_pose.position.y = 0.0;
box_pose.position.z = 0.5;box1的位姿和抓、放可说没关系,它影响的是box1和其它障碍物碰撞检测的结果。它和dimensions一起决定了box1在层级树中的包围盒数值。
collision_object.primitives.push_back(primitive);
collision_object.primitive_poses.push_back(box_pose);
collision_object.operation = collision_object.ADD;
std::vector<moveit_msgs::CollisionObject> collision_objects;
collision_objects.push_back(collision_object);
// Now, let's add the collision object into the world
synchronizeGeometryUpdate([&]() { planning_scene_interface_.addCollisionObjects(collision_objects); });
size_t s = planning_scene_interface_.getObjects().size();
VALIDATE(s == collision_objects.size(), null_str);
}
void attachObject()
{
// attach and detach collision object
synchronizeGeometryUpdate([&]() {
bool ret = move_group_.attachObject("box1", "grasping_frame"); // base_link
VALIDATE(ret, null_str);
});
size_t s = planning_scene_interface_.getAttachedObjects().size();
VALIDATE(s == 1, null_str);
}
private:
moveit::planning_interface::MoveGroupInterface& move_group_;
moveit::planning_interface::PlanningSceneInterface planning_scene_interface_;
planning_scene_monitor::PlanningSceneMonitorPtr psm_;
};MoveGroupTestFixture构造函数、synchronizeGeometryUpdate的实现见“规划[4/5]:避障”。
moveit::planning_interface::MoveGroupInterface::Options opt("robot_arm", "robot_description", nh);
moveit::planning_interface::MoveGroupInterface group(opt);
MoveGroupTestFixture test(group, cbqueue);
test.ModifyPlanningSceneAsyncInterfaces();不论抓取还是放置,都先执行上面的<4行代码>。至此,group针对的是“robot_arm”规划组,物体box1增加到了world树。
1.1 抓取
void fillGrasps(moveit_msgs::PickupGoal& goal, const Eigen::Isometry3d& desire_pose)
{
ROS_DEBUG_NAMED("manipulation", "Using default grasp poses");
goal.minimize_object_distance = true;
const Eigen::Vector3d& trans = desire_pose.translation();
const Eigen::Quaterniond quat(desire_pose.rotation());
SDL_Log("{fillGrasps}desire_pose: %s", verbose_Isometry3d(desire_pose).c_str());
// add a number of default grasp points
// \todo add more!
moveit_msgs::Grasp g;
// g.grasp_pose.header.frame_id = goal.target_name;
g.grasp_pose.header.frame_id = "base_link";
g.grasp_pose.pose.position.x = trans.x();
g.grasp_pose.pose.position.y = trans.y();
g.grasp_pose.pose.position.z = trans.z();
g.grasp_pose.pose.orientation.x = quat.x();
g.grasp_pose.pose.orientation.y = quat.y();
g.grasp_pose.pose.orientation.z = quat.z();
g.grasp_pose.pose.orientation.w = quat.w();
// g.pre_grasp_approach.direction.header.frame_id = lscene->getPlanningFrame();
g.pre_grasp_approach.direction.header.frame_id = "base_link";
g.pre_grasp_approach.direction.vector.x = 1.0;
g.pre_grasp_approach.min_distance = 0.1;
g.pre_grasp_approach.desired_distance = 0.2;
// g.post_grasp_retreat.direction.header.frame_id = lscene->getPlanningFrame();
g.post_grasp_retreat.direction.header.frame_id = "base_link";
g.post_grasp_retreat.direction.vector.z = 1.0;
g.post_grasp_retreat.min_distance = 0.1;
g.post_grasp_retreat.desired_distance = 0.2;
{
int ii = 0;
g.pre_grasp_approach.direction.vector.x = 0.0;
g.post_grasp_retreat.direction.vector.z = 0.0;
}
goal.possible_grasps.push_back(g);
}
void test_pick()
{
<4行代码>
moveit_msgs::PickupGoal goal = group.constructPickupGoal("box1", std::vector<moveit_msgs::Grasp>(), false);
fillGrasps(goal, box1_near_pose);box1_near_pose即,移到它时,就可夹住box1。
goal.minimize_object_distance = 0; moveit::planning_interface::MoveItErrorCode success = group.pick(goal); }
1.2 放置
void test_place()
{
<4行代码>
test.attachObject();先把box1加到机器人树,同时移出world树。
geometry_msgs::PoseStamped pose; pose.header.stamp = ros::Time::now(); pose.header.frame_id = "base_link"; pose.pose = tf2::toMsg(place_pose);
place_pose即,是希望把物体放置到的位姿。
std::vector pls = group.posesToPlaceLocations({ pose });
moveit_msgs::PlaceLocation& pl = pls.back();
pl.pre_place_approach.direction.vector.x = 0.0;
pl.pre_place_approach.direction.vector.y = 0.0;
pl.pre_place_approach.direction.vector.z = 0.0;
pl.post_place_retreat.direction.vector.x = 0.0;
pl.post_place_retreat.direction.vector.y = 0.0;
pl.post_place_retreat.direction.vector.z = 0.0;
moveit_msgs::PlaceGoal goal = group.constructPlaceGoal("box1", pls, false);
goal.place_eef = 1;
moveit::planning_interface::MoveItErrorCode success = group.place(goal);
}
二、server
2.1 PlanExecution::planAndExecuteHelper
planAndExecuteHelper是pick、place都要执行的逻辑,是个同步函数,会等到机器人操作完后再返回。它依次执行两个任务。
- opt.plan_callback_。规划出此次pick或place的路径轨迹。opt.plan_callback_是个函数指针,pick和place指向了不同函数。
- executeAndMonitor。让机器人按上面得出的路径去操作,并同步等到机器人操作完后再返回。
void plan_execution::PlanExecution::planAndExecuteHelper(ExecutableMotionPlan& plan, const Options& opt)
{
// perform initial configuration steps & various checks
preempt_.checkAndClear(); // clear any previous preempt_ request
bool preempt_requested = false;
// run the actual motion plan & execution
unsigned int max_replan_attempts =
opt.replan_ ? (opt.replan_attempts_ > 0 ? opt.replan_attempts_ : default_max_replan_attempts_) : 1;
unsigned int replan_attempts = 0;
bool previously_solved = false;
// run a planning loop for at most the maximum replanning attempts;
// re-planning is executed only in case of known types of failures (e.g., environment changed)
do
{
replan_attempts++;
ROS_INFO_NAMED("plan_execution", "Planning attempt %u of at most %u", replan_attempts, max_replan_attempts);
if (opt.before_plan_callback_)
opt.before_plan_callback_();
new_scene_update_ = false; // we clear any scene updates to be evaluated because we are about to compute a new
// plan, which should consider most recent updates already
// if we never had a solved plan, or there is no specified way of fixing plans, just call the planner; otherwise,
// try to repair the plan we previously had;
bool solved =
(!previously_solved || !opt.repair_plan_callback_) ?
opt.plan_callback_(plan) :
opt.repair_plan_callback_(plan, trajectory_execution_manager_->getCurrentExpectedTrajectoryIndex());previously_solved初值是false,因而总会调用opt.plan_callback_,这是一个函数指针。
pick时,opt.plan_callback_指向MoveGroupPickPlaceAction::planUsingPickPlacePickup,后者依次执行两个步骤。
- 调用PickPlace::planPick。这是主要操作,planPick执行着pick规划,假设变量pick_plan是它的返回值,类型pick_place::PickPlan,派生于PickPlacePlanBase。pick_plan->pipeline_.success_存储着规划出的路径。success_类型是std::vector<ManipulationPlan>,其中success_.back()的认为最好的那个ManipulationPlan,记为result。注:当client生成moveit_msgs::PickupGoal时填的plan=true,即只计算规划路径,不执行,那它就是直接调用PickPlace::planPick,得到路径后转换为result.trajectories_转成client希望的moveit_msgs::RobotTrajectory,并存储在action_res.trajectory_stages,就结束了。
- plan.plan_components_ = result。plan就是opt.plan_callback_传下参数。
place时,opt.plan_callback_指向MoveGroupPickPlaceAction::planUsingPickPlacePlace,后者依次执行两个步骤。
- 调用PickPlace::planPlace。这是主要操作,planPlace执行着place规划,假设变量place_plan是它的返回值,类型pick_place::place_plan,它和PickPlan一样,派生于PickPlacePlanBase。和PickPlan一样,success_.back()是认为最好的那个ManipulationPlan,记为result。注:当client生成moveit_msgs::PlaceGoal时填的plan=true,即只计算规划路径,不执行,那它就是直接调用PickPlace::planPlace,得到路径后转换为result.trajectories_转成client希望的moveit_msgs::RobotTrajectory,并存储在action_res.trajectory_stages,就结束了。
- plan.plan_components_ = result。
至此plan.plan_components_存储着规划出的路径,接下就是要让机器人按这路径去执行。
preempt_requested = preempt_.checkAndClear();
if (preempt_requested)
break;
// if planning fails in a manner that is not recoverable, we exit the loop,
// otherwise, we attempt to continue, if replanning attempts are left
if (plan.error_code_.val == moveit_msgs::MoveItErrorCodes::PLANNING_FAILED ||
plan.error_code_.val == moveit_msgs::MoveItErrorCodes::INVALID_MOTION_PLAN ||
plan.error_code_.val == moveit_msgs::MoveItErrorCodes::UNABLE_TO_AQUIRE_SENSOR_DATA)
{
if (plan.error_code_.val == moveit_msgs::MoveItErrorCodes::UNABLE_TO_AQUIRE_SENSOR_DATA && opt.replan_delay_ > 0.0)
{
ros::WallDuration d(opt.replan_delay_);
d.sleep();
}
continue;
}
// abort if no plan was found
if (solved)
previously_solved = true;
else
break;
if (plan.error_code_.val == moveit_msgs::MoveItErrorCodes::SUCCESS)
{
if (opt.before_execution_callback_)
opt.before_execution_callback_();
preempt_requested = preempt_.checkAndClear();
if (preempt_requested)
break;
// execute the trajectory, and monitor its execution
plan.error_code_ = executeAndMonitor(plan, false);executeAndMonitor功能是让机器人按上面得出的路径去操作,并同步等到机器人操作完后再返回。
}
if (plan.error_code_.val == moveit_msgs::MoveItErrorCodes::PREEMPTED)
preempt_requested = true;
// if execution succeeded or failed in a manner that we do not consider recoverable, we exit the loop (with failure)
if (plan.error_code_.val != moveit_msgs::MoveItErrorCodes::MOTION_PLAN_INVALIDATED_BY_ENVIRONMENT_CHANGE)
break;
else
{
// otherwise, we wait (if needed)
if (opt.replan_delay_ > 0.0)
{
ROS_INFO_NAMED("plan_execution", "Waiting for a %lf seconds before attempting a new plan ...",
opt.replan_delay_);
ros::WallDuration d(opt.replan_delay_);
d.sleep();
ROS_INFO_NAMED("plan_execution", "Done waiting");
}
}
preempt_requested = preempt_.checkAndClear();
if (preempt_requested)
break;
} while (replan_attempts < max_replan_attempts);
if (preempt_requested)
{
ROS_DEBUG_NAMED("plan_execution", "PlanExecution was preempted");
plan.error_code_.val = moveit_msgs::MoveItErrorCodes::PREEMPTED;
}
if (opt.done_callback_)
opt.done_callback_();
if (plan.error_code_.val == moveit_msgs::MoveItErrorCodes::SUCCESS)
ROS_DEBUG_NAMED("plan_execution", "PlanExecution finished successfully.");
else
ROS_DEBUG_NAMED("plan_execution", "PlanExecution terminating with error code %d - '%s'", plan.error_code_.val,
getErrorCodeString(plan.error_code_).c_str());
}2.2 PlanExecution::executeAndMonitor
输入参数plan.plan_components_存储着pick或place规划出的路径,executeAndMonitor负责让机器人按这路径去操作,要等操作完后,函数才返回。既然是按路径去操作机器人,那和“server处理exectraj action:MoveGroupExecuteTrajectoryAction”(https://www.cswamp.com/post/123)中说的有很多相似之处。因为那里介绍得更详细,可以看完那里后再细看这函数。
moveit_msgs::MoveItErrorCodes plan_execution::PlanExecution::executeAndMonitor(ExecutableMotionPlan& plan,
bool reset_preempted)
{
if (reset_preempted)
preempt_.checkAndClear();
if (!plan.planning_scene_monitor_)
plan.planning_scene_monitor_ = planning_scene_monitor_;
if (!plan.planning_scene_)
plan.planning_scene_ = planning_scene_monitor_->getPlanningScene();
moveit_msgs::MoveItErrorCodes result;
// try to execute the trajectory
execution_complete_ = true;
if (!trajectory_execution_manager_)
{
ROS_ERROR_NAMED("plan_execution", "No trajectory execution manager");
result.val = moveit_msgs::MoveItErrorCodes::CONTROL_FAILED;
return result;
}
if (plan.plan_components_.empty())
{
result.val = moveit_msgs::MoveItErrorCodes::SUCCESS;
return result;
}
execution_complete_ = false;
// push the trajectories we have slated for execution to the trajectory execution manager
int prev = -1;
for (std::size_t i = 0; i < plan.plan_components_.size(); ++i)
{plan.plan_components_可能包含多个路径段,每个路径段对应push到TrajectoryExecutionManager,生成一个Part。
// \todo should this be in trajectory_execution ? Maybe. Then that will have to use kinematic_trajectory too;
// spliting trajectories for controllers becomes interesting: tied to groups instead of joints. this could cause
// some problems
// in the meantime we do a hack:
bool unwound = false;
for (std::size_t j = 0; j < i; ++j)
// if we ran unwind on a path for the same group
if (plan.plan_components_[j].trajectory_ &&
plan.plan_components_[j].trajectory_->getGroup() == plan.plan_components_[i].trajectory_->getGroup() &&
!plan.plan_components_[j].trajectory_->empty())
{
plan.plan_components_[i].trajectory_->unwind(plan.plan_components_[j].trajectory_->getLastWayPoint());
unwound = true;
break;
}
if (!unwound)
{
// unwind the path to execute based on the current state of the system
if (prev < 0)
plan.plan_components_[i].trajectory_->unwind(
plan.planning_scene_monitor_ && plan.planning_scene_monitor_->getStateMonitor() ?
*plan.planning_scene_monitor_->getStateMonitor()->getCurrentState() :
plan.planning_scene_->getCurrentState());
else
plan.plan_components_[i].trajectory_->unwind(plan.plan_components_[prev].trajectory_->getLastWayPoint());
}
if (plan.plan_components_[i].trajectory_ && !plan.plan_components_[i].trajectory_->empty())
prev = i;
// convert to message, pass along
moveit_msgs::RobotTrajectory msg;
plan.plan_components_[i].trajectory_->getRobotTrajectoryMsg(msg);
if (!trajectory_execution_manager_->push(msg, plan.plan_components_[i].controller_names_))
{
trajectory_execution_manager_->clear();
ROS_ERROR_STREAM_NAMED("plan_execution", "Apparently trajectory initialization failed");
execution_complete_ = true;
result.val = moveit_msgs::MoveItErrorCodes::CONTROL_FAILED;
return result;
}
}
if (!trajectory_monitor_ && planning_scene_monitor_->getStateMonitor())
{
// Pass current value of reconfigurable parameter plan_execution/record_trajectory_state_frequency
double sampling_frequency = 0.0;
node_handle_.getParam("plan_execution/record_trajectory_state_frequency", sampling_frequency);
trajectory_monitor_ = std::make_shared<planning_scene_monitor::TrajectoryMonitor>(
planning_scene_monitor_->getStateMonitor(), sampling_frequency);
}
// start recording trajectory states
if (trajectory_monitor_)
trajectory_monitor_->startTrajectoryMonitor();
// start a trajectory execution thread
trajectory_execution_manager_->execute(
std::bind(&PlanExecution::doneWithTrajectoryExecution, this, std::placeholders::_1),
std::bind(&PlanExecution::successfulTrajectorySegmentExecution, this, &plan, std::placeholders::_1));
// wait for path to be done, while checking that the path does not become invalidtrajectory_execution_manager_->execute(...)不是一个同步函数,它只是启动TrajectoryExecutionManager::executeThread线程,就返回了。要做到同步,后面代码需等待TrajectoryExecutionManager::executeThread线程执行结束。
在MoveGroupExecuteTrajectoryAction,用的同步方法是调用“context_->trajectory_execution_manager_->waitForExecution()”,这里则是利用了
executeThread结束时会调用这里设置的回调doneWithTrajectoryExecution,该函数只一个动作,把execution_complete_设为true。用了它后,executeAndMonitor通过轮询execution_complete_,来判断executeThread是否已结束。
另一个回调successfulTrajectorySegmentExecution是干什么用的?——executeThread在执行完一个“executePart(...)”后,会回调它。它内中有个操作,如果接下要执行的路径段是不可到达的,则把path_became_invalid_设为true。
ros::Rate r(100);
path_became_invalid_ = false;
bool preempt_requested = false;
while (node_handle_.ok() && !execution_complete_ && !path_became_invalid_)
{execution_complete_==false,意味着还没调用过doneWithTrajectoryExecution,须继续等待executeThread线程结束。
path_became_invalid_==true,意味着后绪路径无效,不必再发送到机器人了。如果因它退出while,并不意味着executeThread线程结束,后面需手动调用stopExecution()来结束executeThread。
r.sleep();
// check the path if there was an environment update in the meantime
if (new_scene_update_)
{
new_scene_update_ = false;
std::pair<int, int> current_index = trajectory_execution_manager_->getCurrentExpectedTrajectoryIndex();
if (!isRemainingPathValid(plan, current_index))
{
ROS_INFO_NAMED("plan_execution", "Trajectory component '%s' is invalid after scene update",
plan.plan_components_[current_index.first].description_.c_str());
path_became_invalid_ = true;
break;
}
}
preempt_requested = preempt_.checkAndClear();
if (preempt_requested)
break;
}能到这里,应该是结束executeThread线程(execution_complete_ = true),或出现路径错误(path_became_invalid_=true)。
// stop execution if needed
if (preempt_requested)
{
ROS_INFO_NAMED("plan_execution", "Stopping execution due to preempt request");
trajectory_execution_manager_->stopExecution();
}
else if (path_became_invalid_)
{
ROS_INFO_NAMED("plan_execution", "Stopping execution because the path to execute became invalid"
"(probably the environment changed)");
trajectory_execution_manager_->stopExecution();路径错误(path_became_invalid_=true)的话,并不意味着executeThread线程结束,需手动调用stopExecution()来结束executeThread。
}
else if (!execution_complete_)
{
ROS_WARN_NAMED("plan_execution", "Stopping execution due to unknown reason."
"Possibly the node is about to shut down.");
trajectory_execution_manager_->stopExecution();
}
// stop recording trajectory states
if (trajectory_monitor_)
{
trajectory_monitor_->stopTrajectoryMonitor();
plan.executed_trajectory_ =
std::make_shared<robot_trajectory::RobotTrajectory>(planning_scene_monitor_->getRobotModel(), "");
trajectory_monitor_->swapTrajectory(*plan.executed_trajectory_);
}
// decide return value
if (path_became_invalid_)
result.val = moveit_msgs::MoveItErrorCodes::MOTION_PLAN_INVALIDATED_BY_ENVIRONMENT_CHANGE;
else
{
if (preempt_requested)
{
result.val = moveit_msgs::MoveItErrorCodes::PREEMPTED;
}
else
{
if (trajectory_execution_manager_->getLastExecutionStatus() ==
moveit_controller_manager::ExecutionStatus::SUCCEEDED)
result.val = moveit_msgs::MoveItErrorCodes::SUCCESS;
else if (trajectory_execution_manager_->getLastExecutionStatus() ==
moveit_controller_manager::ExecutionStatus::TIMED_OUT)
result.val = moveit_msgs::MoveItErrorCodes::TIMED_OUT;
else
result.val = moveit_msgs::MoveItErrorCodes::CONTROL_FAILED;
}
}
return result;
}