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 
42 #include <mbf_msgs/ExePathResult.h>
43 
44 namespace mbf_abstract_nav
45 {
46 
47  const double AbstractControllerExecution::DEFAULT_CONTROLLER_FREQUENCY = 100.0; // 100 Hz
48 
50  const std::string name,
51  const mbf_abstract_core::AbstractController::Ptr& controller_ptr,
52  const ros::Publisher& vel_pub,
53  const ros::Publisher& goal_pub,
54  const TFPtr &tf_listener_ptr,
55  const MoveBaseFlexConfig &config,
56  boost::function<void()> setup_fn,
57  boost::function<void()> cleanup_fn) :
58  AbstractExecutionBase(name, setup_fn, cleanup_fn),
59  controller_(controller_ptr), tf_listener_ptr(tf_listener_ptr), state_(INITIALIZED),
60  moving_(false), max_retries_(0), patience_(0), vel_pub_(vel_pub), current_goal_pub_(goal_pub),
61  calling_duration_(boost::chrono::microseconds(static_cast<int>(1e6 / DEFAULT_CONTROLLER_FREQUENCY)))
62  {
63  ros::NodeHandle nh;
64  ros::NodeHandle private_nh("~");
65 
66  // non-dynamically reconfigurable parameters
67  private_nh.param("robot_frame", robot_frame_, std::string("base_link"));
68  private_nh.param("map_frame", global_frame_, std::string("map"));
69  private_nh.param("mbf_tolerance_check", mbf_tolerance_check_, false);
70  private_nh.param("dist_tolerance", dist_tolerance_, 0.1);
71  private_nh.param("angle_tolerance", angle_tolerance_, M_PI / 18.0);
72  private_nh.param("tf_timeout", tf_timeout_, 1.0);
73 
74  // dynamically reconfigurable parameters
75  reconfigure(config);
76  }
77 
79  {
80  }
81 
83  {
84  // set the calling duration by the moving frequency
85  if (frequency <= 0.0)
86  {
87  ROS_ERROR("Controller frequency must be greater than 0.0! No change of the frequency!");
88  return false;
89  }
90  calling_duration_ = boost::chrono::microseconds(static_cast<int>(1e6 / frequency));
91  return true;
92  }
93 
94  void AbstractControllerExecution::reconfigure(const MoveBaseFlexConfig &config)
95  {
96  boost::lock_guard<boost::mutex> guard(configuration_mutex_);
97  // Timeout granted to the controller. We keep calling it up to this time or up to max_retries times
98  // If it doesn't return within time, the navigator will cancel it and abort the corresponding action
99  patience_ = ros::Duration(config.controller_patience);
100 
101  setControllerFrequency(config.controller_frequency);
102 
103  max_retries_ = config.controller_max_retries;
104  }
105 
106 
108  {
109  setState(STARTED);
110  if (moving_)
111  {
112  return false; // thread is already running.
113  }
114  moving_ = true;
116  }
117 
118 
120  {
121  boost::lock_guard<boost::mutex> guard(state_mtx_);
122  state_ = state;
123  }
124 
125 
128  {
129  boost::lock_guard<boost::mutex> guard(state_mtx_);
130  return state_;
131  }
132 
133  void AbstractControllerExecution::setNewPlan(const std::vector<geometry_msgs::PoseStamped> &plan)
134  {
135  if (moving_)
136  {
137  // This is fine on continuous replanning
138  ROS_DEBUG("Setting new plan while moving");
139  }
140  boost::lock_guard<boost::mutex> guard(plan_mtx_);
141  new_plan_ = true;
142 
143  plan_ = plan;
144  }
145 
146 
148  {
149  boost::lock_guard<boost::mutex> guard(plan_mtx_);
150  return new_plan_;
151  }
152 
153 
154  std::vector<geometry_msgs::PoseStamped> AbstractControllerExecution::getNewPlan()
155  {
156  boost::lock_guard<boost::mutex> guard(plan_mtx_);
157  new_plan_ = false;
158  return plan_;
159  }
160 
161 
163  {
164  bool tf_success = mbf_utility::getRobotPose(*tf_listener_ptr, robot_frame_, global_frame_,
166  // would be 0 if not, as we ask tf listener for the last pose available
167  robot_pose_.header.stamp = ros::Time::now();
168  if (!tf_success)
169  {
170  ROS_ERROR_STREAM("Could not get the robot pose in the global frame. - robot frame: \""
171  << robot_frame_ << "\" global frame: \"" << global_frame_ << std::endl);
172  message_ = "Could not get the robot pose";
173  outcome_ = mbf_msgs::ExePathResult::TF_ERROR;
174  return false;
175  }
176  return true;
177  }
178 
179 
180  uint32_t AbstractControllerExecution::computeVelocityCmd(const geometry_msgs::PoseStamped& robot_pose,
181  const geometry_msgs::TwistStamped& robot_velocity,
182  geometry_msgs::TwistStamped& vel_cmd,
183  std::string& message)
184  {
185  return controller_->computeVelocityCommands(robot_pose, robot_velocity, vel_cmd, message);
186  }
187 
188 
189  void AbstractControllerExecution::setVelocityCmd(const geometry_msgs::TwistStamped &vel_cmd)
190  {
191  boost::lock_guard<boost::mutex> guard(vel_cmd_mtx_);
192  vel_cmd_stamped_ = vel_cmd;
193  if (vel_cmd_stamped_.header.stamp.isZero())
194  vel_cmd_stamped_.header.stamp = ros::Time::now();
195  // TODO what happen with frame id?
196  // TODO Add a queue here for handling the outcome, message and cmd_vel values bundled,
197  // TODO so there should be no loss of information in the feedback stream
198  }
199 
200 
201  geometry_msgs::TwistStamped AbstractControllerExecution::getVelocityCmd()
202  {
203  boost::lock_guard<boost::mutex> guard(vel_cmd_mtx_);
204  return vel_cmd_stamped_;
205  }
206 
207 
209  {
210  boost::lock_guard<boost::mutex> guard(lct_mtx_);
211  return last_call_time_;
212  }
213 
214 
216  {
217  boost::lock_guard<boost::mutex> guard(lct_mtx_);
219  }
220 
221 
223  {
224  return moving_;
225  }
226 
228  {
229  // check whether the controller plugin returns goal reached or if mbf should check for goal reached.
233  }
234 
236  {
237  cancel_ = true;
238  // returns false if cancel is not implemented or rejected by the recovery behavior (will run until completion)
239  if(!controller_->cancel())
240  {
241  ROS_WARN_STREAM("Cancel controlling failed or is not supported by the plugin. "
242  << "Wait until the current control cycle finished!");
243  return false;
244  }
245  return true;
246  }
247 
248 
250  {
252 
253  // init plan
254  std::vector<geometry_msgs::PoseStamped> plan;
255  if (!hasNewPlan())
256  {
257  setState(NO_PLAN);
258  moving_ = false;
259  ROS_ERROR("robot navigation moving has no plan!");
260  }
261 
262  ros::Time last_valid_cmd_time = ros::Time();
263  int retries = 0;
264  int seq = 0;
265 
266  try
267  {
268  while (moving_ && ros::ok())
269  {
270  boost::chrono::thread_clock::time_point loop_start_time = boost::chrono::thread_clock::now();
271 
272  if(cancel_){
275  moving_ = false;
276  return;
277  }
278 
279  // update plan dynamically
280  if (hasNewPlan())
281  {
282  plan = getNewPlan();
283 
284  // check if plan is empty
285  if (plan.empty())
286  {
289  moving_ = false;
290  return;
291  }
292 
293  // check if plan could be set
294  if(!controller_->setPlan(plan))
295  {
298  moving_ = false;
299  return;
300  }
301  current_goal_pub_.publish(plan.back());
302  }
303 
304  // compute robot pose and store it in robot_pose_
306 
307  // ask planner if the goal is reached
308  if (reachedGoalCheck())
309  {
310  ROS_DEBUG_STREAM_NAMED("abstract_controller_execution", "Reached the goal!");
312  // goal reached, tell it the controller
314  moving_ = false;
315  // if not, keep moving
316  }
317  else
318  {
320 
321  // save time and call the plugin
322  lct_mtx_.lock();
324  lct_mtx_.unlock();
325 
326  // call plugin to compute the next velocity command
327  geometry_msgs::TwistStamped cmd_vel_stamped;
328  geometry_msgs::TwistStamped robot_velocity; // TODO pass current velocity to the plugin!
329  outcome_ = computeVelocityCmd(robot_pose_, robot_velocity, cmd_vel_stamped, message_ = "");
330 
331  if (outcome_ < 10)
332  {
334  vel_pub_.publish(cmd_vel_stamped.twist);
335  last_valid_cmd_time = ros::Time::now();
336  retries = 0;
337  }
338  else
339  {
340  boost::lock_guard<boost::mutex> guard(configuration_mutex_);
341  if (max_retries_ >= 0 && ++retries > max_retries_)
342  {
344  moving_ = false;
345  }
346  else if (!patience_.isZero() && ros::Time::now() - last_valid_cmd_time > patience_
348  {
349  // patience limit enabled and running controller for more than patience without valid commands
351  moving_ = false;
352  }
353  else
354  {
355  setState(NO_LOCAL_CMD); // useful for server feedback
356  }
357  // could not compute a valid velocity command -> stop moving the robot
358  publishZeroVelocity(); // command the robot to stop; we still feedback command calculated by the plugin
359  }
360 
361  // set stamped values; timestamp and frame_id should be set by the plugin; otherwise setVelocityCmd will do
362  cmd_vel_stamped.header.seq = seq++; // sequence number
363  setVelocityCmd(cmd_vel_stamped);
365  }
366 
367  boost::chrono::thread_clock::time_point end_time = boost::chrono::thread_clock::now();
368  boost::chrono::microseconds execution_duration =
369  boost::chrono::duration_cast<boost::chrono::microseconds>(end_time - loop_start_time);
370  configuration_mutex_.lock();
371  boost::chrono::microseconds sleep_time = calling_duration_ - execution_duration;
372  configuration_mutex_.unlock();
373  if (moving_ && ros::ok())
374  {
375  if (sleep_time > boost::chrono::microseconds(0))
376  {
377  // interruption point
378  boost::this_thread::sleep_for(sleep_time);
379  }
380  else
381  {
382  // provide an interruption point also with 0 or negative sleep_time
383  boost::this_thread::interruption_point();
384  ROS_WARN_THROTTLE(1.0, "Calculation needs too much time to stay in the moving frequency! (%f > %f)",
385  execution_duration.count()/1000000.0, calling_duration_.count()/1000000.0);
386  }
387  }
388  }
389  }
390  catch (const boost::thread_interrupted &ex)
391  {
392  // Controller thread interrupted; in most cases we have started a new plan
393  // Can also be that robot is oscillating or we have exceeded planner patience
394  ROS_DEBUG_STREAM("Controller thread interrupted!");
396  setState(STOPPED);
398  moving_ = false;
399  }
400  catch (...)
401  {
402  message_ = "Unknown error occurred: " + boost::current_exception_diagnostic_information();
405  }
406  }
407 
408 
410  {
411  geometry_msgs::Twist cmd_vel;
412  cmd_vel.linear.x = 0;
413  cmd_vel.linear.y = 0;
414  cmd_vel.linear.z = 0;
415  cmd_vel.angular.x = 0;
416  cmd_vel.angular.y = 0;
417  cmd_vel.angular.z = 0;
418  vel_pub_.publish(cmd_vel);
419  }
420 
421 } /* 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
void notify_all() BOOST_NOEXCEPT
bool new_plan_
true, if a new plan is available. See hasNewPlan()!
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, boost::function< void()> setup_fn, boost::function< void()> cleanup_fn)
Constructor.
double angle(const geometry_msgs::PoseStamped &pose1, const geometry_msgs::PoseStamped &pose2)
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_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
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)
void setState(ControllerState state)
Sets the controller state. This method makes the communication of the state thread safe...
bool cancel_
flag for canceling controlling
ros::Publisher vel_pub_
publisher for the current velocity command
virtual bool cancel()
Cancel the planner execution. This calls the cancel method of the planner plugin. This could be usefu...
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)
#define ROS_WARN_THROTTLE(period,...)
bool isPatienceExceeded()
Checks whether the patience duration time has been exceeded, ot not.
double angle_tolerance_
angle tolerance to the given goal pose
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
ros::Time getLastPluginCallTime()
Returns the time of the last plugin call.
ros::Time start_time_
The time the controller has been started.
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)
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...
double distance(const geometry_msgs::PoseStamped &pose1, const geometry_msgs::PoseStamped &pose2)
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.
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(...)
void setNewPlan(const std::vector< geometry_msgs::PoseStamped > &plan)
Sets a new plan to the controller execution.


mbf_abstract_nav
Author(s): Sebastian Pütz
autogenerated on Tue Jun 18 2019 19:34:34