hector_path_follower.cpp
Go to the documentation of this file.
1 //=================================================================================================
2 // Copyright (c) 2012, Stefan Kohlbrecher, TU Darmstadt
3 // All rights reserved.
4 
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are met:
7 // * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 // * Redistributions in binary form must reproduce the above copyright
10 // notice, this list of conditions and the following disclaimer in the
11 // documentation and/or other materials provided with the distribution.
12 // * Neither the name of the Simulation, Systems Optimization and Robotics
13 // group, TU Darmstadt nor the names of its contributors may be used to
14 // endorse or promote products derived from this software without
15 // specific prior written permission.
16 
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
21 // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 //=================================================================================================
28 
29 /*********************************************************************
30 * Based heavily on the pose_follower package
31 *********************************************************************/
33 
34 
35 namespace pose_follower {
37 
39  tf_ = tf;
42  ros::NodeHandle node_private("~/");
43 
44  node_private.param("k_trans", K_trans_, 2.0);
45  node_private.param("k_rot", K_rot_, 2.0);
46 
47  node_private.param("tolerance_trans", tolerance_trans_, 0.1);
48  node_private.param("tolerance_rot", tolerance_rot_, 0.2);
49  node_private.param("tolerance_timeout", tolerance_timeout_, 0.5);
50 
51  node_private.param("holonomic", holonomic_, false);
52 
53  node_private.param("samples", samples_, 10);
54 
55  node_private.param("max_vel_lin", max_vel_lin_, 0.9);
56  node_private.param("max_vel_th", max_vel_th_, 1.4);
57 
58  node_private.param("min_vel_lin", min_vel_lin_, 0.1);
59  node_private.param("min_vel_th", min_vel_th_, 0.0);
60  node_private.param("min_in_place_vel_th", min_in_place_vel_th_, 0.0);
61  node_private.param("in_place_trans_vel", in_place_trans_vel_, 0.0);
62 
63  node_private.param("trans_stopped_velocity", trans_stopped_velocity_, 1e-4);
64  node_private.param("rot_stopped_velocity", rot_stopped_velocity_, 1e-4);
65 
66  node_private.param("robot_base_frame", p_robot_base_frame_, std::string("base_link"));
67  node_private.param("global_frame", p_global_frame_, std::string("map"));
68 
69  //ros::NodeHandle node;
70  //vel_pub_ = node.advertise<geometry_msgs::Twist>("cmd_vel", 10);
71 
72  ROS_DEBUG("Initialized");
73  }
74 
75  /*
76  void HectorPathFollower::odomCallback(const nav_msgs::Odometry::ConstPtr& msg){
77  //we assume that the odometry is published in the frame of the base
78  boost::mutex::scoped_lock lock(odom_lock_);
79  base_odom_.twist.twist.linear.x = msg->twist.twist.linear.x;
80  base_odom_.twist.twist.linear.y = msg->twist.twist.linear.y;
81  base_odom_.twist.twist.angular.z = msg->twist.twist.angular.z;
82  ROS_DEBUG("In the odometry callback with velocity values: (%.2f, %.2f, %.2f)",
83  base_odom_.twist.twist.linear.x, base_odom_.twist.twist.linear.y, base_odom_.twist.twist.angular.z);
84  }
85  */
86 
87  double HectorPathFollower::headingDiff(double x, double y, double pt_x, double pt_y, double heading)
88  {
89  double v1_x = x - pt_x;
90  double v1_y = y - pt_y;
91  double v2_x = cos(heading);
92  double v2_y = sin(heading);
93 
94  double perp_dot = v1_x * v2_y - v1_y * v2_x;
95  double dot = v1_x * v2_x + v1_y * v2_y;
96 
97  //get the signed angle
98  double vector_angle = atan2(perp_dot, dot);
99 
100  return -1.0 * vector_angle;
101  }
102 
103  /*
104  bool HectorPathFollower::stopped(){
105  //copy over the odometry information
106  nav_msgs::Odometry base_odom;
107  {
108  boost::mutex::scoped_lock lock(odom_lock_);
109  base_odom = base_odom_;
110  }
111 
112  return fabs(base_odom.twist.twist.angular.z) <= rot_stopped_velocity_
113  && fabs(base_odom.twist.twist.linear.x) <= trans_stopped_velocity_
114  && fabs(base_odom.twist.twist.linear.y) <= trans_stopped_velocity_;
115  }
116  */
117 
118  bool HectorPathFollower::computeVelocityCommands(geometry_msgs::Twist& cmd_vel){
119 
120  if (global_plan_.size() == 0)
121  return false;
122 
123  //get the current pose of the robot in the fixed frame
124  tf::Stamped<tf::Pose> robot_pose;
125  if(!this->getRobotPose(robot_pose)){
126  ROS_ERROR("Can't get robot pose");
127  geometry_msgs::Twist empty_twist;
128  cmd_vel = empty_twist;
129  return false;
130  }
131 
132 
133 
134  //we want to compute a velocity command based on our current waypoint
135  tf::Stamped<tf::Pose> target_pose;
137 
138  ROS_DEBUG("HectorPathFollower: current robot pose %f %f ==> %f", robot_pose.getOrigin().x(), robot_pose.getOrigin().y(), tf::getYaw(robot_pose.getRotation()));
139  ROS_DEBUG("HectorPathFollower: target robot pose %f %f ==> %f", target_pose.getOrigin().x(), target_pose.getOrigin().y(), tf::getYaw(target_pose.getRotation()));
140 
141  //get the difference between the two poses
142  geometry_msgs::Twist diff = diff2D(target_pose, robot_pose);
143  ROS_DEBUG("HectorPathFollower: diff %f %f ==> %f", diff.linear.x, diff.linear.y, diff.angular.z);
144 
145  geometry_msgs::Twist limit_vel = limitTwist(diff);
146 
147  geometry_msgs::Twist test_vel = limit_vel;
148  bool legal_traj = true; //collision_planner_.checkTrajectory(test_vel.linear.x, test_vel.linear.y, test_vel.angular.z, true);
149 
150  double scaling_factor = 1.0;
151  double ds = scaling_factor / samples_;
152 
153  //let's make sure that the velocity command is legal... and if not, scale down
154  if(!legal_traj){
155  for(int i = 0; i < samples_; ++i){
156  test_vel.linear.x = limit_vel.linear.x * scaling_factor;
157  test_vel.linear.y = limit_vel.linear.y * scaling_factor;
158  test_vel.angular.z = limit_vel.angular.z * scaling_factor;
159  test_vel = limitTwist(test_vel);
160  //if(collision_planner_.checkTrajectory(test_vel.linear.x, test_vel.linear.y, test_vel.angular.z, false)){
161  legal_traj = true;
162  break;
163  //}
164  scaling_factor -= ds;
165  }
166  }
167 
168  if(!legal_traj){
169  ROS_ERROR("Not legal (%.2f, %.2f, %.2f)", limit_vel.linear.x, limit_vel.linear.y, limit_vel.angular.z);
170  geometry_msgs::Twist empty_twist;
171  cmd_vel = empty_twist;
172  return false;
173  }
174 
175  //if it is legal... we'll pass it on
176  cmd_vel = test_vel;
177 
178  bool in_goal_position = false;
179  while(fabs(diff.linear.x) <= tolerance_trans_ &&
180  fabs(diff.linear.y) <= tolerance_trans_ &&
181  fabs(diff.angular.z) <= tolerance_rot_)
182  {
183  if(current_waypoint_ < global_plan_.size() - 1)
184  {
185  current_waypoint_++;
186  tf::poseStampedMsgToTF(global_plan_[current_waypoint_], target_pose);
187  diff = diff2D(target_pose, robot_pose);
188  }
189  else
190  {
191  ROS_INFO("Reached goal: %d", current_waypoint_);
192  in_goal_position = true;
193  break;
194  }
195  }
196 
197  //if we're not in the goal position, we need to update time
198  if(!in_goal_position)
200 
201  //check if we've reached our goal for long enough to succeed
203  geometry_msgs::Twist empty_twist;
204  cmd_vel = empty_twist;
205  }
206 
207  return true;
208  }
209 
210  bool HectorPathFollower::setPlan(const std::vector<geometry_msgs::PoseStamped>& global_plan){
211  current_waypoint_ = 0;
213  if(!transformGlobalPlan(*tf_, global_plan, p_global_frame_, global_plan_)){
214  ROS_ERROR("Could not transform the global plan to the frame of the controller");
215  return false;
216  }
217  return true;
218  }
219 
221  /*
222  //@TODO: Do something reasonable here
223  if(goal_reached_time_ + ros::Duration(tolerance_timeout_) < ros::Time::now() && stopped()){
224  return true;
225  }
226  */
227  return false;
228  }
229 
230  geometry_msgs::Twist HectorPathFollower::diff2D(const tf::Pose& pose1, const tf::Pose& pose2)
231  {
232  geometry_msgs::Twist res;
233  tf::Pose diff = pose2.inverse() * pose1;
234  res.linear.x = diff.getOrigin().x();
235  res.linear.y = diff.getOrigin().y();
236  res.angular.z = tf::getYaw(diff.getRotation());
237 
238  if(holonomic_ || (fabs(res.linear.x) <= tolerance_trans_ && fabs(res.linear.y) <= tolerance_trans_))
239  return res;
240 
241  //in the case that we're not rotating to our goal position and we have a non-holonomic robot
242  //we'll need to command a rotational velocity that will help us reach our desired heading
243 
244  //we want to compute a goal based on the heading difference between our pose and the target
245  double yaw_diff = headingDiff(pose1.getOrigin().x(), pose1.getOrigin().y(),
246  pose2.getOrigin().x(), pose2.getOrigin().y(), tf::getYaw(pose2.getRotation()));
247 
248  //we'll also check if we can move more effectively backwards
249  double neg_yaw_diff = headingDiff(pose1.getOrigin().x(), pose1.getOrigin().y(),
250  pose2.getOrigin().x(), pose2.getOrigin().y(), M_PI + tf::getYaw(pose2.getRotation()));
251 
252  //check if its faster to just back up
253  if(fabs(neg_yaw_diff) < fabs(yaw_diff)){
254  ROS_DEBUG("Negative is better: %.2f", neg_yaw_diff);
255  yaw_diff = neg_yaw_diff;
256  }
257 
258  //compute the desired quaterion
259  tf::Quaternion rot_diff = tf::createQuaternionFromYaw(yaw_diff);
260  tf::Quaternion rot = pose2.getRotation() * rot_diff;
261  tf::Pose new_pose = pose1;
262  new_pose.setRotation(rot);
263 
264  diff = pose2.inverse() * new_pose;
265  res.linear.x = diff.getOrigin().x();
266  res.linear.y = diff.getOrigin().y();
267  res.angular.z = tf::getYaw(diff.getRotation());
268  return res;
269  }
270 
271 
272  geometry_msgs::Twist HectorPathFollower::limitTwist(const geometry_msgs::Twist& twist)
273  {
274  geometry_msgs::Twist res = twist;
275  res.linear.x *= K_trans_;
276  if(!holonomic_)
277  res.linear.y = 0.0;
278  else
279  res.linear.y *= K_trans_;
280  res.angular.z *= K_rot_;
281 
282  //make sure to bound things by our velocity limits
283  double lin_overshoot = sqrt(res.linear.x * res.linear.x + res.linear.y * res.linear.y) / max_vel_lin_;
284  double lin_undershoot = min_vel_lin_ / sqrt(res.linear.x * res.linear.x + res.linear.y * res.linear.y);
285  if (lin_overshoot > 1.0)
286  {
287  res.linear.x /= lin_overshoot;
288  res.linear.y /= lin_overshoot;
289  }
290 
291  //we only want to enforce a minimum velocity if we're not rotating in place
292  if(lin_undershoot > 1.0)
293  {
294  res.linear.x *= lin_undershoot;
295  res.linear.y *= lin_undershoot;
296  }
297 
298  if (fabs(res.angular.z) > max_vel_th_) res.angular.z = max_vel_th_ * sign(res.angular.z);
299  if (fabs(res.angular.z) < min_vel_th_) res.angular.z = min_vel_th_ * sign(res.angular.z);
300 
301  //we want to check for whether or not we're desired to rotate in place
302  if(sqrt(twist.linear.x * twist.linear.x + twist.linear.y * twist.linear.y) < in_place_trans_vel_){
303  if (fabs(res.angular.z) < min_in_place_vel_th_) res.angular.z = min_in_place_vel_th_ * sign(res.angular.z);
304  res.linear.x = 0.0;
305  res.linear.y = 0.0;
306  }
307 
308  ROS_DEBUG("Angular command %f", res.angular.z);
309  return res;
310  }
311 
312  bool HectorPathFollower::transformGlobalPlan(const tf::TransformListener& tf, const std::vector<geometry_msgs::PoseStamped>& global_plan,
313  const std::string& global_frame,
314  std::vector<geometry_msgs::PoseStamped>& transformed_plan){
315  const geometry_msgs::PoseStamped& plan_pose = global_plan[0];
316 
317  transformed_plan.clear();
318 
319  try{
320  if (!global_plan.size() > 0)
321  {
322  ROS_ERROR("Received plan with zero length");
323  return false;
324  }
325 
326  tf::StampedTransform transform;
327  tf.lookupTransform(global_frame, ros::Time(),
328  plan_pose.header.frame_id, plan_pose.header.stamp,
329  plan_pose.header.frame_id, transform);
330 
331  tf::Stamped<tf::Pose> tf_pose;
332  geometry_msgs::PoseStamped newer_pose;
333  //now we'll transform until points are outside of our distance threshold
334  for(unsigned int i = 0; i < global_plan.size(); ++i){
335  const geometry_msgs::PoseStamped& pose = global_plan[i];
336  poseStampedMsgToTF(pose, tf_pose);
337  tf_pose.setData(transform * tf_pose);
338  tf_pose.stamp_ = transform.stamp_;
339  tf_pose.frame_id_ = global_frame;
340  poseStampedTFToMsg(tf_pose, newer_pose);
341 
342  transformed_plan.push_back(newer_pose);
343  }
344  }
345  catch(tf::LookupException& ex) {
346  ROS_ERROR("No Transform available Error: %s\n", ex.what());
347  return false;
348  }
349  catch(tf::ConnectivityException& ex) {
350  ROS_ERROR("Connectivity Error: %s\n", ex.what());
351  return false;
352  }
353  catch(tf::ExtrapolationException& ex) {
354  ROS_ERROR("Extrapolation Error: %s\n", ex.what());
355  if (global_plan.size() > 0)
356  ROS_ERROR("Global Frame: %s Plan Frame size %d: %s\n", global_frame.c_str(), (unsigned int)global_plan.size(), global_plan[0].header.frame_id.c_str());
357 
358  return false;
359  }
360 
361  return true;
362  }
363 
364 
366 
367  global_pose.setIdentity();
368  tf::Stamped<tf::Pose> robot_pose;
369  robot_pose.setIdentity();
370  robot_pose.frame_id_ = p_robot_base_frame_;
371  robot_pose.stamp_ = ros::Time(0);
372  ros::Time current_time = ros::Time::now(); // save time for checking tf delay later
373 
374  //get the global pose of the robot
375  try{
376  tf_->transformPose(p_global_frame_, robot_pose, global_pose);
377  }
378  catch(tf::LookupException& ex) {
379  ROS_ERROR_THROTTLE(1.0, "No Transform available Error looking up robot pose: %s\n", ex.what());
380  return false;
381  }
382  catch(tf::ConnectivityException& ex) {
383  ROS_ERROR_THROTTLE(1.0, "Connectivity Error looking up robot pose: %s\n", ex.what());
384  return false;
385  }
386  catch(tf::ExtrapolationException& ex) {
387  ROS_ERROR_THROTTLE(1.0, "Extrapolation Error looking up robot pose: %s\n", ex.what());
388  return false;
389  }
390  // check global_pose timeout
391 
392  /*
393  if (current_time.toSec() - global_pose.stamp_.toSec() > transform_tolerance_) {
394  ROS_WARN_THROTTLE(1.0, "Costmap2DROS transform timeout. Current time: %.4f, global_pose stamp: %.4f, tolerance: %.4f",
395  current_time.toSec() ,global_pose.stamp_.toSec() ,transform_tolerance_);
396  return false;
397  }
398  */
399 
400 
401  return true;
402  }
403 };
TFSIMD_FORCE_INLINE void setRotation(const Quaternion &q)
bool setPlan(const std::vector< geometry_msgs::PoseStamped > &global_plan)
void setData(const T &input)
static double getYaw(const Quaternion &bt_q)
geometry_msgs::Twist diff2D(const tf::Pose &pose1, const tf::Pose &pose2)
bool getRobotPose(tf::Stamped< tf::Pose > &global_pose) const
TFSIMD_FORCE_INLINE const tfScalar & x() const
#define ROS_ERROR_THROTTLE(rate,...)
double headingDiff(double pt_x, double pt_y, double x, double y, double heading)
static Quaternion createQuaternionFromYaw(double yaw)
#define ROS_INFO(...)
bool param(const std::string &param_name, T &param_val, const T &default_val) const
TFSIMD_FORCE_INLINE const tfScalar & y() const
Transform inverse() const
bool transformGlobalPlan(const tf::TransformListener &tf, const std::vector< geometry_msgs::PoseStamped > &global_plan, const std::string &global_frame, std::vector< geometry_msgs::PoseStamped > &transformed_plan)
TFSIMD_FORCE_INLINE tfScalar dot(const Quaternion &q1, const Quaternion &q2)
TFSIMD_FORCE_INLINE Vector3 & getOrigin()
void lookupTransform(const std::string &target_frame, const std::string &source_frame, const ros::Time &time, StampedTransform &transform) const
void initialize(tf::TransformListener *tf)
ros::Time stamp_
bool computeVelocityCommands(geometry_msgs::Twist &cmd_vel)
std::vector< geometry_msgs::PoseStamped > global_plan_
std::string frame_id_
void transformPose(const std::string &target_frame, const geometry_msgs::PoseStamped &stamped_in, geometry_msgs::PoseStamped &stamped_out) const
Quaternion getRotation() const
static Time now()
static void poseStampedMsgToTF(const geometry_msgs::PoseStamped &msg, Stamped< Pose > &bt)
geometry_msgs::Twist limitTwist(const geometry_msgs::Twist &twist)
#define ROS_ERROR(...)
#define ROS_DEBUG(...)


hector_path_follower
Author(s):
autogenerated on Mon Jun 10 2019 13:34:49