Nav2 Navigation Stack - humble  humble
ROS 2 Navigation Stack
drive_on_heading.hpp
1 // Copyright (c) 2018 Intel Corporation
2 // Copyright (c) 2022 Joshua Wallace
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 
16 #ifndef NAV2_BEHAVIORS__PLUGINS__DRIVE_ON_HEADING_HPP_
17 #define NAV2_BEHAVIORS__PLUGINS__DRIVE_ON_HEADING_HPP_
18 
19 #include <chrono>
20 #include <memory>
21 #include <utility>
22 #include <limits>
23 
24 #include "nav2_behaviors/timed_behavior.hpp"
25 #include "nav2_msgs/action/drive_on_heading.hpp"
26 #include "nav2_msgs/action/back_up.hpp"
27 #include "nav2_util/node_utils.hpp"
28 
29 namespace nav2_behaviors
30 {
31 
36 template<typename ActionT = nav2_msgs::action::DriveOnHeading>
37 class DriveOnHeading : public TimedBehavior<ActionT>
38 {
39 public:
44  : TimedBehavior<ActionT>(),
45  feedback_(std::make_shared<typename ActionT::Feedback>()),
46  command_x_(0.0),
47  command_speed_(0.0),
48  simulate_ahead_time_(0.0)
49  {
50  }
51 
52  ~DriveOnHeading() = default;
53 
59  Status onRun(const std::shared_ptr<const typename ActionT::Goal> command) override
60  {
61  if (command->target.y != 0.0 || command->target.z != 0.0) {
62  RCLCPP_INFO(
63  this->logger_,
64  "DrivingOnHeading in Y and Z not supported, will only move in X.");
65  return Status::FAILED;
66  }
67 
68  // Ensure that both the speed and direction have the same sign
69  if (!((command->target.x > 0.0) == (command->speed > 0.0)) ) {
70  RCLCPP_ERROR(this->logger_, "Speed and command sign did not match");
71  return Status::FAILED;
72  }
73 
74  command_x_ = command->target.x;
75  command_speed_ = command->speed;
76  command_time_allowance_ = command->time_allowance;
77 
78  end_time_ = this->clock_->now() + command_time_allowance_;
79 
80  if (!nav2_util::getCurrentPose(
81  initial_pose_, *this->tf_, this->global_frame_, this->robot_base_frame_,
82  this->transform_tolerance_))
83  {
84  RCLCPP_ERROR(this->logger_, "Initial robot pose is not available.");
85  return Status::FAILED;
86  }
87 
88  return Status::SUCCEEDED;
89  }
90 
95  Status onCycleUpdate() override
96  {
97  rclcpp::Duration time_remaining = end_time_ - this->clock_->now();
98  if (time_remaining.seconds() < 0.0 && command_time_allowance_.seconds() > 0.0) {
99  this->stopRobot();
100  RCLCPP_WARN(
101  this->logger_,
102  "Exceeded time allowance before reaching the DriveOnHeading goal - Exiting DriveOnHeading");
103  return Status::FAILED;
104  }
105 
106  geometry_msgs::msg::PoseStamped current_pose;
107  if (!nav2_util::getCurrentPose(
108  current_pose, *this->tf_, this->global_frame_, this->robot_base_frame_,
109  this->transform_tolerance_))
110  {
111  RCLCPP_ERROR(this->logger_, "Current robot pose is not available.");
112  return Status::FAILED;
113  }
114 
115  double diff_x = initial_pose_.pose.position.x - current_pose.pose.position.x;
116  double diff_y = initial_pose_.pose.position.y - current_pose.pose.position.y;
117  double distance = hypot(diff_x, diff_y);
118 
119  feedback_->distance_traveled = distance;
120  this->action_server_->publish_feedback(feedback_);
121 
122  if (distance >= std::fabs(command_x_)) {
123  this->stopRobot();
124  return Status::SUCCEEDED;
125  }
126 
127  auto cmd_vel = std::make_unique<geometry_msgs::msg::Twist>();
128  cmd_vel->linear.y = 0.0;
129  cmd_vel->angular.z = 0.0;
130 
131  bool forward = command_speed_ > 0.0;
132  if (acceleration_limit_ == 0.0 || deceleration_limit_ == 0.0) {
133  RCLCPP_INFO_ONCE(this->logger_, "DriveOnHeading: no acceleration or deceleration limits set");
134  cmd_vel->linear.x = command_speed_;
135  } else {
136  double current_speed = last_vel_ == std::numeric_limits<double>::max() ? 0.0 : last_vel_;
137  double min_feasible_speed, max_feasible_speed;
138  if (forward) {
139  min_feasible_speed = current_speed + deceleration_limit_ / this->cycle_frequency_;
140  max_feasible_speed = current_speed + acceleration_limit_ / this->cycle_frequency_;
141  } else {
142  min_feasible_speed = current_speed - acceleration_limit_ / this->cycle_frequency_;
143  max_feasible_speed = current_speed - deceleration_limit_ / this->cycle_frequency_;
144  }
145  cmd_vel->linear.x = std::clamp(command_speed_, min_feasible_speed, max_feasible_speed);
146 
147  // Check if we need to slow down to avoid overshooting
148  auto remaining_distance = std::fabs(command_x_) - distance;
149  double max_vel_to_stop = std::sqrt(-2.0 * deceleration_limit_ * remaining_distance);
150  if (max_vel_to_stop < std::abs(cmd_vel->linear.x)) {
151  cmd_vel->linear.x = forward ? max_vel_to_stop : -max_vel_to_stop;
152  }
153  }
154 
155  // Ensure we don't go below minimum speed
156  if (std::fabs(cmd_vel->linear.x) < minimum_speed_) {
157  cmd_vel->linear.x = forward ? minimum_speed_ : -minimum_speed_;
158  }
159 
160  geometry_msgs::msg::Pose2D pose2d;
161  pose2d.x = current_pose.pose.position.x;
162  pose2d.y = current_pose.pose.position.y;
163  pose2d.theta = tf2::getYaw(current_pose.pose.orientation);
164 
165  if (!isCollisionFree(distance, cmd_vel.get(), pose2d)) {
166  this->stopRobot();
167  RCLCPP_WARN(this->logger_, "Collision Ahead - Exiting DriveOnHeading");
168  return Status::FAILED;
169  }
170 
171  last_vel_ = cmd_vel->linear.x;
172  this->vel_pub_->publish(std::move(cmd_vel));
173 
174  return Status::RUNNING;
175  }
176 
177  void onCleanup() override {last_vel_ = std::numeric_limits<double>::max();}
178 
179  void onActionCompletion() override
180  {
181  last_vel_ = std::numeric_limits<double>::max();
182  }
183 
184 protected:
193  const double & distance,
194  geometry_msgs::msg::Twist * cmd_vel,
195  geometry_msgs::msg::Pose2D & pose2d)
196  {
197  // Simulate ahead by simulate_ahead_time_ in this->cycle_frequency_ increments
198  int cycle_count = 0;
199  double sim_position_change;
200  const double diff_dist = abs(command_x_) - distance;
201  const int max_cycle_count = static_cast<int>(this->cycle_frequency_ * simulate_ahead_time_);
202  geometry_msgs::msg::Pose2D init_pose = pose2d;
203  bool fetch_data = true;
204 
205  while (cycle_count < max_cycle_count) {
206  sim_position_change = cmd_vel->linear.x * (cycle_count / this->cycle_frequency_);
207  pose2d.x = init_pose.x + sim_position_change * cos(init_pose.theta);
208  pose2d.y = init_pose.y + sim_position_change * sin(init_pose.theta);
209  cycle_count++;
210 
211  if (diff_dist - abs(sim_position_change) <= 0.) {
212  break;
213  }
214 
215  if (!this->collision_checker_->isCollisionFree(pose2d, fetch_data)) {
216  return false;
217  }
218  fetch_data = false;
219  }
220  return true;
221  }
222 
226  void onConfigure() override
227  {
228  auto node = this->node_.lock();
229  if (!node) {
230  throw std::runtime_error{"Failed to lock node"};
231  }
232 
233  nav2_util::declare_parameter_if_not_declared(
234  node,
235  "simulate_ahead_time", rclcpp::ParameterValue(2.0));
236  node->get_parameter("simulate_ahead_time", simulate_ahead_time_);
237 
238  nav2_util::declare_parameter_if_not_declared(
239  node, this->behavior_name_ + ".acceleration_limit",
240  rclcpp::ParameterValue(0.0));
241  nav2_util::declare_parameter_if_not_declared(
242  node, this->behavior_name_ + ".deceleration_limit",
243  rclcpp::ParameterValue(0.0));
244  nav2_util::declare_parameter_if_not_declared(
245  node, this->behavior_name_ + ".minimum_speed",
246  rclcpp::ParameterValue(0.0));
247  node->get_parameter(this->behavior_name_ + ".acceleration_limit", acceleration_limit_);
248  node->get_parameter(this->behavior_name_ + ".deceleration_limit", deceleration_limit_);
249  node->get_parameter(this->behavior_name_ + ".minimum_speed", minimum_speed_);
250  if (acceleration_limit_ < 0.0 || deceleration_limit_ > 0.0) {
251  RCLCPP_ERROR(
252  this->logger_,
253  "DriveOnHeading: acceleration_limit and deceleration_limit must be "
254  "positive and negative respectively");
255  acceleration_limit_ = std::abs(acceleration_limit_);
256  deceleration_limit_ = -std::abs(deceleration_limit_);
257  }
258  }
259 
260  typename ActionT::Feedback::SharedPtr feedback_;
261 
262  geometry_msgs::msg::PoseStamped initial_pose_;
263  double command_x_;
264  double command_speed_;
265  rclcpp::Duration command_time_allowance_{0, 0};
266  rclcpp::Time end_time_;
267  double simulate_ahead_time_;
268  double acceleration_limit_;
269  double deceleration_limit_;
270  double minimum_speed_;
271  double last_vel_ = std::numeric_limits<double>::max();
272 };
273 
274 } // namespace nav2_behaviors
275 
276 #endif // NAV2_BEHAVIORS__PLUGINS__DRIVE_ON_HEADING_HPP_
An action server Behavior for spinning in.
void onConfigure() override
Configuration of behavior action.
DriveOnHeading()
A constructor for nav2_behaviors::DriveOnHeading.
Status onCycleUpdate() override
Loop function to run behavior.
Status onRun(const std::shared_ptr< const typename ActionT::Goal > command) override
Initialization to run behavior.
bool isCollisionFree(const double &distance, geometry_msgs::msg::Twist *cmd_vel, geometry_msgs::msg::Pose2D &pose2d)
Check if pose is collision free.