Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
controller.cpp
1 // Copyright (c) 2024 Open Navigation LLC
2 // Copyright (c) 2024 Alberto J. Tudela Roldán
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 #include <memory>
17 
18 #include "rclcpp/logging.hpp"
19 #include "rclcpp/parameter.hpp"
20 #include "opennav_docking/controller.hpp"
21 #include "nav2_util/geometry_utils.hpp"
22 #include "nav2_ros_common/node_utils.hpp"
23 #include "tf2/utils.hpp"
24 #include "nav2_ros_common/tf2_factories.hpp"
25 
26 using rcl_interfaces::msg::ParameterType;
27 
28 namespace opennav_docking
29 {
30 
32  const nav2::LifecycleNode::SharedPtr & node, nav2::TransformBuffer::SharedPtr tf,
33  std::string fixed_frame, std::string base_frame)
34 : tf2_buffer_(tf), fixed_frame_(fixed_frame), base_frame_(base_frame)
35 {
36  logger_ = node->get_logger();
37  clock_ = node->get_clock();
38 
39  std::string costmap_topic, footprint_topic;
40  k_phi_ = node->declare_or_get_parameter("controller.k_phi", 3.0);
41  k_delta_ = node->declare_or_get_parameter("controller.k_delta", 2.0);
42  beta_ = node->declare_or_get_parameter("controller.beta", 0.4);
43  lambda_ = node->declare_or_get_parameter("controller.lambda", 2.0);
44  v_linear_min_ = node->declare_or_get_parameter("controller.v_linear_min", 0.1);
45  v_linear_max_ = node->declare_or_get_parameter("controller.v_linear_max", 0.25);
46  v_angular_max_ = node->declare_or_get_parameter("controller.v_angular_max", 0.75);
47  slowdown_radius_ = node->declare_or_get_parameter("controller.slowdown_radius", 0.25);
48  deceleration_max_ = node->declare_or_get_parameter("controller.deceleration_max", 2.5);
49  rotate_to_heading_angular_vel_ = node->declare_or_get_parameter(
50  "controller.rotate_to_heading_angular_vel", 1.0);
51  rotate_to_heading_max_angular_accel_ = node->declare_or_get_parameter(
52  "controller.rotate_to_heading_max_angular_accel", 3.2);
53  use_collision_detection_ = node->declare_or_get_parameter(
54  "controller.use_collision_detection", true);
55  costmap_topic = node->declare_or_get_parameter("controller.costmap_topic",
56  std::string("local_costmap/costmap_raw"));
57  footprint_topic = node->declare_or_get_parameter("controller.footprint_topic",
58  std::string("local_costmap/published_footprint"));
59  transform_tolerance_ = node->declare_or_get_parameter(
60  "controller.transform_tolerance", 0.1);
61  projection_time_ = node->declare_or_get_parameter(
62  "controller.projection_time", 5.0);
63  simulation_time_step_ = node->declare_or_get_parameter(
64  "controller.simulation_time_step", 0.1);
65  dock_collision_threshold_ = node->declare_or_get_parameter(
66  "controller.dock_collision_threshold", 0.3);
67 
68  control_law_ = std::make_unique<nav2_graceful_controller::SmoothControlLaw>(
69  k_phi_, k_delta_, beta_, lambda_, slowdown_radius_, deceleration_max_,
70  v_linear_min_, v_linear_max_, v_angular_max_);
71 
72  // Add callback for dynamic parameters
73  post_set_params_handler_ = node->add_post_set_parameters_callback(
74  std::bind(
76  this, std::placeholders::_1));
77  on_set_params_handler_ = node->add_on_set_parameters_callback(
78  std::bind(
80  this, std::placeholders::_1));
81 
82  if (use_collision_detection_) {
83  configureCollisionChecker(node, costmap_topic, footprint_topic, transform_tolerance_);
84  }
85 
86  trajectory_pub_ =
87  node->create_publisher<nav_msgs::msg::Path>("docking_trajectory");
88 }
89 
91 {
92  control_law_.reset();
93  trajectory_pub_.reset();
94  collision_checker_.reset();
95  costmap_sub_.reset();
96  footprint_sub_.reset();
97 }
98 
100  const geometry_msgs::msg::Pose & pose, geometry_msgs::msg::Twist & cmd, bool is_docking,
101  bool backward)
102 {
103  std::lock_guard<std::mutex> lock(dynamic_params_lock_);
104  cmd = control_law_->calculateRegularVelocity(pose, backward);
105  return isTrajectoryCollisionFree(pose, is_docking, backward);
106 }
107 
109  const double & angular_distance_to_heading,
110  const geometry_msgs::msg::Twist & current_velocity,
111  const double & dt)
112 {
113  geometry_msgs::msg::Twist cmd_vel;
114  const double sign = angular_distance_to_heading > 0.0 ? 1.0 : -1.0;
115  const double angular_vel = sign * rotate_to_heading_angular_vel_;
116  const double min_feasible_angular_speed =
117  current_velocity.angular.z - rotate_to_heading_max_angular_accel_ * dt;
118  const double max_feasible_angular_speed =
119  current_velocity.angular.z + rotate_to_heading_max_angular_accel_ * dt;
120  cmd_vel.angular.z =
121  std::clamp(angular_vel, min_feasible_angular_speed, max_feasible_angular_speed);
122 
123  // Check if we need to slow down to avoid overshooting
124  double max_vel_to_stop =
125  std::sqrt(2 * rotate_to_heading_max_angular_accel_ * fabs(angular_distance_to_heading));
126  if (fabs(cmd_vel.angular.z) > max_vel_to_stop) {
127  cmd_vel.angular.z = sign * max_vel_to_stop;
128  }
129 
130  return cmd_vel;
131 }
132 
134  const geometry_msgs::msg::Pose & target_pose, bool is_docking, bool backward)
135 {
136  // Visualization of the trajectory
137  auto trajectory = std::make_unique<nav_msgs::msg::Path>();
138  trajectory->header.frame_id = base_frame_;
139  trajectory->header.stamp = clock_->now();
140 
141  // First pose
142  geometry_msgs::msg::PoseStamped next_pose;
143  next_pose.header.frame_id = base_frame_;
144  trajectory->poses.push_back(next_pose);
145 
146  // Get the transform from base_frame to fixed_frame
147  geometry_msgs::msg::TransformStamped base_to_fixed_transform;
148  try {
149  base_to_fixed_transform = tf2_buffer_->lookupTransform(
150  fixed_frame_, base_frame_, trajectory->header.stamp,
151  tf2::durationFromSec(transform_tolerance_));
152  } catch (tf2::TransformException & ex) {
153  RCLCPP_ERROR(
154  logger_, "Could not get transform from %s to %s: %s",
155  base_frame_.c_str(), fixed_frame_.c_str(), ex.what());
156  return false;
157  }
158 
159  // Generate path
160  double distance = std::numeric_limits<double>::max();
161  unsigned int max_iter = static_cast<unsigned int>(ceil(projection_time_ / simulation_time_step_));
162 
163  do{
164  // Apply velocities to calculate next pose
165  next_pose.pose = control_law_->calculateNextPose(
166  simulation_time_step_, target_pose, next_pose.pose, backward);
167 
168  // Add the pose to the trajectory for visualization
169  trajectory->poses.push_back(next_pose);
170 
171  // Transform pose from base_frame into fixed_frame
172  geometry_msgs::msg::PoseStamped local_pose = next_pose;
173  local_pose.header.stamp = trajectory->header.stamp;
174  tf2::doTransform(local_pose, local_pose, base_to_fixed_transform);
175 
176  // Determine the distance at which to check for collisions
177  // Skip the final segment of the trajectory for docking
178  // and the initial segment for undocking
179  // This avoids false positives when the robot is at the dock
180  double dock_collision_distance = is_docking ?
181  nav2_util::geometry_utils::euclidean_distance(target_pose, next_pose.pose) :
182  std::hypot(next_pose.pose.position.x, next_pose.pose.position.y);
183 
184  // If this distance is greater than the dock_collision_threshold, check for collisions
185  if (use_collision_detection_ &&
186  dock_collision_distance > dock_collision_threshold_ &&
187  !collision_checker_->isCollisionFree(local_pose.pose))
188  {
189  RCLCPP_WARN(
190  logger_, "Collision detected at pose: (%.2f, %.2f, %.2f) in frame %s",
191  local_pose.pose.position.x, local_pose.pose.position.y, local_pose.pose.position.z,
192  local_pose.header.frame_id.c_str());
193  trajectory_pub_->publish(std::move(trajectory));
194  return false;
195  }
196 
197  // Check if we reach the goal
198  distance = nav2_util::geometry_utils::euclidean_distance(target_pose, next_pose.pose);
199  }while(distance > 1e-2 && trajectory->poses.size() < max_iter);
200 
201  trajectory_pub_->publish(std::move(trajectory));
202 
203  return true;
204 }
205 
207  const nav2::LifecycleNode::SharedPtr & node,
208  std::string costmap_topic, std::string footprint_topic, double transform_tolerance)
209 {
210  costmap_sub_ = std::make_unique<nav2_costmap_2d::CostmapSubscriber>(node, costmap_topic);
211  footprint_sub_ = std::make_unique<nav2_costmap_2d::FootprintSubscriber>(
212  node, footprint_topic, *tf2_buffer_, base_frame_, transform_tolerance);
213  collision_checker_ = std::make_shared<nav2_costmap_2d::CostmapTopicCollisionChecker>(
214  *costmap_sub_, *footprint_sub_, node->get_name());
215 }
216 
217 rcl_interfaces::msg::SetParametersResult Controller::validateParameterUpdatesCallback(
218  const std::vector<rclcpp::Parameter> & parameters)
219 {
220  rcl_interfaces::msg::SetParametersResult result;
221  result.successful = true;
222  for (const auto & parameter : parameters) {
223  const auto & param_type = parameter.get_type();
224  const auto & param_name = parameter.get_name();
225  if (param_name.find("controller.") != 0) {
226  continue;
227  }
228  if (param_type == ParameterType::PARAMETER_DOUBLE) {
229  if (parameter.as_double() < 0.0) {
230  RCLCPP_WARN(
231  logger_, "The value of parameter '%s' is incorrectly set to %f, "
232  "it should be >=0. Ignoring parameter update.",
233  param_name.c_str(), parameter.as_double());
234  result.successful = false;
235  }
236  }
237  }
238  return result;
239 }
240 
241 void
242 Controller::updateParametersCallback(const std::vector<rclcpp::Parameter> & parameters)
243 {
244  std::lock_guard<std::mutex> lock(dynamic_params_lock_);
245 
246  for (auto parameter : parameters) {
247  const auto & param_type = parameter.get_type();
248  const auto & param_name = parameter.get_name();
249  if (param_name.find("controller.") != 0) {
250  continue;
251  }
252  if (param_type == rcl_interfaces::msg::ParameterType::PARAMETER_DOUBLE) {
253  if (param_name == "controller.k_phi") {
254  k_phi_ = parameter.as_double();
255  } else if (param_name == "controller.k_delta") {
256  k_delta_ = parameter.as_double();
257  } else if (param_name == "controller.beta") {
258  beta_ = parameter.as_double();
259  } else if (param_name == "controller.lambda") {
260  lambda_ = parameter.as_double();
261  } else if (param_name == "controller.v_linear_min") {
262  v_linear_min_ = parameter.as_double();
263  } else if (param_name == "controller.v_linear_max") {
264  v_linear_max_ = parameter.as_double();
265  } else if (param_name == "controller.v_angular_max") {
266  v_angular_max_ = parameter.as_double();
267  } else if (param_name == "controller.slowdown_radius") {
268  slowdown_radius_ = parameter.as_double();
269  } else if (param_name == "controller.deceleration_max") {
270  deceleration_max_ = parameter.as_double();
271  } else if (param_name == "controller.rotate_to_heading_angular_vel") {
272  rotate_to_heading_angular_vel_ = parameter.as_double();
273  } else if (param_name == "controller.rotate_to_heading_max_angular_accel") {
274  rotate_to_heading_max_angular_accel_ = parameter.as_double();
275  } else if (param_name == "controller.projection_time") {
276  projection_time_ = parameter.as_double();
277  } else if (param_name == "controller.simulation_time_step") {
278  simulation_time_step_ = parameter.as_double();
279  } else if (param_name == "controller.dock_collision_threshold") {
280  dock_collision_threshold_ = parameter.as_double();
281  }
282 
283  // Update the smooth control law with the new params
284  control_law_->setCurvatureConstants(k_phi_, k_delta_, beta_, lambda_);
285  control_law_->setSlowdownRadius(slowdown_radius_);
286  control_law_->setMaxDeceleration(deceleration_max_);
287  control_law_->setSpeedLimit(v_linear_min_, v_linear_max_, v_angular_max_);
288  }
289  }
290 }
291 
292 } // namespace opennav_docking
void updateParametersCallback(const std::vector< rclcpp::Parameter > &parameters)
Apply parameter updates after validation This callback is executed when parameters have been successf...
Definition: controller.cpp:242
bool isTrajectoryCollisionFree(const geometry_msgs::msg::Pose &target_pose, bool is_docking, bool backward=false)
Check if a trajectory is collision free.
Definition: controller.cpp:133
Controller(const nav2::LifecycleNode::SharedPtr &node, nav2::TransformBuffer::SharedPtr tf, std::string fixed_frame, std::string base_frame)
Create a controller instance. Configure ROS 2 parameters.
Definition: controller.cpp:31
bool computeVelocityCommand(const geometry_msgs::msg::Pose &pose, geometry_msgs::msg::Twist &cmd, bool is_docking, bool backward=false)
Compute a velocity command using control law.
Definition: controller.cpp:99
~Controller()
A destructor for opennav_docking::Controller.
Definition: controller.cpp:90
geometry_msgs::msg::Twist computeRotateToHeadingCommand(const double &angular_distance_to_heading, const geometry_msgs::msg::Twist &current_velocity, const double &dt)
Perform a command for in-place rotation.
Definition: controller.cpp:108
void configureCollisionChecker(const nav2::LifecycleNode::SharedPtr &node, std::string costmap_topic, std::string footprint_topic, double transform_tolerance)
Configure the collision checker.
Definition: controller.cpp:206
rcl_interfaces::msg::SetParametersResult validateParameterUpdatesCallback(const std::vector< rclcpp::Parameter > &parameters)
Validate incoming parameter updates before applying them. This callback is triggered when one or more...
Definition: controller.cpp:217