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