graceful_controller_ros.cpp
Go to the documentation of this file.
1 /*********************************************************************
2  *
3  * Software License Agreement (BSD License)
4  *
5  * Copyright (c) 2021-2023, Michael Ferguson
6  * Copyright (c) 2009, Willow Garage, Inc.
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * * Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  * * Redistributions in binary form must reproduce the above
16  * copyright notice, this list of conditions and the following
17  * disclaimer in the documentation and/or other materials provided
18  * with the distribution.
19  * * Neither the name of Willow Garage, Inc. nor the names of its
20  * contributors may be used to endorse or promote products derived
21  * from this software without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34  * POSSIBILITY OF SUCH DAMAGE.
35  *
36  * Author: Eitan Marder-Eppstein, Michael Ferguson
37  *********************************************************************/
38 
39 #include <cmath>
40 
41 #include <angles/angles.h>
44 #include <costmap_2d/footprint.h>
47 #include <std_msgs/Float32.h>
48 
50 
51 namespace graceful_controller
52 {
53 double sign(double x)
54 {
55  return x < 0.0 ? -1.0 : 1.0;
56 }
57 
66 bool isColliding(double x, double y, double theta, costmap_2d::Costmap2DROS* costmap,
67  visualization_msgs::MarkerArray* viz, double inflation = 1.0)
68 {
69  unsigned mx, my;
70  if (!costmap->getCostmap()->worldToMap(x, y, mx, my))
71  {
72  ROS_DEBUG("Path is off costmap (%f,%f)", x, y);
73  addPointMarker(x, y, true, viz);
74  return true;
75  }
76 
77  if (inflation < 1.0)
78  {
79  ROS_WARN("Inflation ratio cannot be less than 1.0");
80  inflation = 1.0;
81  }
82 
83  // Get footprint (centered around robot)
84  std::vector<geometry_msgs::Point> spec = costmap->getRobotFootprint();
85 
86  // Expand footprint by desired infation
87  for (size_t i = 0; i < spec.size(); ++i)
88  {
89  spec[i].x *= inflation;
90  spec[i].y *= inflation;
91  }
92 
93  // Transform footprint to robot pose
94  std::vector<geometry_msgs::Point> footprint;
95  costmap_2d::transformFootprint(x, y, theta, spec, footprint);
96 
97  // If our footprint is less than 4 corners, treat as circle
98  if (footprint.size() < 4)
99  {
101  {
102  ROS_DEBUG("Collision along path at (%f,%f)", x, y);
103  addPointMarker(x, y, true, viz);
104  return true;
105  }
106  // Done collison checking
107  return false;
108  }
109 
110  // Do a complete collision check of the footprint boundary
111  for (size_t i = 0; i < footprint.size(); ++i)
112  {
113  unsigned x0, y0, x1, y1;
114  if (!costmap->getCostmap()->worldToMap(footprint[i].x, footprint[i].y, x0, y0))
115  {
116  ROS_DEBUG("Footprint point %lu is off costmap", i);
117  addPointMarker(footprint[i].x, footprint[i].y, true, viz);
118  return true;
119  }
120  addPointMarker(footprint[i].x, footprint[i].y, false, viz);
121 
122  size_t next = (i + 1) % footprint.size();
123  if (!costmap->getCostmap()->worldToMap(footprint[next].x, footprint[next].y, x1, y1))
124  {
125  ROS_DEBUG("Footprint point %lu is off costmap", next);
126  addPointMarker(footprint[next].x, footprint[next].y, true, viz);
127  return true;
128  }
129  addPointMarker(footprint[next].x, footprint[next].y, false, viz);
130 
131  for (base_local_planner::LineIterator line(x0, y0, x1, y1); line.isValid(); line.advance())
132  {
133  if (costmap->getCostmap()->getCost(line.getX(), line.getY()) >= costmap_2d::LETHAL_OBSTACLE)
134  {
135  ROS_DEBUG("Collision along path at (%f,%f)", x, y);
136  return true;
137  }
138  }
139  }
140 
141  // Not colliding
142  return false;
143 }
144 
145 GracefulControllerROS::GracefulControllerROS() : initialized_(false), has_new_path_(false), collision_points_(NULL)
146 {
147 }
148 
149 GracefulControllerROS::~GracefulControllerROS()
150 {
151  if (dsrv_)
152  {
153  delete dsrv_;
154  }
155  if (collision_points_)
156  {
157  delete collision_points_;
158  }
159 }
160 
161 void GracefulControllerROS::initialize(std::string name, tf2_ros::Buffer* tf, costmap_2d::Costmap2DROS* costmap_ros)
162 {
163  if (!initialized_)
164  {
165  // Publishers (same topics as DWA/TrajRollout)
166  ros::NodeHandle private_nh("~/" + name);
167  global_plan_pub_ = private_nh.advertise<nav_msgs::Path>("global_plan", 1);
168  local_plan_pub_ = private_nh.advertise<nav_msgs::Path>("local_plan", 1);
169  target_pose_pub_ = private_nh.advertise<geometry_msgs::PoseStamped>("target_pose", 1);
170 
171  buffer_ = tf;
172  costmap_ros_ = costmap_ros;
173  costmap_2d::Costmap2D* costmap = costmap_ros_->getCostmap();
174  planner_util_.initialize(tf, costmap, costmap_ros_->getGlobalFrameID());
175 
176  bool publish_collision_points = false;
177  private_nh.getParam("publish_collision_points", publish_collision_points);
178  if (publish_collision_points)
179  {
180  // Create publisher
181  collision_point_pub_ = private_nh.advertise<visualization_msgs::MarkerArray>("collision_points", 1);
182 
183  // Create message to publish
184  collision_points_ = new visualization_msgs::MarkerArray();
185  }
186 
187  std::string odom_topic;
188  if (private_nh.getParam("odom_topic", odom_topic))
189  {
190  odom_helper_.setOdomTopic(odom_topic);
191  private_nh.param("acc_dt", acc_dt_, 0.25);
192  }
193 
194  bool use_vel_topic = false;
195  private_nh.getParam("use_vel_topic", use_vel_topic);
196  if (use_vel_topic)
197  {
198  ros::NodeHandle nh;
199  max_vel_sub_ = nh.subscribe<std_msgs::Float32>("max_vel_x", 1,
200  boost::bind(&GracefulControllerROS::velocityCallback, this, _1));
201  }
202 
203  // Dynamic reconfigure is really only intended for tuning controller!
204  dsrv_ = new dynamic_reconfigure::Server<GracefulControllerConfig>(private_nh);
205  dynamic_reconfigure::Server<GracefulControllerConfig>::CallbackType cb =
206  boost::bind(&GracefulControllerROS::reconfigureCallback, this, _1, _2);
207  dsrv_->setCallback(cb);
208 
209  initialized_ = true;
210  }
211  else
212  {
213  ROS_WARN("This planner has already been initialized, doing nothing.");
214  }
215 }
216 
217 void GracefulControllerROS::reconfigureCallback(GracefulControllerConfig& config, uint32_t level)
218 {
219  // Lock the mutex
220  std::lock_guard<std::mutex> lock(config_mutex_);
221 
222  // Need to configure the planner utils
224  limits.max_vel_trans = 0.0;
225  limits.min_vel_trans = 0.0;
226  limits.max_vel_x = config.max_vel_x;
227  limits.min_vel_x = config.min_vel_x;
228  limits.max_vel_y = 0.0;
229  limits.min_vel_y = 0.0;
230  limits.max_vel_theta = config.max_vel_theta;
231  limits.acc_lim_x = config.acc_lim_x;
232  limits.acc_lim_y = 0.0;
233  limits.acc_lim_theta = config.acc_lim_theta;
234  limits.acc_lim_trans = 0.0;
235  limits.xy_goal_tolerance = config.xy_goal_tolerance;
236  limits.yaw_goal_tolerance = config.yaw_goal_tolerance;
237  limits.prune_plan = false;
238  limits.trans_stopped_vel = 0.0;
239  limits.theta_stopped_vel = 0.0;
240  planner_util_.reconfigureCB(limits, false);
241 
242  max_vel_x_ = config.max_vel_x;
243  min_vel_x_ = config.min_vel_x;
244  max_vel_theta_ = config.max_vel_theta;
245  min_in_place_vel_theta_ = config.min_in_place_vel_theta;
246  max_x_to_max_theta_scale_factor_ = config.max_x_to_max_theta_scale_factor;
247  acc_lim_x_ = config.acc_lim_x;
248  acc_lim_theta_ = config.acc_lim_theta;
249  decel_lim_x_ = config.decel_lim_x;
250  xy_goal_tolerance_ = config.xy_goal_tolerance;
251  yaw_goal_tolerance_ = config.yaw_goal_tolerance;
252  xy_vel_goal_tolerance_ = config.xy_vel_goal_tolerance;
253  yaw_vel_goal_tolerance_ = config.yaw_vel_goal_tolerance;
254  min_lookahead_ = config.min_lookahead;
255  max_lookahead_ = config.max_lookahead;
256  initial_rotate_tolerance_ = config.initial_rotate_tolerance;
257  prefer_final_rotation_ = config.prefer_final_rotation;
258  compute_orientations_ = config.compute_orientations;
259  use_orientation_filter_ = config.use_orientation_filter;
260  yaw_filter_tolerance_ = config.yaw_filter_tolerance;
261  yaw_gap_tolerance_ = config.yaw_goal_tolerance;
262  latch_xy_goal_tolerance_ = config.latch_xy_goal_tolerance;
263  resolution_ = planner_util_.getCostmap()->getResolution();
264 
265  if (decel_lim_x_ < 0.001)
266  {
267  // If decel limit not specified, use accel limit
268  decel_lim_x_ = acc_lim_x_;
269  }
270 
271  if (max_x_to_max_theta_scale_factor_ < 0.001)
272  {
273  // If max_x_to_max_theta_scale_factor not specified, use a high value so it has no functional impact
274  max_x_to_max_theta_scale_factor_ = 100.0;
275  }
276 
277  // limit maximum angular velocity proportional to maximum linear velocity
278  // so we don't make fast in-place turns in areas with low speed limits
279  max_vel_theta_limited_ = max_vel_x_ * max_x_to_max_theta_scale_factor_;
280  max_vel_theta_limited_ = std::min(max_vel_theta_limited_, max_vel_theta_);
281 
282  controller_ =
283  std::make_shared<GracefulController>(config.k1, config.k2, config.min_vel_x, config.max_vel_x, decel_lim_x_,
284  config.max_vel_theta, config.beta, config.lambda);
285 
286  scaling_vel_x_ = std::max(config.scaling_vel_x, config.min_vel_x);
287  scaling_factor_ = config.scaling_factor;
288  scaling_step_ = config.scaling_step;
289 }
290 
291 bool GracefulControllerROS::computeVelocityCommands(geometry_msgs::Twist& cmd_vel)
292 {
293  if (!initialized_)
294  {
295  ROS_ERROR(
296  "Planner is not initialized, call initialize() before using this "
297  "planner");
298  return false;
299  }
300 
301  // Lock the mutex
302  std::lock_guard<std::mutex> lock(config_mutex_);
303 
304  if (!costmap_ros_->getRobotPose(robot_pose_))
305  {
306  ROS_ERROR("Could not get the robot pose");
307  return false;
308  }
309 
310  std::vector<geometry_msgs::PoseStamped> transformed_plan;
311  if (!planner_util_.getLocalPlan(robot_pose_, transformed_plan))
312  {
313  ROS_ERROR("Could not get local plan");
314  return false;
315  }
316 
317  base_local_planner::publishPlan(transformed_plan, global_plan_pub_);
318 
319  if (transformed_plan.empty())
320  {
321  ROS_WARN("Received an empty transform plan");
322  return false;
323  }
324 
325  // Get transforms
326  geometry_msgs::TransformStamped costmap_to_robot;
327  try
328  {
329  costmap_to_robot = buffer_->lookupTransform(costmap_ros_->getBaseFrameID(), costmap_ros_->getGlobalFrameID(),
330  ros::Time(), ros::Duration(0.5));
331  tf2::Stamped<tf2::Transform> tf2_transform;
332  tf2::convert(costmap_to_robot, tf2_transform);
333  tf2_transform.setData(tf2_transform.inverse());
334  robot_to_costmap_transform_ = tf2::toMsg(tf2_transform);
335  }
336  catch (tf2::TransformException& ex)
337  {
338  ROS_ERROR("Could not transform to %s", costmap_ros_->getBaseFrameID().c_str());
339  return false;
340  }
341 
342  // Get the overall goal
343  geometry_msgs::PoseStamped goal_pose;
344  if (!planner_util_.getGoal(goal_pose))
345  {
346  ROS_ERROR("Unable to get goal");
347  return false;
348  }
349 
350  // Get current robot speed
351  double robot_vel_x = 0.0, robot_vel_yaw = 0.0;
352  bool below_velocity_limits = true;
353  if (!odom_helper_.getOdomTopic().empty())
354  {
355  // The API of the OdometryHelperROS uses a PoseStamped
356  // but the data returned is velocities (should be a Twist)
357  geometry_msgs::PoseStamped robot_velocity;
358  odom_helper_.getRobotVel(robot_velocity);
359  robot_vel_x = fabs(robot_velocity.pose.position.x);
360  robot_vel_yaw = fabs(tf2::getYaw(robot_velocity.pose.orientation));
361  if (robot_vel_x > xy_vel_goal_tolerance_ ||
362  robot_vel_yaw > yaw_vel_goal_tolerance_)
363  {
364  // Not sufficiently stopped
365  below_velocity_limits = false;
366  }
367  }
368 
369  // Compute distance to goal
370  double dist_to_goal = std::hypot(goal_pose.pose.position.x - robot_pose_.pose.position.x,
371  goal_pose.pose.position.y - robot_pose_.pose.position.y);
372 
373  // If we've reached the XY goal tolerance, just rotate
374  if ((dist_to_goal < xy_goal_tolerance_ && below_velocity_limits) || goal_tolerance_met_)
375  {
376  // Reached goal, latch if desired
377  goal_tolerance_met_ = latch_xy_goal_tolerance_;
378  // Compute velocity required to rotate towards goal
379  tf2::doTransform(transformed_plan.back(), goal_pose, costmap_to_robot);
380  rotateTowards(tf2::getYaw(goal_pose.pose.orientation), cmd_vel);
381  // Check for collisions between our current pose and goal
382  double yaw_start = tf2::getYaw(robot_pose_.pose.orientation);
383  double yaw_end = tf2::getYaw(goal_pose.pose.orientation);
384  size_t num_steps = fabs(yaw_end - yaw_start) / 0.1;
385  // Need to check at least the end pose
386  if (num_steps < 1)
387  {
388  num_steps = 1;
389  }
390  // If we fail to generate an in place rotation, maybe we need to move along path a bit more
391  bool collision_free = true;
392  for (size_t i = 1; i <= num_steps; ++i)
393  {
394  double step = static_cast<double>(i) / static_cast<double>(num_steps);
395  double yaw = yaw_start + (step * (yaw_start - yaw_end));
396  if (isColliding(robot_pose_.pose.position.x, robot_pose_.pose.position.y, yaw, costmap_ros_, collision_points_))
397  {
398  ROS_WARN("Unable to rotate in place due to collision.");
399  if (collision_points_)
400  {
401  collision_point_pub_.publish(*collision_points_);
402  }
403  collision_free = false;
404  break;
405  }
406  }
407  if (collision_free)
408  {
409  // Safe to rotate, execute computed command
410  return true;
411  }
412  // Otherwise, fall through and try to get closer to goal in XY
413  }
414 
415  // Get controller max velocity based on current speed
416  double max_vel_x = max_vel_x_;
417  if (!odom_helper_.getOdomTopic().empty())
418  {
419  if (robot_vel_x > max_vel_x)
420  {
421  // If our velocity limit has recently changed,
422  // decelerate towards desired max_vel_x while still respecting acceleration limits
423  double decelerating_max_vel_x = robot_vel_x - (decel_lim_x_ * acc_dt_);
424  max_vel_x = std::max(max_vel_x, decelerating_max_vel_x);
425  max_vel_x = std::max(max_vel_x, min_vel_x_);
426  }
427  else
428  {
429  // Otherwise, allow up to max acceleration
430  max_vel_x = robot_vel_x + (acc_lim_x_ * acc_dt_);
431  max_vel_x = std::min(max_vel_x, max_vel_x_);
432  max_vel_x = std::max(max_vel_x, min_vel_x_);
433  }
434  }
435 
436  // Compute distance along path
437  std::vector<geometry_msgs::PoseStamped> target_poses;
438  std::vector<double> target_distances;
439  for (auto pose : transformed_plan)
440  {
441  // Transform potential target pose into base_link
442  geometry_msgs::PoseStamped transformed_pose;
443  tf2::doTransform(pose, transformed_pose, costmap_to_robot);
444  target_poses.push_back(transformed_pose);
445  }
446  computeDistanceAlongPath(target_poses, target_distances);
447 
448  // Work back from the end of plan to find valid target pose
449  for (int i = transformed_plan.size() - 1; i >= 0; --i)
450  {
451  // Underlying control law needs a single target pose, which should:
452  // * Be as far away as possible from the robot (for smoothness)
453  // * But no further than the max_lookahed_ distance
454  // * Be feasible to reach in a collision free manner
455  geometry_msgs::PoseStamped target_pose = target_poses[i];
456  double dist_to_target = target_distances[i];
457 
458  // Continue if target_pose is too far away from robot
459  if (dist_to_target > max_lookahead_)
460  {
461  continue;
462  }
463 
464  if (dist_to_goal < max_lookahead_)
465  {
466  if (prefer_final_rotation_)
467  {
468  // Avoid unstability and big sweeping turns at the end of paths by
469  // ignoring final heading
470  double yaw = std::atan2(target_pose.pose.position.y, target_pose.pose.position.x);
471  target_pose.pose.orientation.z = sin(yaw / 2.0);
472  target_pose.pose.orientation.w = cos(yaw / 2.0);
473  }
474  }
475  else if (dist_to_target < min_lookahead_)
476  {
477  // Make sure target is far enough away to avoid instability
478  break;
479  }
480 
481  // Iteratively try to find a path, incrementally reducing the velocity
482  double sim_velocity = max_vel_x;
483  do
484  {
485  // Configure controller max velocity
486  controller_->setVelocityLimits(min_vel_x_, sim_velocity, max_vel_theta_limited_);
487  // Actually simulate our path
488  if (simulate(target_pose, cmd_vel))
489  {
490  // Have valid command
491  return true;
492  }
493  // Reduce velocity and try again for same target_pose
494  sim_velocity -= scaling_step_;
495  }
496  while (sim_velocity >= scaling_vel_x_);
497  }
498 
499  ROS_ERROR("No pose in path was reachable");
500  return false;
501 }
502 
503 bool GracefulControllerROS::simulate(const geometry_msgs::PoseStamped& target_pose, geometry_msgs::Twist& cmd_vel)
504 {
505  // Simulated path (for debugging/visualization)
506  std::vector<geometry_msgs::PoseStamped> simulated_path;
507  // Should we simulate rotation initially
508  bool sim_initial_rotation_ = has_new_path_ && initial_rotate_tolerance_ > 0.0;
509  // Clear any previous visualizations
510  if (collision_points_)
511  {
512  collision_points_->markers.resize(0);
513  }
514  // Get control and path, iteratively
515  while (true)
516  {
517  // The error between current robot pose and the target pose
518  geometry_msgs::PoseStamped error = target_pose;
519 
520  // Extract error_angle
521  double error_angle = tf2::getYaw(error.pose.orientation);
522 
523  // Move origin to our current simulated pose
524  if (!simulated_path.empty())
525  {
526  double x = error.pose.position.x - simulated_path.back().pose.position.x;
527  double y = error.pose.position.y - simulated_path.back().pose.position.y;
528 
529  double theta = -tf2::getYaw(simulated_path.back().pose.orientation);
530  error.pose.position.x = x * cos(theta) - y * sin(theta);
531  error.pose.position.y = y * cos(theta) + x * sin(theta);
532 
533  error_angle += theta;
534  error.pose.orientation.z = sin(error_angle / 2.0);
535  error.pose.orientation.w = cos(error_angle / 2.0);
536  }
537 
538  // Compute commands
539  double vel_x, vel_th;
540  if (sim_initial_rotation_)
541  {
542  geometry_msgs::Twist rotation;
543  if (fabs(rotateTowards(error, rotation)) < initial_rotate_tolerance_)
544  {
545  if (simulated_path.empty())
546  {
547  // Current robot pose satisifies initial rotate tolerance
548  ROS_WARN("Done rotating towards path");
549  has_new_path_ = false;
550  }
551  sim_initial_rotation_ = false;
552  }
553  vel_x = rotation.linear.x;
554  vel_th = rotation.angular.z;
555  }
556 
557  if (!sim_initial_rotation_)
558  {
559  if (!controller_->approach(error.pose.position.x, error.pose.position.y, error_angle, vel_x, vel_th))
560  {
561  ROS_ERROR("Unable to compute approach");
562  return false;
563  }
564  }
565 
566  if (simulated_path.empty())
567  {
568  // First iteration of simulation, store our commands to the robot
569  cmd_vel.linear.x = vel_x;
570  cmd_vel.angular.z = vel_th;
571  }
572  else if (std::hypot(error.pose.position.x, error.pose.position.y) < resolution_)
573  {
574  // We've simulated to the desired pose, can return this result
575  base_local_planner::publishPlan(simulated_path, local_plan_pub_);
576  target_pose_pub_.publish(target_pose);
577  // Publish visualization if desired
578  if (collision_points_)
579  {
580  collision_point_pub_.publish(*collision_points_);
581  }
582  return true;
583  }
584 
585  // Forward simulate command
586  geometry_msgs::PoseStamped next_pose;
587  next_pose.header.frame_id = costmap_ros_->getBaseFrameID();
588  if (simulated_path.empty())
589  {
590  // Initialize at origin
591  next_pose.pose.orientation.w = 1.0;
592  }
593  else
594  {
595  // Start at last pose
596  next_pose = simulated_path.back();
597  }
598 
599  // Generate next pose
600  double dt = (vel_x > 0.0) ? resolution_ / vel_x : 0.1;
601  double yaw = tf2::getYaw(next_pose.pose.orientation);
602  next_pose.pose.position.x += dt * vel_x * cos(yaw);
603  next_pose.pose.position.y += dt * vel_x * sin(yaw);
604  yaw += dt * vel_th;
605  next_pose.pose.orientation.z = sin(yaw / 2.0);
606  next_pose.pose.orientation.w = cos(yaw / 2.0);
607  simulated_path.push_back(next_pose);
608 
609  // Compute footprint scaling
610  double footprint_scaling = 1.0;
611  if (vel_x > scaling_vel_x_)
612  {
613  // Scaling = (vel_x - scaling_vel_x) / (max_vel_x - scaling_vel_x)
614  // NOTE: max_vel_x_ is possibly changing from ROS topic
615  double ratio = max_vel_x_ - scaling_vel_x_;
616  // Avoid divide by zero
617  if (ratio > 0)
618  {
619  ratio = (vel_x - scaling_vel_x_) / ratio;
620  footprint_scaling += ratio * scaling_factor_;
621  }
622  }
623 
624  // Check next pose for collision
625  tf2::doTransform(next_pose, next_pose, robot_to_costmap_transform_);
626  if (isColliding(next_pose.pose.position.x, next_pose.pose.position.y, tf2::getYaw(next_pose.pose.orientation),
627  costmap_ros_, collision_points_, footprint_scaling))
628  {
629  // Publish visualization if desired
630  if (collision_points_)
631  {
632  collision_point_pub_.publish(*collision_points_);
633  }
634  // Reason will be printed in function
635  return false;
636  }
637  }
638 
639  // Really shouldn't hit this
640  ROS_ERROR("Did not reach target_pose, but stopped simulating?");
641  return false;
642 }
643 
644 bool GracefulControllerROS::isGoalReached()
645 {
646  if (!initialized_)
647  {
648  ROS_ERROR(
649  "Planner is not initialized, call initialize() before using this "
650  "planner");
651  return false;
652  }
653 
654  if (!costmap_ros_->getRobotPose(robot_pose_))
655  {
656  ROS_ERROR("Could not get the robot pose");
657  return false;
658  }
659 
660  geometry_msgs::PoseStamped goal;
661  if (!planner_util_.getGoal(goal))
662  {
663  ROS_ERROR("Unable to get goal");
664  return false;
665  }
666 
667  double dist = std::hypot(goal.pose.position.x - robot_pose_.pose.position.x,
668  goal.pose.position.y - robot_pose_.pose.position.y);
669 
670  double angle =
671  angles::shortest_angular_distance(tf2::getYaw(goal.pose.orientation), tf2::getYaw(robot_pose_.pose.orientation));
672 
673  double dist_reached = goal_tolerance_met_ || (dist < xy_goal_tolerance_);
674 
675  bool below_velocity_limits = true;
676  if (!odom_helper_.getOdomTopic().empty())
677  {
678  // The API of the OdometryHelperROS uses a PoseStamped
679  // but the data returned is velocities (should be a Twist)
680  geometry_msgs::PoseStamped robot_velocity;
681  odom_helper_.getRobotVel(robot_velocity);
682  double robot_vel_x = fabs(robot_velocity.pose.position.x);
683  double robot_vel_yaw = fabs(tf2::getYaw(robot_velocity.pose.orientation));
684  if (robot_vel_x > xy_vel_goal_tolerance_ ||
685  robot_vel_yaw > yaw_vel_goal_tolerance_)
686  {
687  // Not sufficiently stopped
688  below_velocity_limits = false;
689  }
690  }
691 
692  return dist_reached && (fabs(angle) < yaw_goal_tolerance_) && below_velocity_limits;
693 }
694 
695 bool GracefulControllerROS::setPlan(const std::vector<geometry_msgs::PoseStamped>& plan)
696 {
697  if (!initialized_)
698  {
699  ROS_ERROR(
700  "Planner is not initialized, call initialize() before using this "
701  "planner");
702  return false;
703  }
704 
705  // We need orientations on our poses
706  std::vector<geometry_msgs::PoseStamped> oriented_plan;
707  if (compute_orientations_)
708  {
709  oriented_plan = addOrientations(plan);
710  }
711  else
712  {
713  oriented_plan = plan;
714  }
715 
716  // Filter noisy orientations (if desired)
717  std::vector<geometry_msgs::PoseStamped> filtered_plan;
718  if (use_orientation_filter_)
719  {
720  filtered_plan = applyOrientationFilter(oriented_plan, yaw_filter_tolerance_, yaw_gap_tolerance_);
721  }
722  else
723  {
724  filtered_plan = oriented_plan;
725  }
726 
727  // Store the plan for computeVelocityCommands
728  if (planner_util_.setPlan(filtered_plan))
729  {
730  // Reset flags
731  has_new_path_ = true;
732  goal_tolerance_met_ = false;
733  ROS_INFO("Recieved a new path with %lu points", filtered_plan.size());
734  return true;
735  }
736 
737  // Signal error
738  return false;
739 }
740 
741 double GracefulControllerROS::rotateTowards(const geometry_msgs::PoseStamped& pose, geometry_msgs::Twist& cmd_vel)
742 {
743  // Determine error
744  double yaw = 0.0;
745  if (std::hypot(pose.pose.position.x, pose.pose.position.y) > 0.5)
746  {
747  // Goal is far away, point towards goal
748  yaw = std::atan2(pose.pose.position.y, pose.pose.position.x);
749  }
750  else
751  {
752  // Goal is nearby, align heading
753  yaw = tf2::getYaw(pose.pose.orientation);
754  }
755 
756  ROS_DEBUG_NAMED("graceful_controller", "Rotating towards goal, error = %f", yaw);
757 
758  // Compute command velocity
759  rotateTowards(yaw, cmd_vel);
760 
761  // Return error
762  return yaw;
763 }
764 
765 void GracefulControllerROS::rotateTowards(double yaw, geometry_msgs::Twist& cmd_vel)
766 {
767  // Determine max velocity based on current speed
768  double max_vel_th = max_vel_theta_limited_;
769  if (!odom_helper_.getOdomTopic().empty())
770  {
771  // The API of the OdometryHelperROS uses a PoseStamped
772  // but the data returned is velocities (should be a Twist)
773  geometry_msgs::PoseStamped robot_velocity;
774  odom_helper_.getRobotVel(robot_velocity);
775  double abs_vel = fabs(tf2::getYaw(robot_velocity.pose.orientation));
776  double acc_limited = abs_vel + (acc_lim_theta_ * acc_dt_);
777  max_vel_th = std::min(max_vel_th, acc_limited);
778  max_vel_th = std::max(max_vel_th, min_in_place_vel_theta_);
779  }
780 
781  cmd_vel.linear.x = 0.0;
782  cmd_vel.angular.z = std::sqrt(2 * acc_lim_theta_ * fabs(yaw));
783  cmd_vel.angular.z = sign(yaw) * std::min(max_vel_th, std::max(min_in_place_vel_theta_, cmd_vel.angular.z));
784 }
785 
786 void GracefulControllerROS::velocityCallback(const std_msgs::Float32::ConstPtr& max_vel_x)
787 {
788  // Lock the mutex
789  std::lock_guard<std::mutex> lock(config_mutex_);
790 
791  max_vel_x_ = std::max(static_cast<double>(max_vel_x->data), min_vel_x_);
792  // also limit maximum angular velocity proportional to maximum linear velocity
793  // so we don't make fast in-place turns in areas with low speed limits
794  max_vel_theta_limited_ = max_vel_x_ * max_x_to_max_theta_scale_factor_;
795  max_vel_theta_limited_ = std::min(max_vel_theta_limited_, max_vel_theta_);
796 }
797 
798 void computeDistanceAlongPath(const std::vector<geometry_msgs::PoseStamped>& poses,
799  std::vector<double>& distances)
800 {
801  distances.resize(poses.size());
802 
803  // First compute distance from robot to pose
804  for (size_t i = 0; i < poses.size(); ++i)
805  {
806  // Determine distance from robot to pose
807  distances[i] = std::hypot(poses[i].pose.position.x, poses[i].pose.position.y);
808  }
809 
810  // Find the closest target pose
811  auto closest = std::min_element(std::begin(distances), std::end(distances));
812 
813  // Sum distances between poses, starting with the closest pose
814  // Yes, the poses behind the robot will still use euclidean distance from robot, but we don't use those anyways
815  for (size_t i = std::distance(std::begin(distances), closest) + 1; i < distances.size(); ++i)
816  {
817  distances[i] = distances[i - 1] +
818  std::hypot(poses[i].pose.position.x - poses[i - 1].pose.position.x,
819  poses[i].pose.position.y - poses[i - 1].pose.position.y);
820  }
821 }
822 
823 } // namespace graceful_controller
824 
graceful_controller::GracefulControllerROS::GracefulControllerROS
GracefulControllerROS()
Definition: graceful_controller_ros.cpp:181
tf2::convert
void convert(const A &a, B &b)
graceful_controller::applyOrientationFilter
std::vector< geometry_msgs::PoseStamped > applyOrientationFilter(const std::vector< geometry_msgs::PoseStamped > &path, double yaw_tolerance, double gap_tolerance)
Filter a path for orientation noise.
Definition: orientation_tools.cpp:120
angles::shortest_angular_distance
static double shortest_angular_distance(double from, double to)
costmap_2d::transformFootprint
void transformFootprint(double x, double y, double theta, const std::vector< geometry_msgs::Point > &footprint_spec, geometry_msgs::PolygonStamped &oriented_footprint)
graceful_controller::isColliding
bool isColliding(double x, double y, double theta, costmap_2d::Costmap2DROS *costmap, visualization_msgs::MarkerArray *viz, double inflation=1.0)
Collision check the robot pose.
Definition: graceful_controller_ros.cpp:102
angle
TFSIMD_FORCE_INLINE tfScalar angle(const Quaternion &q1, const Quaternion &q2)
base_local_planner::LocalPlannerLimits::prune_plan
bool prune_plan
graceful_controller_ros.hpp
costmap_2d::Costmap2D::worldToMap
bool worldToMap(double wx, double wy, unsigned int &mx, unsigned int &my) const
tf2::Stamped::setData
void setData(const T &input)
tf2::getYaw
double getYaw(const A &a)
costmap_2d::INSCRIBED_INFLATED_OBSTACLE
static const unsigned char INSCRIBED_INFLATED_OBSTACLE
base_local_planner::LocalPlannerLimits::xy_goal_tolerance
double xy_goal_tolerance
base_local_planner::LocalPlannerLimits::min_vel_x
double min_vel_x
goal_functions.h
base_local_planner::LocalPlannerLimits::acc_lim_y
double acc_lim_y
tf2::Stamped
costmap_2d::Costmap2D
base_local_planner::LocalPlannerLimits
line_iterator.h
base_local_planner::LocalPlannerLimits::min_vel_y
double min_vel_y
step
unsigned int step
base_local_planner::LocalPlannerLimits::max_vel_theta
double max_vel_theta
graceful_controller
class_list_macros.h
costmap_2d::Costmap2DROS::getRobotFootprint
const std::vector< geometry_msgs::Point > & getRobotFootprint() const noexcept
sign
double sign(double x)
PLUGINLIB_EXPORT_CLASS
#define PLUGINLIB_EXPORT_CLASS(class_type, base_class_type)
base_local_planner::LocalPlannerLimits::max_vel_x
double max_vel_x
base_local_planner::LocalPlannerLimits::theta_stopped_vel
double theta_stopped_vel
ROS_DEBUG
#define ROS_DEBUG(...)
ROS_DEBUG_NAMED
#define ROS_DEBUG_NAMED(name,...)
graceful_controller::computeDistanceAlongPath
void computeDistanceAlongPath(const std::vector< geometry_msgs::PoseStamped > &poses, std::vector< double > &distances)
Compute distance of poses along a path. Assumes poses are in robot-centric frame.
Definition: graceful_controller_ros.cpp:834
orientation_tools.hpp
base_local_planner::LocalPlannerLimits::max_vel_trans
double max_vel_trans
graceful_controller::sign
double sign(double x)
Definition: graceful_controller_ros.cpp:89
base_local_planner::LineIterator::isValid
bool isValid() const
ROS_WARN
#define ROS_WARN(...)
costmap_2d::LETHAL_OBSTACLE
static const unsigned char LETHAL_OBSTACLE
tf2_ros::Buffer
ros::NodeHandle::subscribe
Subscriber subscribe(const std::string &topic, uint32_t queue_size, const boost::function< void(C)> &callback, const VoidConstPtr &tracked_object=VoidConstPtr(), const TransportHints &transport_hints=TransportHints())
graceful_controller::addOrientations
std::vector< geometry_msgs::PoseStamped > addOrientations(const std::vector< geometry_msgs::PoseStamped > &path)
Add orientation to each pose in a path.
Definition: orientation_tools.cpp:96
graceful_controller.hpp
ros::Time
tf2::doTransform
void doTransform(const T &data_in, T &data_out, const geometry_msgs::TransformStamped &transform)
footprint.h
ROS_ERROR
#define ROS_ERROR(...)
base_local_planner::LineIterator
base_local_planner::LocalPlannerLimits::trans_stopped_vel
double trans_stopped_vel
base_local_planner::LocalPlannerLimits::acc_lim_trans
double acc_lim_trans
tf2::toMsg
B toMsg(const A &a)
tf
base_local_planner::LocalPlannerLimits::min_vel_trans
double min_vel_trans
base_local_planner::LocalPlannerLimits::acc_lim_x
double acc_lim_x
costmap_2d::Costmap2DROS::getCostmap
Costmap2D * getCostmap() const
addPointMarker
void addPointMarker(double x, double y, bool colliding, visualization_msgs::MarkerArray *msg)
Add a visualization point to a marker.
Definition: visualization.cpp:38
tf2::TransformException
ROS_INFO
#define ROS_INFO(...)
costmap_2d::Costmap2D::getCost
unsigned char getCost(unsigned int mx, unsigned int my) const
base_local_planner::publishPlan
void publishPlan(const std::vector< geometry_msgs::PoseStamped > &path, const ros::Publisher &pub)
base_local_planner::LocalPlannerLimits::max_vel_y
double max_vel_y
ros::Duration
graceful_controller::GracefulControllerROS
Definition: graceful_controller_ros.hpp:98
base_local_planner::LocalPlannerLimits::yaw_goal_tolerance
double yaw_goal_tolerance
costmap_2d::Costmap2DROS
base_local_planner::LocalPlannerLimits::acc_lim_theta
double acc_lim_theta
nav_core::BaseLocalPlanner
ros::NodeHandle
angles.h


graceful_controller_ros
Author(s): Michael Ferguson
autogenerated on Wed Oct 23 2024 02:43:04