Nav2 Navigation Stack - jazzy  jazzy
ROS 2 Navigation Stack
velocity_smoother.cpp
1 // Copyright (c) 2022 Samsung Research
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 #include <chrono>
16 #include <limits>
17 #include <memory>
18 #include <string>
19 #include <utility>
20 #include <vector>
21 
22 #include "nav2_velocity_smoother/velocity_smoother.hpp"
23 
24 using namespace std::chrono_literals;
25 using nav2_util::declare_parameter_if_not_declared;
26 using std::placeholders::_1;
27 using rcl_interfaces::msg::ParameterType;
28 
29 namespace nav2_velocity_smoother
30 {
31 
32 VelocitySmoother::VelocitySmoother(const rclcpp::NodeOptions & options)
33 : LifecycleNode("velocity_smoother", "", options),
34  last_command_time_{0, 0, get_clock()->get_clock_type()}
35 {
36 }
37 
39 {
40  if (timer_) {
41  timer_->cancel();
42  timer_.reset();
43  }
44 }
45 
46 nav2_util::CallbackReturn
47 VelocitySmoother::on_configure(const rclcpp_lifecycle::State & state)
48 {
49  RCLCPP_INFO(get_logger(), "Configuring velocity smoother");
50  auto node = shared_from_this();
51  std::string feedback_type;
52  double velocity_timeout_dbl;
53 
54  // Smoothing metadata
55  declare_parameter_if_not_declared(node, "smoothing_frequency", rclcpp::ParameterValue(20.0));
56  declare_parameter_if_not_declared(
57  node, "feedback", rclcpp::ParameterValue(std::string("OPEN_LOOP")));
58  declare_parameter_if_not_declared(node, "scale_velocities", rclcpp::ParameterValue(false));
59  node->get_parameter("smoothing_frequency", smoothing_frequency_);
60  node->get_parameter("feedback", feedback_type);
61  node->get_parameter("scale_velocities", scale_velocities_);
62 
63  // Kinematics
64  declare_parameter_if_not_declared(
65  node, "max_velocity", rclcpp::ParameterValue(std::vector<double>{0.50, 0.0, 2.5}));
66  declare_parameter_if_not_declared(
67  node, "min_velocity", rclcpp::ParameterValue(std::vector<double>{-0.50, 0.0, -2.5}));
68  declare_parameter_if_not_declared(
69  node, "max_accel", rclcpp::ParameterValue(std::vector<double>{2.5, 0.0, 3.2}));
70  declare_parameter_if_not_declared(
71  node, "max_decel", rclcpp::ParameterValue(std::vector<double>{-2.5, 0.0, -3.2}));
72  node->get_parameter("max_velocity", max_velocities_);
73  node->get_parameter("min_velocity", min_velocities_);
74  node->get_parameter("max_accel", max_accels_);
75  node->get_parameter("max_decel", max_decels_);
76 
77  for (unsigned int i = 0; i != 3; i++) {
78  if (max_decels_[i] > 0.0) {
79  RCLCPP_ERROR(
80  get_logger(),
81  "Positive values set of deceleration! These should be negative to slow down!");
82  on_cleanup(state);
83  return nav2_util::CallbackReturn::FAILURE;
84  }
85  if (max_accels_[i] < 0.0) {
86  RCLCPP_ERROR(
87  get_logger(),
88  "Negative values set of acceleration! These should be positive to speed up!");
89  on_cleanup(state);
90  return nav2_util::CallbackReturn::FAILURE;
91  }
92  if (min_velocities_[i] > 0.0) {
93  RCLCPP_ERROR(
94  get_logger(), "Positive values set of min_velocities! These should be negative!");
95  on_cleanup(state);
96  return nav2_util::CallbackReturn::FAILURE;
97  }
98  if (max_velocities_[i] < 0.0) {
99  RCLCPP_ERROR(
100  get_logger(), "Negative values set of max_velocities! These should be positive!");
101  on_cleanup(state);
102  return nav2_util::CallbackReturn::FAILURE;
103  }
104  if (min_velocities_[i] > max_velocities_[i]) {
105  RCLCPP_ERROR(get_logger(), "Min velocities are higher than max velocities!");
106  on_cleanup(state);
107  return nav2_util::CallbackReturn::FAILURE;
108  }
109  }
110 
111  // Get feature parameters
112  declare_parameter_if_not_declared(node, "odom_topic", rclcpp::ParameterValue("odom"));
113  declare_parameter_if_not_declared(node, "odom_duration", rclcpp::ParameterValue(0.1));
114  declare_parameter_if_not_declared(
115  node, "deadband_velocity", rclcpp::ParameterValue(std::vector<double>{0.0, 0.0, 0.0}));
116  declare_parameter_if_not_declared(node, "velocity_timeout", rclcpp::ParameterValue(1.0));
117  node->get_parameter("odom_topic", odom_topic_);
118  node->get_parameter("odom_duration", odom_duration_);
119  node->get_parameter("deadband_velocity", deadband_velocities_);
120  node->get_parameter("velocity_timeout", velocity_timeout_dbl);
121  velocity_timeout_ = rclcpp::Duration::from_seconds(velocity_timeout_dbl);
122 
123  if (max_velocities_.size() != 3 || min_velocities_.size() != 3 ||
124  max_accels_.size() != 3 || max_decels_.size() != 3 || deadband_velocities_.size() != 3)
125  {
126  RCLCPP_ERROR(
127  get_logger(),
128  "Invalid setting of kinematic and/or deadband limits!"
129  " All limits must be size of 3 representing (x, y, theta).");
130  on_cleanup(state);
131  return nav2_util::CallbackReturn::FAILURE;
132  }
133 
134  // Get control type
135  if (feedback_type == "OPEN_LOOP") {
136  open_loop_ = true;
137  } else if (feedback_type == "CLOSED_LOOP") {
138  open_loop_ = false;
139  odom_smoother_ = std::make_unique<nav2_util::OdomSmoother>(node, odom_duration_, odom_topic_);
140  } else {
141  RCLCPP_ERROR(
142  get_logger(),
143  "Invalid feedback_type, options are OPEN_LOOP and CLOSED_LOOP.");
144  on_cleanup(state);
145  return nav2_util::CallbackReturn::FAILURE;
146  }
147 
148  // Define option to overwrite the timestamp of the message containing the smoothed velocity
149  declare_parameter_if_not_declared(
150  node, "stamp_smoothed_velocity_with_smoothing_time", rclcpp::ParameterValue(false));
151  node->get_parameter(
152  "stamp_smoothed_velocity_with_smoothing_time", stamp_smoothed_velocity_with_smoothing_time_);
153 
154  // Setup inputs / outputs
155  smoothed_cmd_pub_ = std::make_unique<nav2_util::TwistPublisher>(node, "cmd_vel_smoothed", 1);
156  cmd_sub_ = std::make_unique<nav2_util::TwistSubscriber>(
157  node,
158  "cmd_vel", rclcpp::QoS(1),
159  std::bind(&VelocitySmoother::inputCommandCallback, this, std::placeholders::_1),
160  std::bind(&VelocitySmoother::inputCommandStampedCallback, this, std::placeholders::_1)
161  );
162 
163  declare_parameter_if_not_declared(node, "use_realtime_priority", rclcpp::ParameterValue(false));
164  bool use_realtime_priority = false;
165  node->get_parameter("use_realtime_priority", use_realtime_priority);
166  if (use_realtime_priority) {
167  try {
168  nav2_util::setSoftRealTimePriority();
169  } catch (const std::runtime_error & e) {
170  RCLCPP_ERROR(get_logger(), "%s", e.what());
171  on_cleanup(state);
172  return nav2_util::CallbackReturn::FAILURE;
173  }
174  }
175 
176  return nav2_util::CallbackReturn::SUCCESS;
177 }
178 
179 nav2_util::CallbackReturn
180 VelocitySmoother::on_activate(const rclcpp_lifecycle::State &)
181 {
182  RCLCPP_INFO(get_logger(), "Activating");
183  smoothed_cmd_pub_->on_activate();
184  double timer_duration_ms = 1000.0 / smoothing_frequency_;
185  timer_ = this->create_wall_timer(
186  std::chrono::milliseconds(static_cast<int>(timer_duration_ms)),
187  std::bind(&VelocitySmoother::smootherTimer, this));
188 
189  dyn_params_handler_ = this->add_on_set_parameters_callback(
190  std::bind(&VelocitySmoother::dynamicParametersCallback, this, _1));
191 
192  // create bond connection
193  createBond();
194  return nav2_util::CallbackReturn::SUCCESS;
195 }
196 
197 nav2_util::CallbackReturn
198 VelocitySmoother::on_deactivate(const rclcpp_lifecycle::State &)
199 {
200  RCLCPP_INFO(get_logger(), "Deactivating");
201  if (timer_) {
202  timer_->cancel();
203  timer_.reset();
204  }
205  smoothed_cmd_pub_->on_deactivate();
206 
207  remove_on_set_parameters_callback(dyn_params_handler_.get());
208  dyn_params_handler_.reset();
209 
210  // destroy bond connection
211  destroyBond();
212  return nav2_util::CallbackReturn::SUCCESS;
213 }
214 
215 nav2_util::CallbackReturn
216 VelocitySmoother::on_cleanup(const rclcpp_lifecycle::State &)
217 {
218  RCLCPP_INFO(get_logger(), "Cleaning up");
219  smoothed_cmd_pub_.reset();
220  odom_smoother_.reset();
221  cmd_sub_.reset();
222  return nav2_util::CallbackReturn::SUCCESS;
223 }
224 
225 nav2_util::CallbackReturn
226 VelocitySmoother::on_shutdown(const rclcpp_lifecycle::State &)
227 {
228  RCLCPP_INFO(get_logger(), "Shutting down");
229  return nav2_util::CallbackReturn::SUCCESS;
230 }
231 
232 void VelocitySmoother::inputCommandStampedCallback(
233  const geometry_msgs::msg::TwistStamped::SharedPtr msg)
234 {
235  // If message contains NaN or Inf, ignore
236  if (!nav2_util::validateTwist(msg->twist)) {
237  RCLCPP_ERROR(get_logger(), "Velocity message contains NaNs or Infs! Ignoring as invalid!");
238  return;
239  }
240 
241  command_ = msg;
242  if (msg->header.stamp.sec == 0 && msg->header.stamp.nanosec == 0) {
243  last_command_time_ = now();
244  } else {
245  last_command_time_ = msg->header.stamp;
246  }
247 }
248 
250  geometry_msgs::msg::Twist::SharedPtr msg)
251 {
252  auto twist_stamped = std::make_shared<geometry_msgs::msg::TwistStamped>();
253  twist_stamped->twist = *msg;
254  inputCommandStampedCallback(twist_stamped);
255 }
256 
258  const double v_curr, const double v_cmd, const double accel, const double decel)
259 {
260  // Exploiting vector scaling properties
261  double dv = v_cmd - v_curr;
262 
263  double v_component_max;
264  double v_component_min;
265 
266  // Accelerating if magnitude of v_cmd is above magnitude of v_curr
267  // and if v_cmd and v_curr have the same sign (i.e. speed is NOT passing through 0.0)
268  // Decelerating otherwise
269  if (abs(v_cmd) >= abs(v_curr) && v_curr * v_cmd >= 0.0) {
270  v_component_max = accel / smoothing_frequency_;
271  v_component_min = -accel / smoothing_frequency_;
272  } else {
273  v_component_max = -decel / smoothing_frequency_;
274  v_component_min = decel / smoothing_frequency_;
275  }
276 
277  if (dv > v_component_max) {
278  return v_component_max / dv;
279  }
280 
281  if (dv < v_component_min) {
282  return v_component_min / dv;
283  }
284 
285  return -1.0;
286 }
287 
289  const double v_curr, const double v_cmd,
290  const double accel, const double decel, const double eta)
291 {
292  double dv = v_cmd - v_curr;
293 
294  double v_component_max;
295  double v_component_min;
296 
297  // Accelerating if magnitude of v_cmd is above magnitude of v_curr
298  // and if v_cmd and v_curr have the same sign (i.e. speed is NOT passing through 0.0)
299  // Decelerating otherwise
300  if (abs(v_cmd) >= abs(v_curr) && v_curr * v_cmd >= 0.0) {
301  v_component_max = accel / smoothing_frequency_;
302  v_component_min = -accel / smoothing_frequency_;
303  } else {
304  v_component_max = -decel / smoothing_frequency_;
305  v_component_min = decel / smoothing_frequency_;
306  }
307 
308  return v_curr + std::clamp(eta * dv, v_component_min, v_component_max);
309 }
310 
312 {
313  // Wait until the first command is received
314  if (!command_) {
315  return;
316  }
317 
318  auto const delta_time_since_last_command = now() - last_command_time_;
319 
320  auto cmd_vel = std::make_unique<geometry_msgs::msg::TwistStamped>();
321  cmd_vel->header.frame_id = command_->header.frame_id;
322  if (stamp_smoothed_velocity_with_smoothing_time_) {
323  // Smooth the timestamp of the smoothed message
324  // Do not keep the same timestamp of the last command; this causes jerky behavior
325  // See https://github.com/ros-navigation/navigation2/issues/5857
326  cmd_vel->header.stamp = command_->header.stamp + delta_time_since_last_command;
327  } else {
328  cmd_vel->header.stamp = command_->header.stamp;
329  }
330 
331  // Check for velocity timeout. If nothing received, publish zeros to apply deceleration
332  if (delta_time_since_last_command > velocity_timeout_) {
333  if (last_cmd_.twist == geometry_msgs::msg::Twist() || stopped_) {
334  stopped_ = true;
335  return;
336  }
337  *command_ = geometry_msgs::msg::TwistStamped();
338  command_->header.stamp = now();
339  }
340 
341  stopped_ = false;
342 
343  // Get current velocity based on feedback type
344  geometry_msgs::msg::TwistStamped current_;
345  if (open_loop_) {
346  current_ = last_cmd_;
347  } else {
348  current_ = odom_smoother_->getTwistStamped();
349  }
350 
351  // Apply absolute velocity restrictions to the command
352  command_->twist.linear.x = std::clamp(
353  command_->twist.linear.x, min_velocities_[0],
354  max_velocities_[0]);
355  command_->twist.linear.y = std::clamp(
356  command_->twist.linear.y, min_velocities_[1],
357  max_velocities_[1]);
358  command_->twist.angular.z = std::clamp(
359  command_->twist.angular.z, min_velocities_[2],
360  max_velocities_[2]);
361 
362  // Find if any component is not within the acceleration constraints. If so, store the most
363  // significant scale factor to apply to the vector <dvx, dvy, dvw>, eta, to reduce all axes
364  // proportionally to follow the same direction, within change of velocity bounds.
365  // In case eta reduces another axis out of its own limit, apply accel constraint to guarantee
366  // output is within limits, even if it deviates from requested command slightly.
367  double eta = 1.0;
368  if (scale_velocities_) {
369  double curr_eta = -1.0;
370 
371  curr_eta = findEtaConstraint(
372  current_.twist.linear.x, command_->twist.linear.x, max_accels_[0], max_decels_[0]);
373  if (curr_eta > 0.0 && std::fabs(1.0 - curr_eta) > std::fabs(1.0 - eta)) {
374  eta = curr_eta;
375  }
376 
377  curr_eta = findEtaConstraint(
378  current_.twist.linear.y, command_->twist.linear.y, max_accels_[1], max_decels_[1]);
379  if (curr_eta > 0.0 && std::fabs(1.0 - curr_eta) > std::fabs(1.0 - eta)) {
380  eta = curr_eta;
381  }
382 
383  curr_eta = findEtaConstraint(
384  current_.twist.angular.z, command_->twist.angular.z, max_accels_[2], max_decels_[2]);
385  if (curr_eta > 0.0 && std::fabs(1.0 - curr_eta) > std::fabs(1.0 - eta)) {
386  eta = curr_eta;
387  }
388  }
389 
390  cmd_vel->twist.linear.x = applyConstraints(
391  current_.twist.linear.x, command_->twist.linear.x, max_accels_[0], max_decels_[0], eta);
392  cmd_vel->twist.linear.y = applyConstraints(
393  current_.twist.linear.y, command_->twist.linear.y, max_accels_[1], max_decels_[1], eta);
394  cmd_vel->twist.angular.z = applyConstraints(
395  current_.twist.angular.z, command_->twist.angular.z, max_accels_[2], max_decels_[2], eta);
396  last_cmd_ = *cmd_vel;
397 
398  // Apply deadband restrictions & publish
399  cmd_vel->twist.linear.x =
400  fabs(cmd_vel->twist.linear.x) < deadband_velocities_[0] ? 0.0 : cmd_vel->twist.linear.x;
401  cmd_vel->twist.linear.y =
402  fabs(cmd_vel->twist.linear.y) < deadband_velocities_[1] ? 0.0 : cmd_vel->twist.linear.y;
403  cmd_vel->twist.angular.z =
404  fabs(cmd_vel->twist.angular.z) < deadband_velocities_[2] ? 0.0 : cmd_vel->twist.angular.z;
405 
406  smoothed_cmd_pub_->publish(std::move(cmd_vel));
407 }
408 
409 rcl_interfaces::msg::SetParametersResult
410 VelocitySmoother::dynamicParametersCallback(std::vector<rclcpp::Parameter> parameters)
411 {
412  rcl_interfaces::msg::SetParametersResult result;
413  result.successful = true;
414 
415  for (auto parameter : parameters) {
416  const auto & type = parameter.get_type();
417  const auto & name = parameter.get_name();
418 
419  if (type == ParameterType::PARAMETER_DOUBLE) {
420  if (name == "smoothing_frequency") {
421  smoothing_frequency_ = parameter.as_double();
422  if (timer_) {
423  timer_->cancel();
424  timer_.reset();
425  }
426 
427  double timer_duration_ms = 1000.0 / smoothing_frequency_;
428  timer_ = this->create_wall_timer(
429  std::chrono::milliseconds(static_cast<int>(timer_duration_ms)),
430  std::bind(&VelocitySmoother::smootherTimer, this));
431  } else if (name == "velocity_timeout") {
432  velocity_timeout_ = rclcpp::Duration::from_seconds(parameter.as_double());
433  } else if (name == "odom_duration") {
434  odom_duration_ = parameter.as_double();
435  odom_smoother_ =
436  std::make_unique<nav2_util::OdomSmoother>(
437  shared_from_this(), odom_duration_, odom_topic_);
438  }
439  } else if (type == ParameterType::PARAMETER_DOUBLE_ARRAY) {
440  if (parameter.as_double_array().size() != 3) {
441  RCLCPP_WARN(get_logger(), "Invalid size of parameter %s. Must be size 3", name.c_str());
442  result.successful = false;
443  break;
444  }
445 
446  if (name == "max_velocity") {
447  for (unsigned int i = 0; i != 3; i++) {
448  if (parameter.as_double_array()[i] < 0.0) {
449  RCLCPP_WARN(
450  get_logger(),
451  "Negative values set of max_velocity! These should be positive!");
452  result.successful = false;
453  }
454  }
455  if (result.successful) {
456  max_velocities_ = parameter.as_double_array();
457  }
458  } else if (name == "min_velocity") {
459  for (unsigned int i = 0; i != 3; i++) {
460  if (parameter.as_double_array()[i] > 0.0) {
461  RCLCPP_WARN(
462  get_logger(),
463  "Positive values set of min_velocity! These should be negative!");
464  result.successful = false;
465  }
466  }
467  if (result.successful) {
468  min_velocities_ = parameter.as_double_array();
469  }
470  } else if (name == "max_accel") {
471  for (unsigned int i = 0; i != 3; i++) {
472  if (parameter.as_double_array()[i] < 0.0) {
473  RCLCPP_WARN(
474  get_logger(),
475  "Negative values set of acceleration! These should be positive to speed up!");
476  result.successful = false;
477  }
478  }
479  if (result.successful) {
480  max_accels_ = parameter.as_double_array();
481  }
482  } else if (name == "max_decel") {
483  for (unsigned int i = 0; i != 3; i++) {
484  if (parameter.as_double_array()[i] > 0.0) {
485  RCLCPP_WARN(
486  get_logger(),
487  "Positive values set of deceleration! These should be negative to slow down!");
488  result.successful = false;
489  }
490  }
491  if (result.successful) {
492  max_decels_ = parameter.as_double_array();
493  }
494  } else if (name == "deadband_velocity") {
495  deadband_velocities_ = parameter.as_double_array();
496  }
497  } else if (type == ParameterType::PARAMETER_STRING) {
498  if (name == "feedback") {
499  if (parameter.as_string() == "OPEN_LOOP") {
500  open_loop_ = true;
501  odom_smoother_.reset();
502  } else if (parameter.as_string() == "CLOSED_LOOP") {
503  open_loop_ = false;
504  odom_smoother_ =
505  std::make_unique<nav2_util::OdomSmoother>(
506  shared_from_this(), odom_duration_, odom_topic_);
507  } else {
508  RCLCPP_WARN(
509  get_logger(), "Invalid feedback_type, options are OPEN_LOOP and CLOSED_LOOP.");
510  result.successful = false;
511  break;
512  }
513  } else if (name == "odom_topic") {
514  odom_topic_ = parameter.as_string();
515  odom_smoother_ =
516  std::make_unique<nav2_util::OdomSmoother>(
517  shared_from_this(), odom_duration_, odom_topic_);
518  }
519  }
520  }
521 
522  return result;
523 }
524 
525 } // namespace nav2_velocity_smoother
526 
527 #include "rclcpp_components/register_node_macro.hpp"
528 RCLCPP_COMPONENTS_REGISTER_NODE(nav2_velocity_smoother::VelocitySmoother)
std::shared_ptr< nav2_util::LifecycleNode > shared_from_this()
Get a shared pointer of this.
void createBond()
Create bond connection to lifecycle manager.
void destroyBond()
Destroy bond connection to lifecycle manager.
This class that smooths cmd_vel velocities for robot bases.
nav2_util::CallbackReturn on_deactivate(const rclcpp_lifecycle::State &state) override
Deactivates member variables.
~VelocitySmoother()
Destructor for nav2_velocity_smoother::VelocitySmoother.
nav2_util::CallbackReturn on_configure(const rclcpp_lifecycle::State &state) override
Configures parameters and member variables.
nav2_util::CallbackReturn on_cleanup(const rclcpp_lifecycle::State &state) override
Calls clean up states and resets member variables.
void inputCommandCallback(const geometry_msgs::msg::Twist::SharedPtr msg)
Callback for incoming velocity commands.
double findEtaConstraint(const double v_curr, const double v_cmd, const double accel, const double decel)
Find the scale factor, eta, which scales axis into acceleration range.
rcl_interfaces::msg::SetParametersResult dynamicParametersCallback(std::vector< rclcpp::Parameter > parameters)
Dynamic reconfigure callback.
void smootherTimer()
Main worker timer function.
nav2_util::CallbackReturn on_activate(const rclcpp_lifecycle::State &state) override
Activates member variables.
nav2_util::CallbackReturn on_shutdown(const rclcpp_lifecycle::State &state) override
Called when in Shutdown state.
double applyConstraints(const double v_curr, const double v_cmd, const double accel, const double decel, const double eta)
Apply acceleration and scale factor constraints.