SMACC2
Loading...
Searching...
No Matches
backward_local_planner.cpp
Go to the documentation of this file.
1// Copyright 2025 Robosoft Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/*****************************************************************************************************************
16 *
17 * Authors: Pablo Inigo Blasco, Brett Aldrich
18 *
19 ******************************************************************************************************************/
20
21#include <angles/angles.h>
24
25#include <boost/intrusive_ptr.hpp>
26#include <chrono>
27#include <nav_2d_utils/tf_help.hpp>
28#include <pluginlib/class_list_macros.hpp>
29#include <visualization_msgs/msg/marker_array.hpp>
30
31// register this planner as a BaseLocalPlanner plugin
32PLUGINLIB_EXPORT_CLASS(
34
35using namespace std::literals::chrono_literals;
36
37namespace cl_nav2z
38{
39namespace backward_local_planner
40{
47
54
56{
57 RCLCPP_INFO_STREAM(nh_->get_logger(), "activating controller BackwardLocalPlanner");
59
60 goalMarkerPublisher_->on_activate();
61 planPub_->on_activate();
62 backwardsPlanPath_.clear();
63}
64
66{
67 this->clearMarkers();
68 RCLCPP_WARN_STREAM(nh_->get_logger(), "[BackwardLocalPlanner] deactivated");
69 planPub_->on_deactivate();
70 goalMarkerPublisher_->on_deactivate();
71}
72
74{
75 this->clearMarkers();
76 RCLCPP_WARN_STREAM(nh_->get_logger(), "[BackwardLocalPlanner] cleanup");
77 this->backwardsPlanPath_.clear();
79}
80
87template <typename T>
88void tryGetOrSet(rclcpp_lifecycle::LifecycleNode::SharedPtr & node, std::string param, T & value)
89{
90 if (!node->get_parameter(param, value))
91 {
92 node->set_parameter(rclcpp::Parameter(param, value));
93 }
94}
95
97 const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent, std::string name,
98 const std::shared_ptr<tf2_ros::Buffer> tf,
99 const std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros)
100{
101 this->costmapRos_ = costmap_ros;
102 this->nh_ = parent.lock();
103 this->name_ = name;
104 this->tf_ = tf;
105
106 k_rho_ = -1.0;
107 k_alpha_ = 0.5;
108 k_betta_ = -1.0; // set to zero means that orientation is not important
116 waitingTimeout_ = rclcpp::Duration(10s);
117
118 this->currentCarrotPoseIndex_ = 0;
119
121 nh_, name_ + ".pure_spinning_straight_line_mode", straightBackwardsAndPureSpinningMode_);
122
123 declareOrSet(nh_, name_ + ".k_rho", k_rho_);
124 declareOrSet(nh_, name_ + ".k_alpha", k_alpha_);
125 declareOrSet(nh_, name_ + ".k_betta", k_betta_);
126 declareOrSet(nh_, name_ + ".linear_mode_rho_error_threshold", linear_mode_rho_error_threshold_);
127
128 declareOrSet(nh_, name_ + ".carrot_distance", carrot_distance_);
129 declareOrSet(nh_, name_ + ".carrot_angular_distance", carrot_angular_distance_);
130 declareOrSet(nh_, name_ + ".enable_obstacle_checking", enable_obstacle_checking_);
131
132 declareOrSet(nh_, name_ + ".max_linear_x_speed", max_linear_x_speed_);
133 declareOrSet(nh_, name_ + ".max_angular_z_speed", max_angular_z_speed_);
134
135 // we have to do this, for example for the case we are refining the final orientation.
136 // check at some point if the carrot is reached in "goal linear distance", then we go into
137 // some automatic pure-spinning mode where we only update the orientation
138 // This means that if we reach the carrot with precision we go into pure spinning mode but we cannot
139 // leave that point (maybe this could be improved)
140
142 {
143 RCLCPP_WARN_STREAM(
144 nh_->get_logger(), "[BackwardLocalPlanner] carrot_angular_distance ("
146 << ") cannot be lower than yaw_goal_tolerance (" << yaw_goal_tolerance_
147 << ") setting carrot_angular_distance = " << yaw_goal_tolerance_);
149 }
150
152 {
153 RCLCPP_WARN_STREAM(
154 nh_->get_logger(), "[BackwardLocalPlanner] carrot_linear_distance ("
155 << carrot_distance_ << ") cannot be lower than xy_goal_tolerance_ ("
157 << ") setting carrot_angular_distance = " << xy_goal_tolerance_);
159 }
160
161 goalMarkerPublisher_ = nh_->create_publisher<visualization_msgs::msg::MarkerArray>(
162 "backward_local_planner/goal_marker", rclcpp::QoS(1));
163
164 planPub_ =
165 nh_->create_publisher<nav_msgs::msg::Path>("backward_local_planner/path", rclcpp::QoS(1));
166}
167
169{
170 RCLCPP_INFO_STREAM(nh_->get_logger(), "--- parameters ---");
171 tryGetOrSet(nh_, name_ + ".k_rho", k_rho_);
172 RCLCPP_INFO_STREAM(nh_->get_logger(), name_ + ".k_rho:" << k_rho_);
173 tryGetOrSet(nh_, name_ + ".k_alpha", k_alpha_);
174 RCLCPP_INFO_STREAM(nh_->get_logger(), name_ + ".k_alpha:" << k_alpha_);
175 tryGetOrSet(nh_, name_ + ".k_betta", k_betta_);
176 RCLCPP_INFO_STREAM(nh_->get_logger(), name_ + ".k_betta:" << k_betta_);
177
178 tryGetOrSet(nh_, name_ + ".enable_obstacle_checking", enable_obstacle_checking_);
179 RCLCPP_INFO_STREAM(
180 nh_->get_logger(), name_ + ".enable_obstacle_checking: " << enable_obstacle_checking_);
181
182 tryGetOrSet(nh_, name_ + ".carrot_distance", carrot_distance_);
183 RCLCPP_INFO_STREAM(nh_->get_logger(), name_ + ".carrot_distance:" << carrot_distance_);
184 tryGetOrSet(nh_, name_ + ".carrot_angular_distance", carrot_angular_distance_);
185 RCLCPP_INFO_STREAM(
186 nh_->get_logger(), name_ + ".carrot_angular_distance: " << carrot_angular_distance_);
187
189 nh_, name_ + ".pure_spinning_straight_line_mode", straightBackwardsAndPureSpinningMode_);
190 RCLCPP_INFO_STREAM(
191 nh_->get_logger(),
192 name_ + ".pure_spinning_straight_line_mode: " << straightBackwardsAndPureSpinningMode_);
193
194 tryGetOrSet(nh_, name_ + ".linear_mode_rho_error_threshold", linear_mode_rho_error_threshold_);
195 RCLCPP_INFO_STREAM(
196 nh_->get_logger(),
197 name_ + ".linear_mode_rho_error_threshold: " << linear_mode_rho_error_threshold_);
198 tryGetOrSet(nh_, name_ + ".max_linear_x_speed", max_linear_x_speed_);
199 RCLCPP_INFO_STREAM(nh_->get_logger(), name_ + ".max_linear_x_speed: " << max_linear_x_speed_);
200 tryGetOrSet(nh_, name_ + ".max_angular_z_speed", max_angular_z_speed_);
201 RCLCPP_INFO_STREAM(nh_->get_logger(), name_ + ".max_angular_z_speed: " << max_angular_z_speed_);
202
204 {
205 RCLCPP_WARN_STREAM(
206 nh_->get_logger(), "[BackwardLocalPlanner] carrot_angular_distance ("
208 << ") cannot be lower than yaw_goal_tolerance (" << yaw_goal_tolerance_
209 << ") setting carrot_angular_distance = " << yaw_goal_tolerance_);
211 nh_->set_parameter(
212 rclcpp::Parameter(name_ + ".carrot_angular_distance", carrot_angular_distance_));
213 }
214 RCLCPP_INFO_STREAM(
215 nh_->get_logger(), name_ + ".carrot_angular_distance: " << carrot_angular_distance_);
216
218 {
219 RCLCPP_WARN_STREAM(
220 nh_->get_logger(), "[BackwardLocalPlanner] carrot_linear_distance ("
221 << carrot_distance_ << ") cannot be lower than xy_goal_tolerance_ ("
223 << ") setting carrot_angular_distance = " << xy_goal_tolerance_);
225 nh_->set_parameter(rclcpp::Parameter(name_ + ".carrot_distance", carrot_distance_));
226 }
227 RCLCPP_INFO_STREAM(nh_->get_logger(), name_ + ".carrot_distance:" << carrot_distance_);
228 RCLCPP_INFO_STREAM(nh_->get_logger(), "--- end params ---");
229}
230
232 const double & /*speed_limit*/, const bool & /*percentage*/)
233{
234 RCLCPP_WARN_STREAM(
235 nh_->get_logger(),
236 "BackwardLocalPlanner::setSpeedLimit invoked. Ignored, functionality not "
237 "implemented.");
238}
245 const geometry_msgs::msg::PoseStamped & tfpose, double & dist, double & angular_error)
246{
247 double angle = tf2::getYaw(tfpose.pose.orientation);
248 auto & carrot_pose = backwardsPlanPath_[currentCarrotPoseIndex_];
249 const geometry_msgs::msg::Point & carrot_point = carrot_pose.pose.position;
250
251 tf2::Quaternion carrot_orientation;
252 tf2::convert(carrot_pose.pose.orientation, carrot_orientation);
253 geometry_msgs::msg::Pose currentPoseDebugMsg = tfpose.pose;
254
255 // take error from the current position to the path point
256 double dx = carrot_point.x - tfpose.pose.position.x;
257 double dy = carrot_point.y - tfpose.pose.position.y;
258
259 dist = sqrt(dx * dx + dy * dy);
260
261 double pangle = tf2::getYaw(carrot_orientation);
262 angular_error = fabs(angles::shortest_angular_distance(pangle, angle));
263
264 RCLCPP_INFO_STREAM(
265 nh_->get_logger(), "[BackwardLocalPlanner] Compute carrot errors from current pose. (linear "
266 << dist << ")(angular " << angular_error << ")" << std::endl
267 << "Current carrot pose: " << std::endl
268 << carrot_pose << std::endl
269 << "Current actual pose:" << std::endl
270 << currentPoseDebugMsg);
271}
272
278bool BackwardLocalPlanner::updateCarrotGoal(const geometry_msgs::msg::PoseStamped & tfpose)
279{
280 RCLCPP_INFO_STREAM(nh_->get_logger(), "[BackwardsLocalPlanner] --- Carrot update ---");
281 double disterr = 0, angleerr = 0;
282 // iterate the point from the current position and backward until reaching a new goal point in the path
283 // this algorithm among other advantages has that skip the looping with an eager global planner
284 // that recalls the same plan (the already performed part of the plan in the current pose is skipped)
285 while (currentCarrotPoseIndex_ < (long)backwardsPlanPath_.size() - 1)
286 {
288
289 RCLCPP_INFO_STREAM(
290 nh_->get_logger(), "[BackwardsLocalPlanner] update carrot goal: Current index: "
291 << currentCarrotPoseIndex_ << "/" << backwardsPlanPath_.size());
292 RCLCPP_INFO(
293 nh_->get_logger(),
294 "[BackwardsLocalPlanner] update carrot goal: linear error %lf, angular error: %lf", disterr,
295 angleerr);
296
297 // target pose found, goal carrot tries to escape!
298 if (disterr < carrot_distance_ && angleerr < carrot_angular_distance_)
299 {
302 RCLCPP_INFO_STREAM(
303 nh_->get_logger(), "[BackwardsLocalPlanner] move carrot fw "
304 << currentCarrotPoseIndex_ << "/" << backwardsPlanPath_.size());
305 }
306 else
307 {
308 // carrot already escaped
309 break;
310 }
311 }
312 // RCLCPP_INFO(nh_->get_logger(),"[BackwardsLocalPlanner] computing angular error");
313 if (
314 currentCarrotPoseIndex_ >= (long)backwardsPlanPath_.size() - 1 && backwardsPlanPath_.size() > 0)
315 {
317 // reupdated errors
319 }
320
321 RCLCPP_INFO(
322 nh_->get_logger(), "[BackwardsLocalPlanner] Current index carrot goal: %d",
324 RCLCPP_INFO(
325 nh_->get_logger(),
326 "[BackwardsLocalPlanner] Update carrot goal: linear error %lf (xytol: %lf), angular error: "
327 "%lf",
328 disterr, xy_goal_tolerance_, angleerr);
329
330 bool carrotInGoalLinearRange = disterr < xy_goal_tolerance_;
331 RCLCPP_INFO(
332 nh_->get_logger(), "[BackwardsLocalPlanner] carrot in goal radius: %d",
333 carrotInGoalLinearRange);
334
335 RCLCPP_INFO(nh_->get_logger(), "[BackwardsLocalPlanner] ---End carrot update---");
336
337 return carrotInGoalLinearRange;
338}
339
341{
342 // this function should be called always the carrot is updated
343 divergenceDetectionLastCarrotLinearDistance_ = std::numeric_limits<double>::max();
344 return true;
345}
346
347bool BackwardLocalPlanner::divergenceDetectionUpdate(const geometry_msgs::msg::PoseStamped & tfpose)
348{
349 double disterr = 0, angleerr = 0;
351
352 RCLCPP_INFO_STREAM(
353 nh_->get_logger(), "[BackwardLocalPlanner] Divergence check. carrot goal distance. was: "
355 << ", now it is: " << disterr);
357 {
358 // candidate of divergence, we do not throw the divergence alarm yet
359 // but we neither update the distance since it is worse than the one
360 // we had previously with the same carrot.
361 const double MARGIN_FACTOR = 1.2;
362 if (disterr > MARGIN_FACTOR * divergenceDetectionLastCarrotLinearDistance_)
363 {
364 RCLCPP_ERROR_STREAM(
365 nh_->get_logger(),
366 "[BackwardLocalPlanner] Divergence detected. The same carrot goal distance was previously: "
367 << divergenceDetectionLastCarrotLinearDistance_ << "but now it is: " << disterr);
368 return true;
369 }
370 else
371 {
372 // divergence candidate
373 return false;
374 }
375 }
376 else
377 {
378 // update:
380 return false;
381 }
382}
383
385 const geometry_msgs::msg::PoseStamped & tfpose)
386{
387 // this function is specially useful when we want to reach the goal with a lot
388 // of precision. We may pass the goal and then the controller enters in some
389 // unstable state. With this, we are able to detect when stop moving.
390
391 // only apply if the carrot is in goal position and also if we are not in a pure spinning behavior v!=0
392
393 auto & carrot_pose = backwardsPlanPath_[currentCarrotPoseIndex_];
394 const geometry_msgs::msg::Point & carrot_point = carrot_pose.pose.position;
395 double yaw = tf2::getYaw(carrot_pose.pose.orientation);
396
397 // direction vector
398 double vx = cos(yaw);
399 double vy = sin(yaw);
400
401 // line implicit equation
402 // ax + by + c = 0
403 double c = -vx * carrot_point.x - vy * carrot_point.y;
404 const double C_OFFSET_METERS = 0.05; // 5 cm
405 double check = vx * tfpose.pose.position.x + vy * tfpose.pose.position.y + c + C_OFFSET_METERS;
406
407 RCLCPP_INFO_STREAM(
408 nh_->get_logger(),
409 "[BackwardLocalPlanner] half plane constraint:" << vx << "*" << carrot_point.x << " + " << vy
410 << "*" << carrot_point.y << " + " << c);
411 RCLCPP_INFO_STREAM(
412 nh_->get_logger(), "[BackwardLocalPlanner] constraint evaluation: "
413 << vx << "*" << tfpose.pose.position.x << " + " << vy << "*"
414 << tfpose.pose.position.y << " + " << c << " = " << check);
415
416 return check < 0;
417}
418
420 const geometry_msgs::msg::PoseStamped & tfpose,
421 const geometry_msgs::msg::Twist & /*currentTwist*/, double angle_error, bool & linearGoalReached,
422 nav2_core::GoalChecker * /*goal_checker*/)
423{
424 auto & finalgoal = backwardsPlanPath_.back();
425 double gdx = finalgoal.pose.position.x - tfpose.pose.position.x;
426 double gdy = finalgoal.pose.position.y - tfpose.pose.position.y;
427 double goaldist = sqrt(gdx * gdx + gdy * gdy);
428
429 auto abs_angle_error = fabs(angle_error);
430 RCLCPP_INFO_STREAM(
431 nh_->get_logger(), "[BackwardLocalPlanner] goal check. linear dist: "
432 << goaldist << "(" << this->xy_goal_tolerance_ << ")" << ", angular dist: "
433 << abs_angle_error << "(" << this->yaw_goal_tolerance_ << ")");
434
435 linearGoalReached = goaldist < this->xy_goal_tolerance_;
436
437 return linearGoalReached && abs_angle_error < this->yaw_goal_tolerance_;
438}
439
446 const geometry_msgs::msg::PoseStamped & /*tfpose*/, double & vetta, double & gamma,
447 double alpha_error, double betta_error, double rho_error)
448{
449 if (rho_error > linear_mode_rho_error_threshold_) // works in straight motion mode
450 {
451 vetta = k_rho_ * rho_error;
452 gamma = k_alpha_ * alpha_error;
453 }
454 else if (fabs(betta_error) >= this->yaw_goal_tolerance_) // works in pure spinning mode
455 {
456 vetta = 0; // disable linear
457 gamma = k_betta_ * betta_error;
458 }
459}
460
466geometry_msgs::msg::TwistStamped BackwardLocalPlanner::computeVelocityCommands(
467 const geometry_msgs::msg::PoseStamped & pose, const geometry_msgs::msg::Twist & velocity,
468 nav2_core::GoalChecker * goal_checker)
469{
470 RCLCPP_INFO(
471 nh_->get_logger(),
472 "[BackwardLocalPlanner] ------------------- LOCAL PLANNER LOOP -----------------");
473 this->updateParameters();
474
475 // consistency check
476 if (this->backwardsPlanPath_.size() > 0)
477 {
478 RCLCPP_INFO_STREAM(
479 nh_->get_logger(), "[BackwardLocalPlanner] Current pose frame id: "
480 << backwardsPlanPath_.front().header.frame_id
481 << ", path pose frame id: " << pose.header.frame_id);
482
483 if (backwardsPlanPath_.front().header.frame_id != pose.header.frame_id)
484 {
485 RCLCPP_ERROR_STREAM(nh_->get_logger(), "[BackwardLocalPlanner] Inconsistent frames");
486 }
487 }
488
489 // xy_goal_tolerance and yaw_goal_tolerance are just used for logging proposes and clamping the carrot
490 // goal distance (parameter safety)
491 if (xy_goal_tolerance_ == -1 || yaw_goal_tolerance_ == -1)
492 {
493 geometry_msgs::msg::Pose posetol;
494 geometry_msgs::msg::Twist twistol;
495 if (goal_checker->getTolerances(posetol, twistol))
496 {
497 xy_goal_tolerance_ = posetol.position.x;
498 yaw_goal_tolerance_ = tf2::getYaw(posetol.orientation);
499
500 RCLCPP_INFO_STREAM(
501 nh_->get_logger(), "[BackwardLocalPlanner] xy_goal_tolerance_: "
503 << ", yaw_goal_tolerance_: " << yaw_goal_tolerance_);
504 }
505 else
506 {
507 RCLCPP_INFO_STREAM(
508 nh_->get_logger(), "[BackwardLocalPlanner] could not get tolerances from goal checker");
509 }
510 }
511
512 RCLCPP_INFO(
513 nh_->get_logger(),
514 "[BackwardLocalPlanner] ------------------- LOCAL PLANNER LOOP -----------------");
515
516 geometry_msgs::msg::TwistStamped cmd_vel;
517 RCLCPP_INFO(nh_->get_logger(), "[BackwardLocalPlanner] LOCAL PLANNER LOOP");
518 geometry_msgs::msg::PoseStamped paux;
519 geometry_msgs::msg::PoseStamped tfpose;
520
521 if (!costmapRos_->getRobotPose(tfpose))
522 {
523 RCLCPP_ERROR(
524 nh_->get_logger(),
525 "[BackwardLocalPlanner] missing robot pose, canceling compute Velocity Command");
526 } // it is not working in the pure spinning reel example, maybe the hyperplane check is enough
527 bool divergenceDetected = false;
528
529 bool emergency_stop = false;
530 if (divergenceDetected)
531 {
532 RCLCPP_ERROR(
533 nh_->get_logger(), "[BackwardLocalPlanner] Divergence detected. Sending emergency stop.");
534 emergency_stop = true;
535 }
536
537 bool carrotInLinearGoalRange = updateCarrotGoal(tfpose);
538 RCLCPP_INFO_STREAM(nh_->get_logger(), "[BackwardLocalPlanner] carrot goal created");
539
540 if (emergency_stop)
541 {
542 cmd_vel.twist.linear.x = 0;
543 cmd_vel.twist.angular.z = 0;
544 RCLCPP_INFO_STREAM(
545 nh_->get_logger(), "[BackwardLocalPlanner] emergency stop, exit compute commands");
546 return cmd_vel;
547 }
548
549 // ------ Evaluate the current context ----
550 double rho_error, betta_error, alpha_error;
551
552 // getting carrot goal information
553 tf2::Quaternion q;
554 tf2::convert(tfpose.pose.orientation, q);
555
556 RCLCPP_INFO_STREAM(
557 nh_->get_logger(), "[BackwardLocalPlanner] carrot goal: " << currentCarrotPoseIndex_ << "/"
558 << backwardsPlanPath_.size());
559 const geometry_msgs::msg::PoseStamped & carrotgoalpose =
561 RCLCPP_INFO_STREAM(
562 nh_->get_logger(), "[BackwardLocalPlanner] carrot goal pose current index: "
563 << currentCarrotPoseIndex_ << "/" << backwardsPlanPath_.size() << ": "
564 << carrotgoalpose);
565 const geometry_msgs::msg::Point & carrotGoalPosition = carrotgoalpose.pose.position;
566
567 tf2::Quaternion goalQ;
568 tf2::fromMsg(carrotgoalpose.pose.orientation, goalQ);
569 RCLCPP_INFO_STREAM(nh_->get_logger(), "[BackwardLocalPlanner] -- Control Policy --");
570 // goal orientation (global frame)
571 double betta = tf2::getYaw(goalQ);
572 RCLCPP_INFO_STREAM(nh_->get_logger(), "[BackwardLocalPlanner] goal orientation: " << betta);
573 betta = betta + betta_offset_;
574
575 double dx = carrotGoalPosition.x - tfpose.pose.position.x;
576 double dy = carrotGoalPosition.y - tfpose.pose.position.y;
577
578 // distance error to the targetpoint
579 rho_error = sqrt(dx * dx + dy * dy);
580
581 // heading to goal angle
582 double theta = tf2::getYaw(q);
583 double alpha = atan2(dy, dx);
584 alpha = alpha + alpha_offset_;
585
586 alpha_error = angles::shortest_angular_distance(alpha, theta);
587 betta_error = angles::shortest_angular_distance(betta, theta);
588 //------------- END CONTEXT EVAL ----------
589
590 bool linearGoalReached;
591 bool currentPoseInGoal =
592 checkCurrentPoseInGoalRange(tfpose, velocity, betta_error, linearGoalReached, goal_checker);
593
594 // Make sure the robot is very close to the goal and it is really in the the last goal point.
595 bool carrotInFinalGoalIndex = currentCarrotPoseIndex_ == (int)backwardsPlanPath_.size() - 1;
596
597 // checking if we are really in the end goal pose
598 if (currentPoseInGoal && carrotInFinalGoalIndex)
599 {
600 goalReached_ = true;
601 RCLCPP_INFO_STREAM(
602 nh_->get_logger(),
603 "[BackwardLocalPlanner] GOAL REACHED. Send stop command and skipping trajectory collision: "
604 << cmd_vel.twist);
605 cmd_vel.twist.linear.x = 0;
606 cmd_vel.twist.angular.z = 0;
607 return cmd_vel;
608 }
609 else if (
610 carrotInLinearGoalRange &&
611 linearGoalReached) // checking if we are in the end goal point but with incorrect
612 // orientation
613 {
614 // this means that we are not in the final angular distance, and we may even not be in the last carrot index
615 // (several intermediate angular poses until the last goal pose)
617 }
618
619 // --------------------
620 double vetta, gamma;
622 {
623 // decorated control rule for this mode
625 tfpose, vetta, gamma, alpha_error, betta_error, rho_error);
626 }
627 else // default free navigation backward motion mode
628 {
629 // regular control rule
630 vetta = k_rho_ * rho_error;
631 gamma = k_alpha_ * alpha_error + k_betta_ * betta_error;
632
633 // Even if we are in free navigation, we can enter in the pure spinning state.
634 // then, the linear motion is deactivated.
636 {
637 RCLCPP_INFO(
638 nh_->get_logger(),
639 "[BackwardLocalPlanner] we entered in a pure spinning state even in not pure-spining "
640 "configuration, "
641 "carrotDistanceGoalReached: %d",
642 carrotInLinearGoalRange);
643 gamma = k_betta_ * betta_error;
644 vetta = 0;
645 }
646
647 // classical control to reach a goal backwards
648 }
649
650 // Apply command and Clamp to limits
651 cmd_vel.twist.linear.x = vetta;
652 cmd_vel.twist.angular.z = gamma;
653
654 if (cmd_vel.twist.linear.x > max_linear_x_speed_)
655 {
656 cmd_vel.twist.linear.x = max_linear_x_speed_;
657 }
658 else if (cmd_vel.twist.linear.x < -max_linear_x_speed_)
659 {
660 cmd_vel.twist.linear.x = -max_linear_x_speed_;
661 }
662
663 if (cmd_vel.twist.angular.z > max_angular_z_speed_)
664 {
665 cmd_vel.twist.angular.z = max_angular_z_speed_;
666 }
667 else if (cmd_vel.twist.angular.z < -max_angular_z_speed_)
668 {
669 cmd_vel.twist.angular.z = -max_angular_z_speed_;
670 }
671
672 publishGoalMarker(carrotGoalPosition.x, carrotGoalPosition.y, betta);
673
674 RCLCPP_INFO_STREAM(
675 nh_->get_logger(), "[BackwardLocalPlanner] local planner,"
676 << std::endl
677 << " current pose in goal: " << currentPoseInGoal << std::endl
678 << " carrot in final goal index: " << carrotInFinalGoalIndex << std::endl
679 << " carrot in linear goal range: " << carrotInLinearGoalRange << std::endl
680 << " straightAnPureSpiningMode: " << straightBackwardsAndPureSpinningMode_
681 << std::endl
682 << " inGoalPureSpinningState: " << inGoalPureSpinningState_ << std::endl
683 << " theta: " << theta << std::endl
684 << " betta: " << theta << std::endl
685 << " err_x: " << dx << std::endl
686 << " err_y:" << dy << std::endl
687 << " rho_error:" << rho_error << std::endl
688 << " alpha_error:" << alpha_error << std::endl
689 << " betta_error:" << betta_error << std::endl
690 << " vetta:" << vetta << std::endl
691 << " gamma:" << gamma << std::endl
692 << " cmd_vel.lin.x:" << cmd_vel.twist.linear.x << std::endl
693 << " cmd_vel.ang.z:" << cmd_vel.twist.angular.z);
694
696 {
697 bool carrotHalfPlaneConstraintFailure = checkCarrotHalfPlainConstraint(tfpose);
698
699 if (carrotHalfPlaneConstraintFailure)
700 {
701 RCLCPP_ERROR(
702 nh_->get_logger(),
703 "[BackwardLocalPlanner] CarrotHalfPlaneConstraintFailure detected. Sending "
704 "emergency stop and success to the planner.");
705 cmd_vel.twist.linear.x = 0;
706 }
707 }
708
709 // ---------------------- TRAJECTORY PREDICTION AND COLLISION AVOIDANCE ---------------------
710 geometry_msgs::msg::PoseStamped global_pose;
711 costmapRos_->getRobotPose(global_pose);
712
713 auto * costmap2d = costmapRos_->getCostmap();
714 auto yaw = tf2::getYaw(global_pose.pose.orientation);
715
716 auto & pos = global_pose.pose.position;
717
718 Eigen::Vector3f currentpose(pos.x, pos.y, yaw);
719 Eigen::Vector3f currentvel(
720 cmd_vel.twist.linear.x, cmd_vel.twist.linear.y, cmd_vel.twist.angular.z);
721 std::vector<Eigen::Vector3f> trajectory;
722 this->generateTrajectory(
723 currentpose, currentvel, 0.8 /*meters*/, M_PI / 8 /*rads*/, 3.0 /*seconds*/, 0.05 /*seconds*/,
724 trajectory);
725
726 // check plan rejection
727 bool acceptedLocalTrajectoryFreeOfObstacles = true;
728
729 unsigned int mx, my;
730
732 {
733 if (backwardsPlanPath_.size() > 0)
734 {
735 auto & finalgoalpose = backwardsPlanPath_.back();
736
737 int i = 0;
738 // RCLCPP_INFO_STREAM(nh_->get_logger(), "lplanner goal: " << finalgoalpose.pose.position);
739 geometry_msgs::msg::Twist mockzerospeed;
740
741 for (auto & p : trajectory)
742 {
743 float dx = p[0] - finalgoalpose.pose.position.x;
744 float dy = p[1] - finalgoalpose.pose.position.y;
745
746 float dst = sqrt(dx * dx + dy * dy);
747 if (dst < xy_goal_tolerance_)
748 {
749 RCLCPP_INFO(
750 nh_->get_logger(),
751 "[BackwardLocalPlanner] trajectory simulation for collision checking: goal "
752 "reached with no collision");
753 break;
754 }
755
756 costmap2d->worldToMap(p[0], p[1], mx, my);
757
758 if (costmap2d->getCost(mx, my) >= nav2_costmap_2d::INSCRIBED_INFLATED_OBSTACLE)
759 {
760 acceptedLocalTrajectoryFreeOfObstacles = false;
761 RCLCPP_WARN_STREAM(
762 nh_->get_logger(),
763 "[BackwardLocalPlanner] ABORTED LOCAL PLAN BECAUSE OBSTACLE DETEDTED at point "
764 << i << "/" << trajectory.size() << std::endl
765 << p[0] << ", " << p[1]);
766 break;
767 }
768 i++;
769 }
770 }
771 else
772 {
773 RCLCPP_WARN(
774 nh_->get_logger(), "[BackwardLocalPlanner] Abort local - Backwards global plan size: %ld",
775 backwardsPlanPath_.size());
776 cmd_vel.twist.angular.z = 0;
777 cmd_vel.twist.linear.x = 0;
778 }
779 }
780
781 if (acceptedLocalTrajectoryFreeOfObstacles)
782 {
783 waiting_ = false;
784 RCLCPP_INFO(
785 nh_->get_logger(),
786 "[BackwardLocalPlanner] accepted local trajectory free of obstacle. Local planner "
787 "continues.");
788 return cmd_vel;
789 }
790 else // that is not appceted because existence of obstacles
791 {
792 // emergency stop for collision: waiting a while before sending error
793 cmd_vel.twist.linear.x = 0;
794 cmd_vel.twist.angular.z = 0;
795
796 if (waiting_ == false)
797 {
798 waiting_ = true;
799 waitingStamp_ = nh_->now();
800 RCLCPP_WARN(
801 nh_->get_logger(), "[BackwardLocalPlanner][Not accepted local plan] starting countdown");
802 }
803 else
804 {
805 auto waitingduration = nh_->now() - waitingStamp_;
806
807 if (waitingduration > this->waitingTimeout_)
808 {
809 RCLCPP_WARN(
810 nh_->get_logger(), "[BackwardLocalPlanner][Abort local] timeout! duration %lf/%f",
811 waitingduration.seconds(), waitingTimeout_.seconds());
812 cmd_vel.twist.linear.x = 0;
813 cmd_vel.twist.angular.z = 0;
814 return cmd_vel;
815 }
816 }
817
818 return cmd_vel;
819 }
820}
821
828{
829 RCLCPP_INFO(nh_->get_logger(), "[BackwardLocalPlanner] isGoalReached call");
830 return goalReached_;
831}
832
833bool BackwardLocalPlanner::findInitialCarrotGoal(geometry_msgs::msg::PoseStamped & tfpose)
834{
835 double lineardisterr, angleerr;
836 bool inCarrotRange = false;
837
838 // initial state check
839 computeCurrentEuclideanAndAngularErrorsToCarrotGoal(tfpose, lineardisterr, angleerr);
840
841 // lets set the carrot-goal in the correct place with this loop
842 while (currentCarrotPoseIndex_ < (int)backwardsPlanPath_.size() && !inCarrotRange)
843 {
844 computeCurrentEuclideanAndAngularErrorsToCarrotGoal(tfpose, lineardisterr, angleerr);
845
846 RCLCPP_INFO(
847 nh_->get_logger(),
848 "[BackwardLocalPlanner] Finding initial carrot goal i=%d - error to carrot, linear = %lf "
849 "(%lf), "
850 "angular : %lf (%lf)",
852
853 // current path point is inside the carrot distance range, goal carrot tries to escape!
854 if (lineardisterr < carrot_distance_ && angleerr < carrot_angular_distance_)
855 {
856 RCLCPP_INFO(
857 nh_->get_logger(),
858 "[BackwardLocalPlanner] Finding initial carrot goal i=%d - in carrot Range",
860 inCarrotRange = true;
861 // we are inside the goal range
862 }
863 else if (
864 inCarrotRange && (lineardisterr > carrot_distance_ || angleerr > carrot_angular_distance_))
865 {
866 // we were inside the carrot range but not anymore, now we are just leaving. we want to continue forward
867 // (currentCarrotPoseIndex_++) unless we go out of the carrot range
868
869 // but we rollback last index increment (to go back inside the carrot goal scope) and start motion with that
870 // carrot goal we found
872 break;
873 }
874 else
875 {
876 RCLCPP_INFO(
877 nh_->get_logger(),
878 "[BackwardLocalPlanner] Finding initial carrot goal i=%d - carrot out of range, searching "
879 "coincidence...",
881 }
882
884 RCLCPP_INFO_STREAM(
885 nh_->get_logger(), "[BackwardLocalPlanner] setPlan: fw" << currentCarrotPoseIndex_);
886 }
887
888 RCLCPP_INFO_STREAM(
889 nh_->get_logger(), "[BackwardLocalPlanner] setPlan: (found first carrot:"
890 << inCarrotRange << ") initial carrot point index: "
891 << currentCarrotPoseIndex_ << "/" << backwardsPlanPath_.size());
892
893 return inCarrotRange;
894}
895
897{
898 // this algorithm is really important to have a precise carrot (linear or angular)
899 // and not being considered as a divergence from the path
900
901 RCLCPP_INFO(nh_->get_logger(), "[BackwardLocalPlanner] resample precise");
902 if (backwardsPlanPath_.size() <= 1)
903 {
904 RCLCPP_INFO_STREAM(
905 nh_->get_logger(),
906 "[BackwardLocalPlanner] resample precise skipping, size: " << backwardsPlanPath_.size());
907 return false;
908 }
909
910 int counter = 0;
911 double maxallowedAngularError = 0.45 * this->carrot_angular_distance_; // nyquist
912 double maxallowedLinearError = 0.45 * this->carrot_distance_; // nyquist
913
914 for (int i = 0; i < (int)backwardsPlanPath_.size() - 1; i++)
915 {
916 RCLCPP_INFO_STREAM(nh_->get_logger(), "[BackwardLocalPlanner] resample precise, check: " << i);
917 auto & currpose = backwardsPlanPath_[i];
918 auto & nextpose = backwardsPlanPath_[i + 1];
919
920 tf2::Quaternion qCurrent, qNext;
921 tf2::convert(currpose.pose.orientation, qCurrent);
922 tf2::convert(nextpose.pose.orientation, qNext);
923
924 double dx = nextpose.pose.position.x - currpose.pose.position.x;
925 double dy = nextpose.pose.position.y - currpose.pose.position.y;
926 double dist = sqrt(dx * dx + dy * dy);
927
928 bool resample = false;
929 if (dist > maxallowedLinearError)
930 {
931 RCLCPP_INFO_STREAM(
932 nh_->get_logger(), "[BackwardLocalPlanner] resampling point, linear distance:"
933 << dist << "(" << maxallowedLinearError << ")" << i);
934 resample = true;
935 }
936 else
937 {
938 double currentAngle = tf2::getYaw(qCurrent);
939 double nextAngle = tf2::getYaw(qNext);
940
941 double angularError = fabs(angles::shortest_angular_distance(currentAngle, nextAngle));
942 if (angularError > maxallowedAngularError)
943 {
944 resample = true;
945 RCLCPP_INFO_STREAM(
946 nh_->get_logger(), "[BackwardLocalPlanner] resampling point, angular distance:"
947 << angularError << "(" << maxallowedAngularError << ")" << i);
948 }
949 }
950
951 if (resample)
952 {
953 geometry_msgs::msg::PoseStamped pintermediate;
954 auto duration = rclcpp::Time(nextpose.header.stamp) - rclcpp::Time(currpose.header.stamp);
955
956 pintermediate.header.frame_id = currpose.header.frame_id;
957 pintermediate.header.stamp = rclcpp::Time(currpose.header.stamp) + duration * 0.5;
958
959 pintermediate.pose.position.x = 0.5 * (currpose.pose.position.x + nextpose.pose.position.x);
960 pintermediate.pose.position.y = 0.5 * (currpose.pose.position.y + nextpose.pose.position.y);
961 pintermediate.pose.position.z = 0.5 * (currpose.pose.position.z + nextpose.pose.position.z);
962 tf2::Quaternion intermediateQuat = tf2::slerp(qCurrent, qNext, 0.5);
963 pintermediate.pose.orientation = tf2::toMsg(intermediateQuat);
964
965 this->backwardsPlanPath_.insert(this->backwardsPlanPath_.begin() + i + 1, pintermediate);
966
967 // retry this point
968 i--;
969 counter++;
970 }
971 }
972
973 RCLCPP_INFO_STREAM(
974 nh_->get_logger(), "[BackwardLocalPlanner] End resampling. resampled:" << counter
975 << " new inserted poses "
976 "during precise "
977 "resmapling.");
978 return true;
979}
980
986void BackwardLocalPlanner::setPlan(const nav_msgs::msg::Path & path)
987{
988 RCLCPP_INFO_STREAM(
989 nh_->get_logger(),
990 "[BackwardLocalPlanner] setPlan: new global plan received ( " << path.poses.size() << ")");
991
992 //------------- TRANSFORM TO LOCAL FRAME PATH ---------------------------
993 nav_msgs::msg::Path transformedPlan;
994 rclcpp::Duration ttol = rclcpp::Duration::from_seconds(transform_tolerance_);
995 // transform global plan to the navigation reference frame
996 for (auto & p : path.poses)
997 {
998 geometry_msgs::msg::PoseStamped transformedPose;
999 nav_2d_utils::transformPose(tf_, costmapRos_->getGlobalFrameID(), p, transformedPose, ttol);
1000 transformedPose.header.frame_id = costmapRos_->getGlobalFrameID();
1001 transformedPlan.poses.push_back(transformedPose);
1002 }
1003
1004 backwardsPlanPath_ = transformedPlan.poses;
1005
1006 // --------- resampling path feature -----------
1007 geometry_msgs::msg::PoseStamped tfpose;
1008 if (!costmapRos_->getRobotPose(tfpose))
1009 {
1010 RCLCPP_ERROR(nh_->get_logger(), "Failure getting pose from Backward local planner");
1011 return;
1012 }
1013
1014 geometry_msgs::msg::PoseStamped posestamped = tfpose;
1015 backwardsPlanPath_.insert(backwardsPlanPath_.begin(), posestamped);
1016 this->resamplePrecisePlan();
1017
1018 nav_msgs::msg::Path planMsg;
1019 planMsg.poses = backwardsPlanPath_;
1020 planMsg.header.frame_id = costmapRos_->getGlobalFrameID();
1021 planMsg.header.stamp = nh_->now();
1022 planPub_->publish(planMsg);
1023
1024 // ------ reset controller state ----------------------
1025 goalReached_ = false;
1029
1030 if (path.poses.size() == 0)
1031 {
1032 RCLCPP_INFO_STREAM(nh_->get_logger(), "[BackwardLocalPlanner] received plan without any pose");
1033 return;
1034 }
1035
1036 // -------- initialize carrot ----------------
1037 bool foundInitialCarrotGoal = this->findInitialCarrotGoal(tfpose);
1038 if (!foundInitialCarrotGoal)
1039 {
1040 RCLCPP_ERROR(
1041 nh_->get_logger(),
1042 "[BackwardLocalPlanner] new plan rejected. The initial point in the global path is "
1043 "too much far away from the current state (according to carrot_distance "
1044 "parameter)");
1045 // return false; // in this case, the new plan broke the current execution
1046 return;
1047 }
1048 else
1049 {
1050 this->divergenceDetectionUpdate(tfpose);
1051 return;
1052 }
1053}
1054
1056 const Eigen::Vector3f & pos, const Eigen::Vector3f & vel, float maxdist, float maxanglediff,
1057 float maxtime, float dt, std::vector<Eigen::Vector3f> & outtraj)
1058{
1059 // simulate the trajectory and check for collisions, updating costs along the way
1060 bool end = false;
1061 float time = 0;
1062 Eigen::Vector3f currentpos = pos;
1063 int i = 0;
1064 while (!end)
1065 {
1066 auto loop_vel = vel;
1067 // update the position of the robot using the velocities passed in
1068 auto newpos = computeNewPositions(currentpos, loop_vel, dt);
1069
1070 auto dx = newpos[0] - currentpos[0];
1071 auto dy = newpos[1] - currentpos[1];
1072 float dist, angledist;
1073
1074 // RCLCPP_INFO(nh_->get_logger(), "traj point %d", i);
1075 dist = sqrt(dx * dx + dy * dy);
1076 if (dist > maxdist)
1077 {
1078 end = true;
1079 // RCLCPP_INFO(nh_->get_logger(), "dist break: %f", dist);
1080 }
1081 else
1082 {
1083 // ouble from, double to
1084 angledist = angles::shortest_angular_distance(currentpos[2], newpos[2]);
1085 if (angledist > maxanglediff)
1086 {
1087 end = true;
1088 // RCLCPP_INFO(nh_->get_logger(), "angle dist break: %f", angledist);
1089 }
1090 else
1091 {
1092 outtraj.push_back(newpos);
1093
1094 time += dt;
1095 if (time > maxtime)
1096 {
1097 end = true;
1098 // RCLCPP_INFO(nh_->get_logger(), "time break: %f", time);
1099 }
1100
1101 // RCLCPP_INFO(nh_->get_logger(), "dist: %f, angledist: %f, time: %f", dist, angledist, time);
1102 }
1103 }
1104
1105 currentpos = newpos;
1106 i++;
1107 } // end for simulation steps
1108}
1109
1111 const Eigen::Vector3f & pos, const Eigen::Vector3f & vel, double dt)
1112{
1113 Eigen::Vector3f new_pos = Eigen::Vector3f::Zero();
1114 new_pos[0] = pos[0] + (static_cast<double>(vel[0]) * cos(pos[2]) +
1115 static_cast<double>(vel[1]) * cos(M_PI_2 + pos[2])) *
1116 dt;
1117 new_pos[1] = pos[1] + (static_cast<double>(vel[0]) * sin(pos[2]) +
1118 static_cast<double>(vel[1]) * sin(M_PI_2 + pos[2])) *
1119 dt;
1120 new_pos[2] = pos[2] + vel[2] * dt;
1121 return new_pos;
1122}
1123
1125{
1126 visualization_msgs::msg::Marker marker;
1127 marker.header.frame_id = this->costmapRos_->getGlobalFrameID();
1128 marker.header.stamp = nh_->now();
1129
1130 marker.ns = "my_namespace2";
1131 marker.id = 0;
1132 marker.type = visualization_msgs::msg::Marker::ARROW;
1133 marker.action = visualization_msgs::msg::Marker::DELETEALL;
1134
1135 visualization_msgs::msg::MarkerArray ma;
1136 ma.markers.push_back(marker);
1137
1138 goalMarkerPublisher_->publish(ma);
1139}
1140
1146void BackwardLocalPlanner::publishGoalMarker(double x, double y, double phi)
1147{
1148 visualization_msgs::msg::Marker marker;
1149 marker.header.frame_id = this->costmapRos_->getGlobalFrameID();
1150 marker.header.stamp = nh_->now();
1151
1152 marker.ns = "my_namespace2";
1153 marker.id = 0;
1154 marker.type = visualization_msgs::msg::Marker::ARROW;
1155 marker.action = visualization_msgs::msg::Marker::ADD;
1156 marker.lifetime = rclcpp::Duration(1.0s);
1157
1158 marker.pose.orientation.w = 1;
1159
1160 marker.scale.x = 0.05;
1161 marker.scale.y = 0.15;
1162 marker.scale.z = 0.05;
1163 marker.color.a = 1.0;
1164
1165 // red marker
1166 marker.color.r = 1;
1167 marker.color.g = 0;
1168 marker.color.b = 0;
1169
1170 geometry_msgs::msg::Point start, end;
1171 start.x = x;
1172 start.y = y;
1173
1174 end.x = x + 0.5 * cos(phi);
1175 end.y = y + 0.5 * sin(phi);
1176
1177 marker.points.push_back(start);
1178 marker.points.push_back(end);
1179
1180 visualization_msgs::msg::MarkerArray ma;
1181 ma.markers.push_back(marker);
1182
1183 goalMarkerPublisher_->publish(ma);
1184}
1185} // namespace backward_local_planner
1186} // namespace cl_nav2z
bool updateCarrotGoal(const geometry_msgs::msg::PoseStamped &pose)
bool checkCarrotHalfPlainConstraint(const geometry_msgs::msg::PoseStamped &pose)
Eigen::Vector3f computeNewPositions(const Eigen::Vector3f &pos, const Eigen::Vector3f &vel, double dt)
virtual geometry_msgs::msg::TwistStamped computeVelocityCommands(const geometry_msgs::msg::PoseStamped &pose, const geometry_msgs::msg::Twist &velocity, nav2_core::GoalChecker *goal_checker) override
nav2_core computeVelocityCommands - calculates the best command given the current pose and velocity
void straightBackwardsAndPureSpinCmd(const geometry_msgs::msg::PoseStamped &pose, double &vetta, double &gamma, double alpha_error, double betta_error, double rho_error)
std::vector< geometry_msgs::msg::PoseStamped > backwardsPlanPath_
void generateTrajectory(const Eigen::Vector3f &pos, const Eigen::Vector3f &vel, float maxdist, float maxangle, float maxtime, float dt, std::vector< Eigen::Vector3f > &outtraj)
std::shared_ptr< rclcpp_lifecycle::LifecyclePublisher< visualization_msgs::msg::MarkerArray > > goalMarkerPublisher_
bool findInitialCarrotGoal(geometry_msgs::msg::PoseStamped &pose)
bool divergenceDetectionUpdate(const geometry_msgs::msg::PoseStamped &pose)
void setPlan(const nav_msgs::msg::Path &path) override
nav2_core setPlan - Sets the global plan
void computeCurrentEuclideanAndAngularErrorsToCarrotGoal(const geometry_msgs::msg::PoseStamped &pose, double &dist, double &angular_error)
bool checkCurrentPoseInGoalRange(const geometry_msgs::msg::PoseStamped &tfpose, const geometry_msgs::msg::Twist &currentTwist, double angle_error, bool &linearGoalReached, nav2_core::GoalChecker *goalChecker)
std::shared_ptr< rclcpp_lifecycle::LifecyclePublisher< nav_msgs::msg::Path > > planPub_
void configure(const rclcpp_lifecycle::LifecycleNode::WeakPtr &parent, std::string name, const std::shared_ptr< tf2_ros::Buffer > tf, const std::shared_ptr< nav2_costmap_2d::Costmap2DROS > costmap_ros) override
virtual void setSpeedLimit(const double &speed_limit, const bool &percentage) override
std::shared_ptr< nav2_costmap_2d::Costmap2DROS > costmapRos_
void declareOrSet(rclcpp_lifecycle::LifecycleNode::SharedPtr &node, std::string param, T &value)
Definition common.hpp:34
void tryGetOrSet(rclcpp_lifecycle::LifecycleNode::SharedPtr &node, std::string param, T &value)