- DOF(Degrees of Freedom)。自由度。
- js(JointState)。关节状态。专门指存储在/joint_states话题、最后发送到机器人的sensor_msgs/JointState。
client通过moveit::planning_interface::MoveGroupInterfacemoveit::plan(...)规划出一个轨迹后,调用execute(const Plan& plan)执行该轨迹,内中会发送moveit_msgs::ExecuteTrajectoryGoal请求action。对应到server会触发MoveGroupExecuteTrajectoryAction回调exectraj服务的处理例程:executePathCallback。
- 控制器(MoveItControllerManager):moveit四大插件之一。负责把轨迹转换为机器人的关节状态,并发布。默认使用MoveItFakeControllerManager,标识:moveit_fake_controller_manager/MoveItFakeControllerManager。
- 控制器插件包括两个部分:一个派生于MoveItControllerManager的插件对象,一个派生于MoveItControllerHandle,后者负责把轨迹中的point转换为js,并发布到机器人。
- 过程中至少会新建一个线程:TrajectoryExecutionManager::executeThread。如果控制器插件MoveItControllerHandle转换point较复杂,会在executeThread线程内再新建一个线程,内中处理转换point和发布,像ThreadedController::execTrajectory。
一、MoveGroupExecuteTrajectoryAction::executePathCallback
@goal[IN]。plan生成的轨迹。
void MoveGroupExecuteTrajectoryAction::executePathCallback(const moveit_msgs::ExecuteTrajectoryGoalConstPtr& goal)
{
moveit_msgs::ExecuteTrajectoryResult action_res;
if (!context_->trajectory_execution_manager_)
{不应该进入这入口。trajectory_execution_manager_类型是TrajectoryExecutionManager,内中有个成员controller_manager_,存储着当前正使用的控制器插件。
const std::string response = "Cannot execute trajectory since ~allow_trajectory_execution was set to false"; action_res.error_code.val = moveit_msgs::MoveItErrorCodes::CONTROL_FAILED; execute_action_server_->setAborted(action_res, response); return; } // execute主逻辑,后面有展开。 executePath(goal, action_res); const std::string response = getActionResultString(action_res.error_code, false, false);
getActionResultString把用状态码(action_res.error_code)表示的执行结果转换为字符串表示,不影响主逻辑。
if (action_res.error_code.val == moveit_msgs::MoveItErrorCodes::SUCCESS)
{
execute_action_server_->setSucceeded(action_res, response);
}
else if (action_res.error_code.val == moveit_msgs::MoveItErrorCodes::PREEMPTED)
{
execute_action_server_->setPreempted(action_res, response);
}
else
{
execute_action_server_->setAborted(action_res, response);
}
setExecuteTrajectoryState(IDLE);向client发布feedback话题,表示目前的execute进度是结束了。不影响主逻辑
}
二、MoveGroupExecuteTrajectoryAction::executePath
- @goal[IN]。plan生成的轨迹。内容就一个trajectory字段,其类型moveit_msgs/RobotTrajectory。RobotTrajectory要封装成ExecuteTrajectoryGoal,目的是命名上符合action规范。
- @action_res[OUT]。存储执行结果。
void MoveGroupExecuteTrajectoryAction::executePath(const moveit_msgs::ExecuteTrajectoryGoalConstPtr& goal,
moveit_msgs::ExecuteTrajectoryResult& action_res)
{
ROS_INFO_NAMED(getName(), "Execution request received");
context_->trajectory_execution_manager_->clear();
if (context_->trajectory_execution_manager_->push(goal->trajectory))
{
setExecuteTrajectoryState(MONITOR);
context_->trajectory_execution_manager_->execute();
moveit_controller_manager::ExecutionStatus status = context_->trajectory_execution_manager_->waitForExecution();trajectory_execution_manager_->push会调用configure,内中调用TrajectoryExecutionManager::distributeTrajectory。
setExecuteTrajectoryState只是feedback到client,不影响server逻辑。以下的execute()是主逻辑。distributeTrajectory
顾名思议,waitForExecution作用是同步等待执行结束。它是通过等待信号量execution_complete_condition_,trajectory_execution_manager_->execute()执行结束后,会把它置为有信号。——同步不包括等待ThreadedController::execTrajectory线程发布/joint_states。
if (status == moveit_controller_manager::ExecutionStatus::SUCCEEDED)
{
action_res.error_code.val = moveit_msgs::MoveItErrorCodes::SUCCESS;
} else if (...) {一系列if/else,把存储在status中的状态转换为action_res.error_code.val表示的状态。
...
} else {
action_res.error_code.val = moveit_msgs::MoveItErrorCodes::CONTROL_FAILED;
}
ROS_INFO_STREAM_NAMED(getName(), "Execution completed: " << status.asString());
}
else
{
action_res.error_code.val = moveit_msgs::MoveItErrorCodes::CONTROL_FAILED;
}
}
三、TrajectoryExecutionManager::push
每个新来的执行任务会创建一个私有上下文对象TrajectoryExecutionContext,并放入trajectories_。
bool TrajectoryExecutionManager::push(const moveit_msgs::RobotTrajectory& trajectory,
const std::vector<std::string>& controllers)
{
if (!execution_complete_)
{
ROS_ERROR_NAMED(name_, "Cannot push a new trajectory while another is being executed");
return false;
}
TrajectoryExecutionContext* context = new TrajectoryExecutionContext();
if (configure(*context, trajectory, controllers))
{每个新来的执行任务会创建一个私有上下文对象TrajectoryExecutionContext,configure(...)作用是设置好这个上下文的相关变量。
...
trajectories_.push_back(context);
return true;
}
else
{
delete context;
last_execution_status_ = moveit_controller_manager::ExecutionStatus::ABORTED;
}
return false;
}
四、TrajectoryExecutionManager::execute
- @callback[IN]。不填时empty。针对示例,是empty。
- @auto_clear[IN]。不填时true。针对示例,是true。
void TrajectoryExecutionManager::execute(const ExecutionCompleteCallback& callback, bool auto_clear)
{
execute(callback, PathSegmentCompleteCallback(), auto_clear);
}
- @callback。
- @part_callback。
- @auto_clear。
void TrajectoryExecutionManager::execute(const ExecutionCompleteCallback& callback,
const PathSegmentCompleteCallback& part_callback, bool auto_clear)
{
stopExecution(false);
// check whether first trajectory starts at current robot state
if (!trajectories_.empty() && !validate(*trajectories_.front()))
{validate(...)会要求1秒内必须收到过/joint_states话题。
last_execution_status_ = moveit_controller_manager::ExecutionStatus::ABORTED; if (auto_clear) clear(); if (callback) callback(last_execution_status_); return; } // start the execution thread execution_complete_ = false; execution_thread_ = std::make_unique<boost::thread>(&TrajectoryExecutionManager::executeThread, this, callback, part_callback, auto_clear); }
五、TrajectoryExecutionManager::executeThread
在executeThread线程执行操作机械臂。
void TrajectoryExecutionManager::executeThread(const ExecutionCompleteCallback& callback,
const PathSegmentCompleteCallback& part_callback, bool auto_clear)
{
// if we already got a stop request before we even started anything, we abort
if (execution_complete_)
{
last_execution_status_ = moveit_controller_manager::ExecutionStatus::ABORTED;
if (callback)
callback(last_execution_status_);
return;
}
ROS_DEBUG_NAMED(name_, "Starting trajectory execution ...");
// assume everything will be OK
last_execution_status_ = moveit_controller_manager::ExecutionStatus::SUCCEEDED;
// execute each trajectory, one after the other (executePart() is blocking) or until one fails.
// on failure, the status is set by executePart(). Otherwise, it will remain as set above (success)
std::size_t i = 0;
for (; i < trajectories_.size(); ++i)
{
bool epart = executePart(i);
if (epart && part_callback)
part_callback(i);
if (!epart || execution_complete_)
{
++i;
break;
}
}
// only report that execution finished successfully when the robot actually stopped moving
if (last_execution_status_ == moveit_controller_manager::ExecutionStatus::SUCCEEDED)
waitForRobotToStop(*trajectories_[i - 1]);
ROS_INFO_NAMED(name_, "Completed trajectory execution with status %s ...", last_execution_status_.asString().c_str());
// notify whoever is waiting for the event of trajectory completion
execution_state_mutex_.lock();
execution_complete_ = true;
execution_state_mutex_.unlock();
execution_complete_condition_.notify_all();
// clear the paths just executed, if needed
if (auto_clear)
clear();
// call user-specified callback
if (callback)
callback(last_execution_status_);
}
六、TrajectoryExecutionManager::executePart
“Part”指moveit可能累积了数个exeucte(...),part是当中的哪一个。part_index指向了此刻要执行的、它在trajectories_这个vector内的索引。
<libros>/moveit_ros/planning/trajectory_execution_manager/src/trajectory_execution_manager.cpp
bool TrajectoryExecutionManager::executePart(std::size_t part_index)
{
TrajectoryExecutionContext& context = *trajectories_[part_index];
...
for (std::size_t i = 0; i < context.trajectory_parts_.size(); ++i)
{
bool ok = false;
try
{
ok = active_handles_[i]->sendTrajectory(context.trajectory_parts_[i]);
}
catch (std::exception& ex)
{
ROS_ERROR_NAMED(name_, "Caught %s when sending trajectory to controller", ex.what());
}
if (!ok)
{
for (std::size_t j = 0; j < i; ++j)
try
{
active_handles_[j]->cancelExecution();
}
catch (std::exception& ex)
{
ROS_ERROR_NAMED(name_, "Caught %s when canceling execution", ex.what());
}
ROS_ERROR_NAMED(name_, "Failed to send trajectory part %zu of %zu to controller %s", i + 1,
context.trajectory_parts_.size(), active_handles_[i]->getName().c_str());
if (i > 0)
ROS_ERROR_NAMED(name_, "Cancelling previously sent trajectory parts");
active_handles_.clear();
current_context_ = -1;
last_execution_status_ = moveit_controller_manager::ExecutionStatus::ABORTED;
return false;
}
}
...
}
七、ThreadedController::sendTrajectory
ThreadedController::sendTrajectory是moveit_fake_controller_manager中私有函数,而且不是插件MoveItControllerManager的虚函数,那TrajectoryExecutionManager是怎么直接调用到它的?——私有控制器除提供派生于MoveItControllerManager的插件对象,还需要提供派生于moveit_controller_manager::MoveItControllerHandle的point处理对象。至于moveit是怎么把同属于该控制器的MoveItControllerManager、MoveItControllerHandle关联在一起?——MoveItControllerManager须实现的虚函数getController。
virtual MoveItControllerHandlePtr getControllerHandle(const std::string& name) = 0;
对如何把轨迹中point转换到能包发布到机器人的js,MoveItFakeControllerManager提供了三种可选方法,何一时刻只能选一种。
- LastPointController。立即把原值作为js发送向关节。因为操作可立即、快速结束,此种场景不创建第二线程。
- ViaPointController。每个点有个time_from_start字段,指示该point要发送到机器人的时刻。选择这种时,时间必须已到这个时刻,才把原值为作为js发送向关节。
- InterpolatingController(默认)。根据前后两个point算出一个均值,作为发送向关节的js。
<libros>/moveit_plugins/moveit_fake_controller_manager/src/moveit_fake_controllers.cpp
bool ThreadedController::sendTrajectory(const moveit_msgs::RobotTrajectory& t)
{
cancelTrajectory(); // cancel any previous fake motion
cancel_ = false;
status_ = moveit_controller_manager::ExecutionStatus::PREEMPTED;
thread_ = boost::thread(std::bind(&ThreadedController::execTrajectory, this, t));
return true;
}
八、InterpolatingController::execTrajectory
<libros>/moveit_plugins/moveit_fake_controller_manager/src/moveit_fake_controllers.cpp
void InterpolatingController::execTrajectory(const moveit_msgs::RobotTrajectory& t)
{
ROS_INFO("Fake execution of trajectory");
if (t.joint_trajectory.points.empty())
return;
sensor_msgs::JointState js;
js.header = t.joint_trajectory.header;
js.name = t.joint_trajectory.joint_names;
const std::vector<trajectory_msgs::JointTrajectoryPoint>& points = t.joint_trajectory.points;
std::vector<trajectory_msgs::JointTrajectoryPoint>::const_iterator prev = points.begin(), // previous via point
next = points.begin() + 1, // currently targetted via point
end = points.end();
ros::Time start_time = ros::Time::now();
while (!cancelled())
{
ros::Duration elapsed = ros::Time::now() - start_time;
// hop to next targetted via point
while (next != end && elapsed > next->time_from_start)
{
++prev;
++next;
}
if (next == end)
break;
double duration = (next->time_from_start - prev->time_from_start).toSec();
ROS_DEBUG("elapsed: %.3f via points %td,%td / %td alpha: %.3f", elapsed.toSec(), prev - points.begin(),
next - points.begin(), end - points.begin(),
duration > std::numeric_limits<double>::epsilon() ? (elapsed - prev->time_from_start).toSec() / duration :
1.0);
interpolate(js, *prev, *next, elapsed);根据前、后两个关节算出要插入的关节点。
js.header.stamp = ros::Time::now();
pub_.publish(js);
rate_.sleep();
}
if (cancelled())
return;
ros::Duration elapsed = ros::Time::now() - start_time;
ROS_DEBUG("elapsed: %.3f via points %td,%td / %td alpha: 1.0", elapsed.toSec(), prev - points.begin(),
next - points.begin(), end - points.begin());
// publish last point
interpolate(js, *prev, *prev, prev->time_from_start);
js.header.stamp = ros::Time::now();
pub_.publish(js);
ROS_DEBUG("Fake execution of trajectory: done");
}
九、MoveGroupExecuteTrajectoryAction::setExecuteTrajectoryState
- @state[IN]。要feedback到client的状态。
void MoveGroupExecuteTrajectoryAction::setExecuteTrajectoryState(MoveGroupState state)
{
moveit_msgs::ExecuteTrajectoryFeedback execute_feedback;
execute_feedback.state = stateToStr(state);
execute_action_server_->publishFeedback(execute_feedback);
}不要被函数名set字眼混淆了,它只是把执行状态通过feedback话题发布到client,不会改变server端MoveGroupExecuteTrajectoryAction状态,可说没有它也不会影响server端的执行逻辑。