abstract_controller_execution.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  * abstract_controller_execution.cpp
34  *
35  * authors:
36  * Sebastian Pütz <spuetz@uni-osnabrueck.de>
37  * Jorge Santos Simón <santos@magazino.eu>
38  *
39  */
40 
41 #include <mbf_msgs/ExePathResult.h>
42 
44 
45 namespace mbf_abstract_nav
46 {
47 
49 
51  const std::string &name,
52  const mbf_abstract_core::AbstractController::Ptr &controller_ptr,
53  const ros::Publisher &vel_pub,
54  const ros::Publisher &goal_pub,
55  const TFPtr &tf_listener_ptr,
56  const MoveBaseFlexConfig &config) :
58  controller_(controller_ptr), tf_listener_ptr(tf_listener_ptr), state_(INITIALIZED),
59  moving_(false), max_retries_(0), patience_(0), vel_pub_(vel_pub), current_goal_pub_(goal_pub),
60  calling_duration_(boost::chrono::microseconds(static_cast<int>(1e6 / DEFAULT_CONTROLLER_FREQUENCY)))
61 {
62  ros::NodeHandle nh;
63  ros::NodeHandle private_nh("~");
64 
65  // non-dynamically reconfigurable parameters
66  private_nh.param("robot_frame", robot_frame_, std::string("base_link"));
67  private_nh.param("map_frame", global_frame_, std::string("map"));
68  private_nh.param("force_stop_at_goal", force_stop_at_goal_, false);
69  private_nh.param("force_stop_on_cancel", force_stop_on_cancel_, false);
70  private_nh.param("mbf_tolerance_check", mbf_tolerance_check_, false);
71  private_nh.param("dist_tolerance", dist_tolerance_, 0.1);
72  private_nh.param("angle_tolerance", angle_tolerance_, M_PI / 18.0);
73  private_nh.param("tf_timeout", tf_timeout_, 1.0);
74 
75  // dynamically reconfigurable parameters
76  reconfigure(config);
77 }
78 
80 {
81 }
82 
84 {
85  // set the calling duration by the moving frequency
86  if (frequency <= 0.0)
87  {
88  ROS_ERROR("Controller frequency must be greater than 0.0! No change of the frequency!");
89  return false;
90  }
91  calling_duration_ = boost::chrono::microseconds(static_cast<int>(1e6 / frequency));
92  return true;
93 }
94 
95 void AbstractControllerExecution::reconfigure(const MoveBaseFlexConfig &config)
96 {
97  boost::lock_guard<boost::mutex> guard(configuration_mutex_);
98  // Timeout granted to the controller. We keep calling it up to this time or up to max_retries times
99  // If it doesn't return within time, the navigator will cancel it and abort the corresponding action
100  patience_ = ros::Duration(config.controller_patience);
101 
102  setControllerFrequency(config.controller_frequency);
103 
104  max_retries_ = config.controller_max_retries;
105 }
106 
107 
109 {
110  setState(STARTED);
111  if (moving_)
112  {
113  return false; // thread is already running.
114  }
115  moving_ = true;
117 }
118 
119 
121 {
122  boost::lock_guard<boost::mutex> guard(state_mtx_);
123  state_ = state;
124 }
125 
126 
129 {
130  boost::lock_guard<boost::mutex> guard(state_mtx_);
131  return state_;
132 }
133 
135  const std::vector<geometry_msgs::PoseStamped> &plan,
136  bool tolerance_from_action,
137  double action_dist_tolerance,
138  double action_angle_tolerance)
139 {
140  if (moving_)
141  {
142  // This is fine on continuous replanning
143  ROS_DEBUG("Setting new plan while moving");
144  }
145  boost::lock_guard<boost::mutex> guard(plan_mtx_);
146  new_plan_ = true;
147 
148  plan_ = plan;
149  tolerance_from_action_ = tolerance_from_action;
150  action_dist_tolerance_ = action_dist_tolerance;
151  action_angle_tolerance_ = action_angle_tolerance;
152 }
153 
154 
156 {
157  boost::lock_guard<boost::mutex> guard(plan_mtx_);
158  return new_plan_;
159 }
160 
161 
162 std::vector<geometry_msgs::PoseStamped> AbstractControllerExecution::getNewPlan()
163 {
164  boost::lock_guard<boost::mutex> guard(plan_mtx_);
165  new_plan_ = false;
166  return plan_;
167 }
168 
169 
171 {
174  {
175  ROS_ERROR_STREAM("Could not get the robot pose in the global frame. - robot frame: \""
176  << robot_frame_ << "\" global frame: \"" << global_frame_);
177  message_ = "Could not get the robot pose";
178  outcome_ = mbf_msgs::ExePathResult::TF_ERROR;
179  return false;
180  }
181  return true;
182 }
183 
184 
185 uint32_t AbstractControllerExecution::computeVelocityCmd(const geometry_msgs::PoseStamped &robot_pose,
186  const geometry_msgs::TwistStamped &robot_velocity,
187  geometry_msgs::TwistStamped &vel_cmd,
188  std::string &message)
189 {
190  return controller_->computeVelocityCommands(robot_pose, robot_velocity, vel_cmd, message);
191 }
192 
193 
194 void AbstractControllerExecution::setVelocityCmd(const geometry_msgs::TwistStamped &vel_cmd)
195 {
196  boost::lock_guard<boost::mutex> guard(vel_cmd_mtx_);
197  vel_cmd_stamped_ = vel_cmd;
198  if (vel_cmd_stamped_.header.stamp.isZero())
199  vel_cmd_stamped_.header.stamp = ros::Time::now();
200  // TODO what happen with frame id?
201  // TODO Add a queue here for handling the outcome, message and cmd_vel values bundled,
202  // TODO so there should be no loss of information in the feedback stream
203 }
204 
205 
206 geometry_msgs::TwistStamped AbstractControllerExecution::getVelocityCmd()
207 {
208  boost::lock_guard<boost::mutex> guard(vel_cmd_mtx_);
209  return vel_cmd_stamped_;
210 }
211 
212 
214 {
215  boost::lock_guard<boost::mutex> guard(lct_mtx_);
216  return last_call_time_;
217 }
218 
219 
221 {
222  boost::lock_guard<boost::mutex> guard(lct_mtx_);
223  if(!patience_.isZero() && ros::Time::now() - start_time_ > patience_) // not zero -> activated, start_time handles init case
224  {
226  {
227  ROS_WARN_STREAM_THROTTLE(3, "The controller plugin \"" << name_ << "\" needs more time to compute in one run than the patience time!");
228  return true;
229  }
231  {
232  ROS_DEBUG_STREAM("The controller plugin \"" << name_ << "\" does not return a success state (outcome < 10) for more than the patience time in multiple runs!");
233  return true;
234  }
235  }
236  return false;
237 }
238 
239 
241 {
242  return moving_;
243 }
244 
246 {
247  //if action has a specific tolerance, check goal reached with those tolerances
249  {
253  }
254 
255  // Otherwise, check whether the controller plugin returns goal reached or if mbf should check for goal reached.
259 }
260 
262 {
263  // request the controller to cancel; it returns false if cancel is not implemented or rejected by the plugin
264  if (!controller_->cancel())
265  {
266  ROS_WARN_STREAM("Cancel controlling failed. Wait until the current control cycle finished!");
267  }
268  // then wait for the control cycle to stop (should happen immediately if the controller cancel returned true)
269  cancel_ = true;
270  if (waitForStateUpdate(boost::chrono::milliseconds(500)) == boost::cv_status::timeout)
271  {
272  // this situation should never happen; if it does, the action server will be unready for goals immediately sent
273  ROS_WARN_STREAM("Timeout while waiting for control cycle to stop; immediately sent goals can get stuck");
274  return false;
275  }
276  return true;
277 }
278 
279 
281 {
283 
284  // init plan
285  std::vector<geometry_msgs::PoseStamped> plan;
286  if (!hasNewPlan())
287  {
288  setState(NO_PLAN);
289  moving_ = false;
290  ROS_ERROR("robot navigation moving has no plan!");
291  }
292 
294  int retries = 0;
295  int seq = 0;
296 
297  try
298  {
299  while (moving_ && ros::ok())
300  {
301  boost::chrono::thread_clock::time_point loop_start_time = boost::chrono::thread_clock::now();
302 
303  if (cancel_)
304  {
306  {
307  publishZeroVelocity(); // command the robot to stop on canceling navigation
308  }
310  condition_.notify_all();
311  moving_ = false;
312  return;
313  }
314 
315  if (!safetyCheck())
316  {
317  // the specific implementation must have detected a risk situation; at this abstract level, we
318  // cannot tell what the problem is, but anyway we command the robot to stop to avoid crashes
319  publishZeroVelocity(); // note that we still feedback command calculated by the plugin
320  boost::this_thread::sleep_for(calling_duration_);
321  }
322 
323  // update plan dynamically
324  if (hasNewPlan())
325  {
326  plan = getNewPlan();
327 
328  // check if plan is empty
329  if (plan.empty())
330  {
332  condition_.notify_all();
333  moving_ = false;
334  return;
335  }
336 
337  // check if plan could be set
338  if (!controller_->setPlan(plan))
339  {
341  condition_.notify_all();
342  moving_ = false;
343  return;
344  }
345  current_goal_pub_.publish(plan.back());
346  }
347 
348  // compute robot pose and store it in robot_pose_
349  if (!computeRobotPose())
350  {
353  condition_.notify_all();
354  moving_ = false;
355  return;
356  }
357 
358  // ask planner if the goal is reached
359  if (reachedGoalCheck())
360  {
361  ROS_DEBUG_STREAM_NAMED("abstract_controller_execution", "Reached the goal!");
363  {
365  }
367  // goal reached, tell it the controller
368  condition_.notify_all();
369  moving_ = false;
370  // if not, keep moving
371  }
372  else
373  {
375 
376  // save time and call the plugin
377  lct_mtx_.lock();
379  lct_mtx_.unlock();
380 
381  // call plugin to compute the next velocity command
382  geometry_msgs::TwistStamped cmd_vel_stamped;
383  geometry_msgs::TwistStamped robot_velocity; // TODO pass current velocity to the plugin!
384  outcome_ = computeVelocityCmd(robot_pose_, robot_velocity, cmd_vel_stamped, message_ = "");
385 
386  if (outcome_ < 10)
387  {
389  vel_pub_.publish(cmd_vel_stamped.twist);
391  retries = 0;
392  }
393  else
394  {
395  boost::lock_guard<boost::mutex> guard(configuration_mutex_);
396  if (max_retries_ >= 0 && ++retries > max_retries_)
397  {
399  moving_ = false;
400  }
401  else if (isPatienceExceeded())
402  {
403  // patience limit enabled and running controller for more than patience without valid commands
405  moving_ = false;
406  }
407  else
408  {
409  setState(NO_LOCAL_CMD); // useful for server feedback
410  }
411  // could not compute a valid velocity command -> stop moving the robot
412  publishZeroVelocity(); // command the robot to stop; we still feedback command calculated by the plugin
413  }
414 
415  // set stamped values; timestamp and frame_id should be set by the plugin; otherwise setVelocityCmd will do
416  cmd_vel_stamped.header.seq = seq++; // sequence number
417  setVelocityCmd(cmd_vel_stamped);
418  condition_.notify_all();
419  }
420 
421  boost::chrono::thread_clock::time_point end_time = boost::chrono::thread_clock::now();
422  boost::chrono::microseconds execution_duration =
423  boost::chrono::duration_cast<boost::chrono::microseconds>(end_time - loop_start_time);
424  configuration_mutex_.lock();
425  boost::chrono::microseconds sleep_time = calling_duration_ - execution_duration;
426  configuration_mutex_.unlock();
427  if (moving_ && ros::ok())
428  {
429  if (sleep_time > boost::chrono::microseconds(0))
430  {
431  // interruption point
432  boost::this_thread::sleep_for(sleep_time);
433  }
434  else
435  {
436  // provide an interruption point also with 0 or negative sleep_time
437  boost::this_thread::interruption_point();
438  ROS_WARN_THROTTLE(1.0, "Calculation needs too much time to stay in the moving frequency! (%f > %f)",
439  execution_duration.count()/1000000.0, calling_duration_.count()/1000000.0);
440  }
441  }
442  }
443  }
444  catch (const boost::thread_interrupted &ex)
445  {
446  // Controller thread interrupted; in most cases we have started a new plan
447  // Can also be that robot is oscillating or we have exceeded planner patience
448  ROS_DEBUG_STREAM("Controller thread interrupted!");
450  setState(STOPPED);
451  condition_.notify_all();
452  moving_ = false;
453  }
454  catch (...)
455  {
456  message_ = "Unknown error occurred: " + boost::current_exception_diagnostic_information();
459  }
460 }
461 
462 
464 {
465  geometry_msgs::Twist cmd_vel;
466  cmd_vel.linear.x = 0;
467  cmd_vel.linear.y = 0;
468  cmd_vel.linear.z = 0;
469  cmd_vel.angular.x = 0;
470  cmd_vel.angular.y = 0;
471  cmd_vel.angular.z = 0;
472  vel_pub_.publish(cmd_vel);
473 }
474 
475 } /* namespace mbf_abstract_nav */
boost::mutex plan_mtx_
mutex to handle safe thread communication for the current plan
boost::condition_variable condition_
condition variable to wake up control thread
bool new_plan_
true, if a new plan is available. See hasNewPlan()!
double angle(const geometry_msgs::PoseStamped &pose1, const geometry_msgs::PoseStamped &pose2)
bool tolerance_from_action_
whether check for action specific tolerance
std::string global_frame_
the global frame the robot is controlling in.
boost::chrono::microseconds calling_duration_
the duration which corresponds with the controller frequency.
ros::Duration patience_
The time / duration of patience, before changing the state.
#define ROS_WARN_THROTTLE(rate,...)
#define ROS_DEBUG_STREAM_NAMED(name, args)
boost::mutex configuration_mutex_
dynamic reconfigure config mutex, thread safe param reading and writing
void publish(const boost::shared_ptr< M > &message) const
boost::mutex vel_cmd_mtx_
mutex to handle safe thread communication for the current velocity command
Exceeded the maximum number of retries without a valid command.
Received no velocity command by the plugin, in the current cycle.
ros::Publisher current_goal_pub_
publisher for the current goal
std::string name_
the plugin name; not the plugin type!
bool hasNewPlan()
Returns true if a new plan is available, false otherwise! A new plan is set by another thread! ...
void setVelocityCmd(const geometry_msgs::TwistStamped &vel_cmd_stamped)
Sets the velocity command, to make it available for another thread.
double dist_tolerance_
distance tolerance to the given goal pose
The controller has been started without a plan.
bool getRobotPose(const TF &tf, const std::string &robot_frame, const std::string &global_frame, const ros::Duration &timeout, geometry_msgs::PoseStamped &robot_pose)
double action_dist_tolerance_
replaces parameter dist_tolerance_ for the action
void setState(ControllerState state)
Sets the controller state. This method makes the communication of the state thread safe...
boost::cv_status waitForStateUpdate(boost::chrono::microseconds const &duration)
bool cancel_
flag for canceling controlling
ros::Publisher vel_pub_
publisher for the current velocity command
const TFPtr & tf_listener_ptr
shared pointer to the shared tf listener
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.
std::string message_
the last received plugin execution message
virtual void run()
The main run method, a thread will execute this method. It contains the main controller execution loo...
geometry_msgs::TwistStamped vel_cmd_stamped_
the last calculated velocity command
Exceeded the patience time without a valid command.
bool isMoving()
Returns whether the robot should normally move or not. True if the controller seems to work properly...
bool mbf_tolerance_check_
whether move base flex should check for the goal tolerance or not.
AbstractControllerExecution::ControllerState state_
the current controller state
bool moving_
main controller loop variable, true if the controller is running, false otherwise ...
#define ROS_FATAL_STREAM(args)
bool force_stop_at_goal_
whether move base flex should force the robot to stop once the goal is reached.
bool isPatienceExceeded()
Checks whether the patience duration time has been exceeded, ot not.
double angle_tolerance_
angle tolerance to the given goal pose
virtual bool safetyCheck()
Check if its safe to drive. This method gets called at every controller cycle, stopping the robot if ...
bool param(const std::string &param_name, T &param_val, const T &default_val) const
boost::mutex state_mtx_
mutex to handle safe thread communication for the current value of the state
ROSCPP_DECL bool ok()
uint32_t outcome_
the last received plugin execution outcome
bool force_stop_on_cancel_
whether move base flex should force the robot to stop on canceling navigation.
ros::Time getLastPluginCallTime()
Returns the time of the last plugin call.
ros::Time start_time_
The time the controller has been started.
AbstractControllerExecution(const std::string &name, const mbf_abstract_core::AbstractController::Ptr &controller_ptr, const ros::Publisher &vel_pub, const ros::Publisher &goal_pub, const TFPtr &tf_listener_ptr, const MoveBaseFlexConfig &config)
Constructor.
std::vector< geometry_msgs::PoseStamped > getNewPlan()
Gets the new available plan. This method is thread safe.
virtual uint32_t computeVelocityCmd(const geometry_msgs::PoseStamped &pose, const geometry_msgs::TwistStamped &velocity, geometry_msgs::TwistStamped &vel_cmd, std::string &message)
Request plugin for a new velocity command, given the current position, orientation, and velocity of the robot. We use this virtual method to give concrete implementations as move_base the chance to override it and do additional stuff, for example locking the costmap.
#define ROS_WARN_STREAM(args)
#define ROS_DEBUG_STREAM(args)
double action_angle_tolerance_
replaces parameter angle_tolerance_ for the action
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.
double distance(const geometry_msgs::PoseStamped &pose1, const geometry_msgs::PoseStamped &pose2)
#define ROS_WARN_STREAM_THROTTLE(rate, args)
void publishZeroVelocity()
Publishes a velocity command with zero values to stop the robot.
bool reachedGoalCheck()
Checks whether the goal has been reached in the range of tolerance or not.
mbf_abstract_core::AbstractController::Ptr controller_
the local planer to calculate the velocity command
ControllerState getState()
Return the current state of the controller execution. Thread communication safe.
boost::mutex lct_mtx_
mutex to handle safe thread communication for the last plugin call time
geometry_msgs::PoseStamped robot_pose_
current robot pose;
std::string robot_frame_
the frame of the robot, which will be used to determine its position.
static Time now()
void reconfigure(const MoveBaseFlexConfig &config)
Is called by the server thread to reconfigure the controller execution, if a user uses dynamic reconf...
virtual bool start()
Starts the controller, a valid plan should be given in advance.
ros::Time last_valid_cmd_time_
The time the controller responded with a success output (output < 10).
double tf_timeout_
time before a timeout used for tf requests
#define ROS_ERROR_STREAM(args)
bool setControllerFrequency(double frequency)
Sets the controller frequency.
#define ROS_ERROR(...)
std::vector< geometry_msgs::PoseStamped > plan_
the last set plan which is currently processed by the controller
ros::Time last_call_time_
The current cycle start time of the last cycle run. Will by updated each cycle.
#define ROS_DEBUG(...)


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