Nav2 Navigation Stack - jazzy  jazzy
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 
23 #include "nav2_behaviors/timed_behavior.hpp"
24 #include "nav2_msgs/action/drive_on_heading.hpp"
25 #include "nav2_msgs/action/back_up.hpp"
26 #include "nav2_util/node_utils.hpp"
27 #include "geometry_msgs/msg/twist_stamped.hpp"
28 
29 namespace nav2_behaviors
30 {
31 
36 template<typename ActionT = nav2_msgs::action::DriveOnHeading>
37 class DriveOnHeading : public TimedBehavior<ActionT>
38 {
39  using CostmapInfoType = nav2_core::CostmapInfoType;
40 
41 public:
46  : TimedBehavior<ActionT>(),
47  feedback_(std::make_shared<typename ActionT::Feedback>()),
48  command_x_(0.0),
49  command_speed_(0.0),
50  simulate_ahead_time_(0.0)
51  {
52  }
53 
54  ~DriveOnHeading() = default;
55 
61  ResultStatus onRun(const std::shared_ptr<const typename ActionT::Goal> command) override
62  {
63  if (command->target.y != 0.0 || command->target.z != 0.0) {
64  RCLCPP_INFO(
65  this->logger_,
66  "DrivingOnHeading in Y and Z not supported, will only move in X.");
67  return ResultStatus{Status::FAILED, ActionT::Result::INVALID_INPUT};
68  }
69 
70  // Ensure that both the speed and direction have the same sign
71  if (!((command->target.x > 0.0) == (command->speed > 0.0)) ) {
72  RCLCPP_ERROR(this->logger_, "Speed and command sign did not match");
73  return ResultStatus{Status::FAILED, ActionT::Result::INVALID_INPUT};
74  }
75 
76  command_x_ = command->target.x;
77  command_speed_ = command->speed;
78  command_time_allowance_ = command->time_allowance;
79 
80  end_time_ = this->clock_->now() + command_time_allowance_;
81 
82  if (!nav2_util::getCurrentPose(
83  initial_pose_, *this->tf_, this->local_frame_, this->robot_base_frame_,
84  this->transform_tolerance_))
85  {
86  RCLCPP_ERROR(this->logger_, "Initial robot pose is not available.");
87  return ResultStatus{Status::FAILED, ActionT::Result::TF_ERROR};
88  }
89 
90  return ResultStatus{Status::SUCCEEDED, ActionT::Result::NONE};
91  }
92 
98  {
99  rclcpp::Duration time_remaining = end_time_ - this->clock_->now();
100  if (time_remaining.seconds() < 0.0 && command_time_allowance_.seconds() > 0.0) {
101  this->stopRobot();
102  RCLCPP_WARN(
103  this->logger_,
104  "Exceeded time allowance before reaching the DriveOnHeading goal - Exiting DriveOnHeading");
105  return ResultStatus{Status::FAILED, ActionT::Result::TIMEOUT};
106  }
107 
108  geometry_msgs::msg::PoseStamped current_pose;
109  if (!nav2_util::getCurrentPose(
110  current_pose, *this->tf_, this->local_frame_, this->robot_base_frame_,
111  this->transform_tolerance_))
112  {
113  RCLCPP_ERROR(this->logger_, "Current robot pose is not available.");
114  return ResultStatus{Status::FAILED, ActionT::Result::TF_ERROR};
115  }
116 
117  double diff_x = initial_pose_.pose.position.x - current_pose.pose.position.x;
118  double diff_y = initial_pose_.pose.position.y - current_pose.pose.position.y;
119  double distance = hypot(diff_x, diff_y);
120 
121  feedback_->distance_traveled = distance;
122  this->action_server_->publish_feedback(feedback_);
123 
124  if (distance >= std::fabs(command_x_)) {
125  this->stopRobot();
126  return ResultStatus{Status::SUCCEEDED, ActionT::Result::NONE};
127  }
128 
129  auto cmd_vel = std::make_unique<geometry_msgs::msg::TwistStamped>();
130  cmd_vel->header.stamp = this->clock_->now();
131  cmd_vel->header.frame_id = this->robot_base_frame_;
132  cmd_vel->twist.linear.y = 0.0;
133  cmd_vel->twist.angular.z = 0.0;
134  cmd_vel->twist.linear.x = command_speed_;
135 
136  geometry_msgs::msg::Pose2D pose2d;
137  pose2d.x = current_pose.pose.position.x;
138  pose2d.y = current_pose.pose.position.y;
139  pose2d.theta = tf2::getYaw(current_pose.pose.orientation);
140 
141  if (!isCollisionFree(distance, cmd_vel->twist, pose2d)) {
142  this->stopRobot();
143  RCLCPP_WARN(this->logger_, "Collision Ahead - Exiting DriveOnHeading");
144  return ResultStatus{Status::FAILED, ActionT::Result::COLLISION_AHEAD};
145  }
146 
147  this->vel_pub_->publish(std::move(cmd_vel));
148 
149  return ResultStatus{Status::RUNNING, ActionT::Result::NONE};
150  }
151 
156  CostmapInfoType getResourceInfo() override {return CostmapInfoType::LOCAL;}
157 
158 protected:
167  const double & distance,
168  const geometry_msgs::msg::Twist & cmd_vel,
169  geometry_msgs::msg::Pose2D & pose2d)
170  {
171  // Simulate ahead by simulate_ahead_time_ in this->cycle_frequency_ increments
172  int cycle_count = 0;
173  double sim_position_change;
174  const double diff_dist = abs(command_x_) - distance;
175  const int max_cycle_count = static_cast<int>(this->cycle_frequency_ * simulate_ahead_time_);
176  geometry_msgs::msg::Pose2D init_pose = pose2d;
177  bool fetch_data = true;
178 
179  while (cycle_count < max_cycle_count) {
180  sim_position_change = cmd_vel.linear.x * (cycle_count / this->cycle_frequency_);
181  pose2d.x = init_pose.x + sim_position_change * cos(init_pose.theta);
182  pose2d.y = init_pose.y + sim_position_change * sin(init_pose.theta);
183  cycle_count++;
184 
185  if (diff_dist - abs(sim_position_change) <= 0.) {
186  break;
187  }
188 
189  if (!this->local_collision_checker_->isCollisionFree(pose2d, fetch_data)) {
190  return false;
191  }
192  fetch_data = false;
193  }
194  return true;
195  }
196 
200  void onConfigure() override
201  {
202  auto node = this->node_.lock();
203  if (!node) {
204  throw std::runtime_error{"Failed to lock node"};
205  }
206 
207  nav2_util::declare_parameter_if_not_declared(
208  node,
209  "simulate_ahead_time", rclcpp::ParameterValue(2.0));
210  node->get_parameter("simulate_ahead_time", simulate_ahead_time_);
211  }
212 
213  typename ActionT::Feedback::SharedPtr feedback_;
214 
215  geometry_msgs::msg::PoseStamped initial_pose_;
216  double command_x_;
217  double command_speed_;
218  rclcpp::Duration command_time_allowance_{0, 0};
219  rclcpp::Time end_time_;
220  double simulate_ahead_time_;
221 };
222 
223 } // namespace nav2_behaviors
224 
225 #endif // NAV2_BEHAVIORS__PLUGINS__DRIVE_ON_HEADING_HPP_
An action server Behavior for spinning in.
ResultStatus onCycleUpdate() override
Loop function to run behavior.
void onConfigure() override
Configuration of behavior action.
DriveOnHeading()
A constructor for nav2_behaviors::DriveOnHeading.
bool isCollisionFree(const double &distance, const geometry_msgs::msg::Twist &cmd_vel, geometry_msgs::msg::Pose2D &pose2d)
Check if pose is collision free.
ResultStatus onRun(const std::shared_ptr< const typename ActionT::Goal > command) override
Initialization to run behavior.
CostmapInfoType getResourceInfo() override
Method to determine the required costmap info.