controller_action.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2018, Magazino GmbH, Sebastian Pütz, Jorge Santos Simón
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following
13  * disclaimer in the documentation and/or other materials provided
14  * with the distribution.
15  *
16  * 3. Neither the name of the copyright holder nor the names of its
17  * contributors may be used to endorse or promote products derived
18  * from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31  * POSSIBILITY OF SUCH DAMAGE.
32  *
33  * controller_action.cpp
34  *
35  * authors:
36  * Sebastian Pütz <spuetz@uni-osnabrueck.de>
37  * Jorge Santos Simón <santos@magazino.eu>
38  *
39  */
40 
42 
43 namespace mbf_abstract_nav
44 {
45 
47  const std::string &action_name,
48  const mbf_utility::RobotInformation &robot_info)
49  : AbstractActionBase(action_name, robot_info, boost::bind(&mbf_abstract_nav::ControllerAction::run, this, _1, _2))
50 {
51 }
52 
54  GoalHandle &goal_handle,
55  typename AbstractControllerExecution::Ptr execution_ptr
56 )
57 {
58  if(goal_handle.getGoalStatus().status == actionlib_msgs::GoalStatus::RECALLING)
59  {
60  goal_handle.setCanceled();
61  return;
62  }
63 
64  uint8_t slot = goal_handle.getGoal()->concurrency_slot;
65 
66  bool update_plan = false;
67  slot_map_mtx_.lock();
68  std::map<uint8_t, ConcurrencySlot>::iterator slot_it = concurrency_slots_.find(slot);
69  if(slot_it != concurrency_slots_.end() && slot_it->second.in_use)
70  {
71  boost::lock_guard<boost::mutex> goal_guard(goal_mtx_);
72  if(slot_it->second.execution->getName() == goal_handle.getGoal()->controller ||
73  goal_handle.getGoal()->controller.empty())
74  {
75  update_plan = true;
76  // Goal requests to run the same controller on the same concurrency slot already in use:
77  // we update the goal handle and pass the new plan and tolerances from the action to the
78  // execution without stopping it
79  execution_ptr = slot_it->second.execution;
80  execution_ptr->setNewPlan(goal_handle.getGoal()->path.poses,
81  goal_handle.getGoal()->tolerance_from_action,
82  goal_handle.getGoal()->dist_tolerance,
83  goal_handle.getGoal()->angle_tolerance);
84  // Update also goal pose, so the feedback remains consistent
85  goal_pose_ = goal_handle.getGoal()->path.poses.back();
86  mbf_msgs::ExePathResult result;
87  fillExePathResult(mbf_msgs::ExePathResult::CANCELED, "Goal preempted by a new plan", result);
88  concurrency_slots_[slot].goal_handle.setCanceled(result, result.message);
89  concurrency_slots_[slot].goal_handle = goal_handle;
90  concurrency_slots_[slot].goal_handle.setAccepted();
91  }
92  }
93  slot_map_mtx_.unlock();
94  if(!update_plan)
95  {
96  // Otherwise run parent version of this method
97  AbstractActionBase::start(goal_handle, execution_ptr);
98  }
99 }
100 
101 void ControllerAction::run(GoalHandle &goal_handle, AbstractControllerExecution &execution)
102 {
103  goal_mtx_.lock();
104  // Note that we always use the goal handle stored on the concurrency slots map, as it can change when replanning
105  uint8_t slot = goal_handle.getGoal()->concurrency_slot;
106  goal_mtx_.unlock();
107 
108  ROS_DEBUG_STREAM_NAMED(name_, "Start action " << name_);
109 
110  // ensure we don't provide values from previous execution on case of error before filling both poses
111  goal_pose_ = geometry_msgs::PoseStamped();
112  robot_pose_ = geometry_msgs::PoseStamped();
113 
114  ros::NodeHandle private_nh("~");
115 
116  double oscillation_timeout_tmp;
117  private_nh.param("oscillation_timeout", oscillation_timeout_tmp, 0.0);
118  ros::Duration oscillation_timeout(oscillation_timeout_tmp);
119 
120  double oscillation_distance;
121  private_nh.param("oscillation_distance", oscillation_distance, 0.03);
122 
123  mbf_msgs::ExePathResult result;
124  mbf_msgs::ExePathFeedback feedback;
125 
126  typename AbstractControllerExecution::ControllerState state_moving_input;
127  bool controller_active = true;
128 
129  goal_mtx_.lock();
130  const mbf_msgs::ExePathGoal &goal = *(goal_handle.getGoal().get());
131 
132  const std::vector<geometry_msgs::PoseStamped> &plan = goal.path.poses;
133  if (plan.empty())
134  {
135  fillExePathResult(mbf_msgs::ExePathResult::INVALID_PATH, "Controller started with an empty plan!", result);
136  goal_handle.setAborted(result, result.message);
137  ROS_ERROR_STREAM_NAMED(name_, result.message << " Canceling the action call.");
138  controller_active = false;
139  goal_mtx_.unlock();
140  return;
141  }
142 
143  goal_pose_ = plan.back();
144  ROS_DEBUG_STREAM_NAMED(name_, "Called action \""
145  << name_ << "\" with plan:" << std::endl
146  << "frame: \"" << goal.path.header.frame_id << "\" " << std::endl
147  << "stamp: " << goal.path.header.stamp << std::endl
148  << "poses: " << goal.path.poses.size() << std::endl
149  << "goal: (" << goal_pose_.pose.position.x << ", "
150  << goal_pose_.pose.position.y << ", "
151  << goal_pose_.pose.position.z << ")");
152 
153  goal_mtx_.unlock();
154 
155 
156  geometry_msgs::PoseStamped oscillation_pose;
157  ros::Time last_oscillation_reset = ros::Time::now();
158 
159  bool first_cycle = true;
160 
161  while (controller_active && ros::ok())
162  {
163  // goal_handle could change between the loop cycles due to adapting the plan
164  // with a new goal received for the same concurrency slot
165  if (!robot_info_.getRobotPose(robot_pose_))
166  {
167  controller_active = false;
168  fillExePathResult(mbf_msgs::ExePathResult::TF_ERROR, "Could not get the robot pose!", result);
169  goal_mtx_.lock();
170  goal_handle.setAborted(result, result.message);
171  goal_mtx_.unlock();
172  ROS_ERROR_STREAM_NAMED(name_, result.message << " Canceling the action call.");
173  break;
174  }
175 
176  if (first_cycle)
177  {
178  // init oscillation pose
179  oscillation_pose = robot_pose_;
180  }
181 
182  goal_mtx_.lock();
183  state_moving_input = execution.getState();
184 
185  switch (state_moving_input)
186  {
188  execution.setNewPlan(plan, goal.tolerance_from_action, goal.dist_tolerance, goal.angle_tolerance);
189  execution.start();
190  break;
191 
193  ROS_WARN_STREAM_NAMED(name_, "The controller has been stopped rigorously!");
194  controller_active = false;
195  result.outcome = mbf_msgs::ExePathResult::STOPPED;
196  result.message = "Controller has been stopped!";
197  goal_handle.setAborted(result, result.message);
198  break;
199 
201  ROS_INFO_STREAM("Action \"exe_path\" canceled");
202  fillExePathResult(mbf_msgs::ExePathResult::CANCELED, "Controller canceled", result);
203  goal_handle.setCanceled(result, result.message);
204  controller_active = false;
205  break;
206 
208  ROS_DEBUG_STREAM_NAMED(name_, "The moving has been started!");
209  break;
210 
212  if (execution.isPatienceExceeded())
213  {
214  ROS_INFO_STREAM("Try to cancel the plugin \"" << name_ << "\" after the patience time has been exceeded!");
215  if (execution.cancel())
216  {
217  ROS_INFO_STREAM("Successfully canceled the plugin \"" << name_ << "\" after the patience time has been exceeded!");
218  }
219  }
220  break;
221 
223  ROS_WARN_STREAM_NAMED(name_, "The controller has been aborted after it exceeded the maximum number of retries!");
224  controller_active = false;
225  fillExePathResult(execution.getOutcome(), execution.getMessage(), result);
226  goal_handle.setAborted(result, result.message);
227  break;
228 
230  ROS_WARN_STREAM_NAMED(name_, "The controller has been aborted after it exceeded the patience time");
231  controller_active = false;
232  fillExePathResult(mbf_msgs::ExePathResult::PAT_EXCEEDED, execution.getMessage(), result);
233  goal_handle.setAborted(result, result.message);
234  break;
235 
237  ROS_WARN_STREAM_NAMED(name_, "The controller has been started without a plan!");
238  controller_active = false;
239  fillExePathResult(mbf_msgs::ExePathResult::INVALID_PATH, "Controller started without a path", result);
240  goal_handle.setAborted(result, result.message);
241  break;
242 
244  ROS_WARN_STREAM_NAMED(name_, "The controller has received an empty plan");
245  controller_active = false;
246  fillExePathResult(mbf_msgs::ExePathResult::INVALID_PATH, "Controller started with an empty plan", result);
247  goal_handle.setAborted(result, result.message);
248  break;
249 
251  ROS_WARN_STREAM_NAMED(name_, "The controller has received an invalid plan");
252  controller_active = false;
253  fillExePathResult(mbf_msgs::ExePathResult::INVALID_PATH, "Controller started with an invalid plan", result);
254  goal_handle.setAborted(result, result.message);
255  break;
256 
258  ROS_WARN_STREAM_THROTTLE_NAMED(3, name_, "No velocity command received from controller! "
259  << execution.getMessage());
260  publishExePathFeedback(goal_handle, execution.getOutcome(), execution.getMessage(), execution.getVelocityCmd());
261  break;
262 
264  if (!oscillation_timeout.isZero())
265  {
266  // check if oscillating
267  if (mbf_utility::distance(robot_pose_, oscillation_pose) >= oscillation_distance)
268  {
269  last_oscillation_reset = ros::Time::now();
270  oscillation_pose = robot_pose_;
271  }
272  else if (last_oscillation_reset + oscillation_timeout < ros::Time::now())
273  {
274  ROS_WARN_STREAM_NAMED(name_, "The controller is oscillating for "
275  << (ros::Time::now() - last_oscillation_reset).toSec() << "s");
276 
277  execution.cancel();
278  controller_active = false;
279  fillExePathResult(mbf_msgs::ExePathResult::OSCILLATION, "Oscillation detected!", result);
280  goal_handle.setAborted(result, result.message);
281  break;
282  }
283  }
284  publishExePathFeedback(goal_handle, execution.getOutcome(), execution.getMessage(), execution.getVelocityCmd());
285  break;
286 
288  ROS_DEBUG_STREAM_NAMED(name_, "Controller succeeded; arrived at goal");
289  controller_active = false;
290  fillExePathResult(mbf_msgs::ExePathResult::SUCCESS, "Controller succeeded; arrived at goal!", result);
291  goal_handle.setSucceeded(result, result.message);
292  break;
293 
295  ROS_FATAL_STREAM_NAMED(name_, "Internal error: Unknown error thrown by the plugin: " << execution.getMessage());
296  controller_active = false;
297  fillExePathResult(mbf_msgs::ExePathResult::INTERNAL_ERROR, "Internal error: Unknown error thrown by the plugin!", result);
298  goal_handle.setAborted(result, result.message);
299  break;
300 
301  default:
302  std::stringstream ss;
303  ss << "Internal error: Unknown state in a move base flex controller execution with the number: "
304  << static_cast<int>(state_moving_input);
305  fillExePathResult(mbf_msgs::ExePathResult::INTERNAL_ERROR, ss.str(), result);
306  ROS_FATAL_STREAM_NAMED(name_, result.message);
307  goal_handle.setAborted(result, result.message);
308  controller_active = false;
309  }
310  goal_mtx_.unlock();
311 
312  if (controller_active)
313  {
314  // try to sleep a bit
315  // normally this thread should be woken up from the controller execution thread
316  // in order to transfer the results to the controller
317  execution.waitForStateUpdate(boost::chrono::milliseconds(500));
318  }
319 
320  first_cycle = false;
321  } // while (controller_active && ros::ok())
322 
323  if (!controller_active)
324  {
325  ROS_DEBUG_STREAM_NAMED(name_, "\"" << name_ << "\" action ended properly.");
326  }
327  else
328  {
329  // normal on continuous replanning
330  ROS_DEBUG_STREAM_NAMED(name_, "\"" << name_ << "\" action has been stopped!");
331  }
332 }
333 
335  GoalHandle &goal_handle,
336  uint32_t outcome, const std::string &message,
337  const geometry_msgs::TwistStamped &current_twist)
338 {
339  mbf_msgs::ExePathFeedback feedback;
340  feedback.outcome = outcome;
341  feedback.message = message;
342 
343  feedback.last_cmd_vel = current_twist;
344  if (feedback.last_cmd_vel.header.stamp.isZero())
345  feedback.last_cmd_vel.header.stamp = ros::Time::now();
346 
347  feedback.current_pose = robot_pose_;
348  feedback.dist_to_goal = static_cast<float>(mbf_utility::distance(robot_pose_, goal_pose_));
349  feedback.angle_to_goal = static_cast<float>(mbf_utility::angle(robot_pose_, goal_pose_));
350  goal_handle.publishFeedback(feedback);
351 }
352 
354  uint32_t outcome, const std::string &message,
355  mbf_msgs::ExePathResult &result)
356 {
357  result.outcome = outcome;
358  result.message = message;
359  result.final_pose = robot_pose_;
360  result.dist_to_goal = static_cast<float>(mbf_utility::distance(robot_pose_, goal_pose_));
361  result.angle_to_goal = static_cast<float>(mbf_utility::angle(robot_pose_, goal_pose_));
362 }
363 
364 } /* mbf_abstract_nav */
void fillExePathResult(uint32_t outcome, const std::string &message, mbf_msgs::ExePathResult &result)
Utility method to fill the ExePath action result in a single line.
double angle(const geometry_msgs::PoseStamped &pose1, const geometry_msgs::PoseStamped &pose2)
#define ROS_DEBUG_STREAM_NAMED(name, args)
#define ROS_ERROR_STREAM_NAMED(name, args)
geometry_msgs::PoseStamped robot_pose_
Current robot pose.
Exceeded the maximum number of retries without a valid command.
Received no velocity command by the plugin, in the current cycle.
void run(GoalHandle &goal_handle, AbstractControllerExecution &execution)
The controller has been started without a plan.
boost::cv_status waitForStateUpdate(boost::chrono::microseconds const &duration)
std::string getMessage()
Gets the current plugin execution message.
The AbstractControllerExecution class loads and binds the controller plugin. It contains a thread run...
geometry_msgs::PoseStamped goal_pose_
Current goal pose.
virtual bool cancel()
Cancel the controller execution. This calls the cancel method of the controller plugin, sets the cancel_ flag to true, and waits for the control loop to stop. Normally called upon aborting the navigation.
Exceeded the patience time without a valid command.
#define ROS_FATAL_STREAM_NAMED(name, args)
bool isPatienceExceeded()
Checks whether the patience duration time has been exceeded, ot not.
bool param(const std::string &param_name, T &param_val, const T &default_val) const
#define ROS_WARN_STREAM_THROTTLE_NAMED(rate, name, args)
void start(GoalHandle &goal_handle, typename AbstractControllerExecution::Ptr execution_ptr)
Start controller action. Override abstract action version to allow updating current plan without stop...
ROSCPP_DECL bool ok()
Received an invalid plan that the controller plugin rejected.
geometry_msgs::TwistStamped getVelocityCmd()
Returns the last velocity command calculated by the plugin. Set by setVelocityCmd method...
void setNewPlan(const std::vector< geometry_msgs::PoseStamped > &plan, bool tolerance_from_action=false, double action_dist_tolerance=1.0, double action_angle_tolerance=3.1415)
Sets a new plan to the controller execution.
void publishExePathFeedback(GoalHandle &goal_handle, uint32_t outcome, const std::string &message, const geometry_msgs::TwistStamped &current_twist)
double distance(const geometry_msgs::PoseStamped &pose1, const geometry_msgs::PoseStamped &pose2)
ControllerAction(const std::string &name, const mbf_utility::RobotInformation &robot_info)
ControllerState getState()
Return the current state of the controller execution. Thread communication safe.
#define ROS_INFO_STREAM(args)
boost::mutex goal_mtx_
lock goal handle for updating it while running
static Time now()
virtual bool start()
Starts the controller, a valid plan should be given in advance.
uint32_t getOutcome()
Gets the current plugin execution outcome.
#define ROS_WARN_STREAM_NAMED(name, args)


mbf_abstract_nav
Author(s): Sebastian Pütz
autogenerated on Fri Nov 6 2020 03:56:24