- 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 invalid
trajectory_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; }