22 #include "nav2_velocity_smoother/velocity_smoother.hpp"
24 using namespace std::chrono_literals;
25 using std::placeholders::_1;
26 using rcl_interfaces::msg::ParameterType;
28 namespace nav2_velocity_smoother
31 VelocitySmoother::VelocitySmoother(
const rclcpp::NodeOptions & options)
32 : LifecycleNode(
"velocity_smoother",
"", options),
33 last_command_time_{0, 0, get_clock()->get_clock_type()}
48 RCLCPP_INFO(get_logger(),
"Configuring velocity smoother");
52 smoothing_frequency_ = node->declare_or_get_parameter(
53 "smoothing_frequency", 20.0);
54 std::string feedback_type = node->declare_or_get_parameter(
55 "feedback", std::string(
"OPEN_LOOP"));
56 scale_velocities_ = node->declare_or_get_parameter(
"scale_velocities",
false);
59 max_velocities_ = node->declare_or_get_parameter(
60 "max_velocity", std::vector<double>{0.50, 0.0, 2.5});
61 min_velocities_ = node->declare_or_get_parameter(
62 "min_velocity", std::vector<double>{-0.50, 0.0, -2.5});
63 max_accels_ = node->declare_or_get_parameter(
64 "max_accel", std::vector<double>{2.5, 0.0, 3.2});
65 max_decels_ = node->declare_or_get_parameter(
66 "max_decel", std::vector<double>{-2.5, 0.0, -3.2});
69 odom_topic_ = node->declare_or_get_parameter(
"odom_topic", std::string(
"odom"));
70 odom_duration_ = node->declare_or_get_parameter(
"odom_duration", 0.1);
71 odom_smoother_ = std::make_unique<nav2_util::OdomSmoother>(node, odom_duration_, odom_topic_);
72 deadband_velocities_ = node->declare_or_get_parameter(
73 "deadband_velocity", std::vector<double>{0.0, 0.0, 0.0});
74 double velocity_timeout_dbl = node->declare_or_get_parameter(
"velocity_timeout", 1.0);
75 velocity_timeout_ = rclcpp::Duration::from_seconds(velocity_timeout_dbl);
78 size_t size = max_velocities_.size();
79 is_6dof_ = (size == 6);
81 if ((size != 3 && size != 6) ||
82 min_velocities_.size() != size ||
83 max_accels_.size() != size ||
84 max_decels_.size() != size ||
85 deadband_velocities_.size() != size)
89 "Invalid setting of kinematic and/or deadband limits!"
90 " All limits must be size of 3 (x, y, theta) or 6 (x, y, z, r, p, y)");
92 return nav2::CallbackReturn::FAILURE;
95 for (
unsigned int i = 0; i != size; i++) {
96 if (max_decels_[i] > 0.0) {
99 "Positive values set of deceleration! These should be negative to slow down!");
101 return nav2::CallbackReturn::FAILURE;
103 if (max_accels_[i] < 0.0) {
106 "Negative values set of acceleration! These should be positive to speed up!");
108 return nav2::CallbackReturn::FAILURE;
110 if (min_velocities_[i] > 0.0) {
112 get_logger(),
"Positive values set of min_velocities! These should be negative!");
114 return nav2::CallbackReturn::FAILURE;
116 if (max_velocities_[i] < 0.0) {
118 get_logger(),
"Negative values set of max_velocities! These should be positive!");
120 return nav2::CallbackReturn::FAILURE;
122 if (min_velocities_[i] > max_velocities_[i]) {
123 RCLCPP_ERROR(get_logger(),
"Min velocities are higher than max velocities!");
125 return nav2::CallbackReturn::FAILURE;
130 if (feedback_type ==
"OPEN_LOOP") {
132 }
else if (feedback_type ==
"CLOSED_LOOP") {
137 "Invalid feedback_type, options are OPEN_LOOP and CLOSED_LOOP.");
139 return nav2::CallbackReturn::FAILURE;
143 smoothed_cmd_pub_ = std::make_unique<nav2_util::TwistPublisher>(node,
"cmd_vel_smoothed");
144 cmd_sub_ = std::make_unique<nav2_util::TwistSubscriber>(
148 std::bind(&VelocitySmoother::inputCommandStampedCallback,
this, std::placeholders::_1));
150 bool use_realtime_priority = node->declare_or_get_parameter(
"use_realtime_priority",
false);
151 if (use_realtime_priority) {
153 nav2::setSoftRealTimePriority();
154 }
catch (
const std::runtime_error & e) {
155 RCLCPP_ERROR(get_logger(),
"%s", e.what());
157 return nav2::CallbackReturn::FAILURE;
161 return nav2::CallbackReturn::SUCCESS;
167 RCLCPP_INFO(get_logger(),
"Activating");
168 smoothed_cmd_pub_->on_activate();
169 double timer_duration_ms = 1000.0 / smoothing_frequency_;
171 std::chrono::milliseconds(
static_cast<int>(timer_duration_ms)),
176 post_set_params_handler_ = node->add_post_set_parameters_callback(
179 this, std::placeholders::_1));
180 on_set_params_handler_ = node->add_on_set_parameters_callback(
183 this, std::placeholders::_1));
187 return nav2::CallbackReturn::SUCCESS;
193 RCLCPP_INFO(get_logger(),
"Deactivating");
198 smoothed_cmd_pub_->on_deactivate();
201 if (post_set_params_handler_ && node) {
202 node->remove_post_set_parameters_callback(post_set_params_handler_.get());
204 post_set_params_handler_.reset();
205 if (on_set_params_handler_ && node) {
206 node->remove_on_set_parameters_callback(on_set_params_handler_.get());
208 on_set_params_handler_.reset();
212 return nav2::CallbackReturn::SUCCESS;
218 RCLCPP_INFO(get_logger(),
"Cleaning up");
219 smoothed_cmd_pub_.reset();
220 odom_smoother_.reset();
222 return nav2::CallbackReturn::SUCCESS;
228 RCLCPP_INFO(get_logger(),
"Shutting down");
229 return nav2::CallbackReturn::SUCCESS;
232 void VelocitySmoother::inputCommandStampedCallback(
233 const geometry_msgs::msg::TwistStamped::ConstSharedPtr & msg)
236 if (!nav2_util::validateTwist(msg->twist)) {
237 RCLCPP_ERROR(get_logger(),
"Velocity message contains NaNs or Infs! Ignoring as invalid!");
242 if (msg->header.stamp.sec == 0 && msg->header.stamp.nanosec == 0) {
243 last_command_time_ = now();
245 last_command_time_ = msg->header.stamp;
247 received_first_command_ =
true;
251 const geometry_msgs::msg::Twist::ConstSharedPtr & msg)
253 auto twist_stamped = std::make_shared<geometry_msgs::msg::TwistStamped>();
254 twist_stamped->twist = *msg;
255 inputCommandStampedCallback(twist_stamped);
259 const double v_curr,
const double v_cmd,
const double accel,
const double decel)
262 double dv = v_cmd - v_curr;
264 double v_component_max;
265 double v_component_min;
270 if (abs(v_cmd) >= abs(v_curr) && v_curr * v_cmd >= 0.0) {
271 v_component_max = accel / smoothing_frequency_;
272 v_component_min = -accel / smoothing_frequency_;
274 v_component_max = -decel / smoothing_frequency_;
275 v_component_min = decel / smoothing_frequency_;
278 if (dv > v_component_max) {
279 return v_component_max / dv;
282 if (dv < v_component_min) {
283 return v_component_min / dv;
290 const double v_curr,
const double v_cmd,
291 const double accel,
const double decel,
const double eta)
293 double dv = v_cmd - v_curr;
295 double v_component_max;
296 double v_component_min;
301 if (abs(v_cmd) >= abs(v_curr) && v_curr * v_cmd >= 0.0) {
302 v_component_max = accel / smoothing_frequency_;
303 v_component_min = -accel / smoothing_frequency_;
305 v_component_max = -decel / smoothing_frequency_;
306 v_component_min = decel / smoothing_frequency_;
309 return v_curr + std::clamp(eta * dv, v_component_min, v_component_max);
314 std::lock_guard<std::mutex> lock(mutex_);
316 if (!received_first_command_) {
320 auto const delta_time_since_last_command = now() - last_command_time_;
322 auto cmd_vel = std::make_unique<geometry_msgs::msg::TwistStamped>();
323 cmd_vel->header.frame_id = command_.header.frame_id;
327 cmd_vel->header.stamp = command_.header.stamp + delta_time_since_last_command;
330 if (delta_time_since_last_command > velocity_timeout_) {
331 if (last_cmd_.twist == geometry_msgs::msg::Twist() || stopped_) {
335 command_ = geometry_msgs::msg::TwistStamped();
336 command_.header.stamp = now();
342 geometry_msgs::msg::TwistStamped current_;
344 current_ = last_cmd_;
346 current_ = odom_smoother_->getTwistStamped();
351 command_.twist.linear.x = std::clamp(
352 command_.twist.linear.x, min_velocities_[0],
354 command_.twist.linear.y = std::clamp(
355 command_.twist.linear.y, min_velocities_[1],
357 command_.twist.angular.z = std::clamp(
358 command_.twist.angular.z, min_velocities_[2],
361 command_.twist.linear.x = std::clamp(
362 command_.twist.linear.x, min_velocities_[0],
364 command_.twist.linear.y = std::clamp(
365 command_.twist.linear.y, min_velocities_[1],
367 command_.twist.linear.z = std::clamp(
368 command_.twist.linear.z, min_velocities_[2],
370 command_.twist.angular.x = std::clamp(
371 command_.twist.angular.x, min_velocities_[3],
373 command_.twist.angular.y = std::clamp(
374 command_.twist.angular.y, min_velocities_[4],
376 command_.twist.angular.z = std::clamp(
377 command_.twist.angular.z, min_velocities_[5],
387 if (scale_velocities_) {
388 double curr_eta = -1.0;
391 current_.twist.linear.x, command_.twist.linear.x, max_accels_[0], max_decels_[0]);
392 if (curr_eta > 0.0 && std::fabs(1.0 - curr_eta) > std::fabs(1.0 - eta)) {
397 current_.twist.linear.y, command_.twist.linear.y, max_accels_[1], max_decels_[1]);
398 if (curr_eta > 0.0 && std::fabs(1.0 - curr_eta) > std::fabs(1.0 - eta)) {
403 current_.twist.angular.z, command_.twist.angular.z, max_accels_[2], max_decels_[2]);
404 if (curr_eta > 0.0 && std::fabs(1.0 - curr_eta) > std::fabs(1.0 - eta)) {
409 current_.twist.linear.x, command_.twist.linear.x, max_accels_[0], max_decels_[0]);
410 if (curr_eta > 0.0 && std::fabs(1.0 - curr_eta) > std::fabs(1.0 - eta)) {
415 current_.twist.linear.y, command_.twist.linear.y, max_accels_[1], max_decels_[1]);
416 if (curr_eta > 0.0 && std::fabs(1.0 - curr_eta) > std::fabs(1.0 - eta)) {
421 current_.twist.linear.z, command_.twist.linear.z, max_accels_[2], max_decels_[2]);
422 if (curr_eta > 0.0 && std::fabs(1.0 - curr_eta) > std::fabs(1.0 - eta)) {
427 current_.twist.angular.x, command_.twist.angular.x, max_accels_[3], max_decels_[3]);
428 if (curr_eta > 0.0 && std::fabs(1.0 - curr_eta) > std::fabs(1.0 - eta)) {
433 current_.twist.angular.y, command_.twist.angular.y, max_accels_[4], max_decels_[4]);
434 if (curr_eta > 0.0 && std::fabs(1.0 - curr_eta) > std::fabs(1.0 - eta)) {
439 current_.twist.angular.z, command_.twist.angular.z, max_accels_[5], max_decels_[5]);
440 if (curr_eta > 0.0 && std::fabs(1.0 - curr_eta) > std::fabs(1.0 - eta)) {
448 current_.twist.linear.x, command_.twist.linear.x, max_accels_[0], max_decels_[0], eta);
450 current_.twist.linear.y, command_.twist.linear.y, max_accels_[1], max_decels_[1], eta);
452 current_.twist.angular.z, command_.twist.angular.z, max_accels_[2], max_decels_[2], eta);
455 current_.twist.linear.x, command_.twist.linear.x, max_accels_[0], max_decels_[0], eta);
457 current_.twist.linear.y, command_.twist.linear.y, max_accels_[1], max_decels_[1], eta);
459 current_.twist.linear.z, command_.twist.linear.z, max_accels_[2], max_decels_[2], eta);
461 current_.twist.angular.x, command_.twist.angular.x, max_accels_[3], max_decels_[3], eta);
463 current_.twist.angular.y, command_.twist.angular.y, max_accels_[4], max_decels_[4], eta);
465 current_.twist.angular.z, command_.twist.angular.z, max_accels_[5], max_decels_[5], eta);
468 last_cmd_ = *cmd_vel;
473 cmd_vel->twist.linear.x =
474 fabs(cmd_vel->twist.linear.x) < deadband_velocities_[0] ? 0.0 : cmd_vel->twist.linear.x;
475 cmd_vel->twist.linear.y =
476 fabs(cmd_vel->twist.linear.y) < deadband_velocities_[1] ? 0.0 : cmd_vel->twist.linear.y;
477 cmd_vel->twist.linear.z = command_.twist.linear.z;
478 cmd_vel->twist.angular.x = command_.twist.angular.x;
479 cmd_vel->twist.angular.y = command_.twist.angular.y;
480 cmd_vel->twist.angular.z =
481 fabs(cmd_vel->twist.angular.z) < deadband_velocities_[2] ? 0.0 : cmd_vel->twist.angular.z;
483 cmd_vel->twist.linear.x =
484 fabs(cmd_vel->twist.linear.x) < deadband_velocities_[0] ? 0.0 : cmd_vel->twist.linear.x;
485 cmd_vel->twist.linear.y =
486 fabs(cmd_vel->twist.linear.y) < deadband_velocities_[1] ? 0.0 : cmd_vel->twist.linear.y;
487 cmd_vel->twist.linear.z =
488 fabs(cmd_vel->twist.linear.z) < deadband_velocities_[2] ? 0.0 : cmd_vel->twist.linear.z;
489 cmd_vel->twist.angular.x =
490 fabs(cmd_vel->twist.angular.x) < deadband_velocities_[3] ? 0.0 : cmd_vel->twist.angular.x;
491 cmd_vel->twist.angular.y =
492 fabs(cmd_vel->twist.angular.y) < deadband_velocities_[4] ? 0.0 : cmd_vel->twist.angular.y;
493 cmd_vel->twist.angular.z =
494 fabs(cmd_vel->twist.angular.z) < deadband_velocities_[5] ? 0.0 : cmd_vel->twist.angular.z;
496 smoothed_cmd_pub_->publish(std::move(cmd_vel));
500 const std::vector<rclcpp::Parameter> & parameters)
502 rcl_interfaces::msg::SetParametersResult result;
503 result.successful =
true;
504 for (
const auto & parameter : parameters) {
505 const auto & param_type = parameter.get_type();
506 const auto & param_name = parameter.get_name();
507 if (param_name.find(
'.') != std::string::npos) {
511 if (param_type == ParameterType::PARAMETER_DOUBLE) {
512 if (parameter.as_double() <= 0.0 && param_name ==
"smoothing_frequency") {
514 get_logger(),
"The value of smoothing_frequency is incorrectly set to %f, "
515 "it should be >0. Ignoring parameter update.",
516 parameter.as_double());
517 result.successful =
false;
519 }
else if (parameter.as_double() < 0.0) {
521 get_logger(),
"The value of parameter '%s' is incorrectly set to %f, "
522 "it should be >=0. Ignoring parameter update.",
523 param_name.c_str(), parameter.as_double());
524 result.successful =
false;
527 }
else if (param_type == ParameterType::PARAMETER_DOUBLE_ARRAY) {
528 size_t size = is_6dof_ ? 6 : 3;
529 if (parameter.as_double_array().size() != size) {
531 get_logger(),
"Invalid size of parameter %s. Must be size %ld",
532 param_name.c_str(), size);
533 result.successful =
false;
535 }
else if (param_name ==
"max_velocity" || param_name ==
"max_accel") {
536 for (
auto val : parameter.as_double_array()) {
539 get_logger(),
"The value of parameter '%s' is incorrectly set to %f, "
540 "it should be >=0. Ignoring parameter update.",
541 param_name.c_str(), val);
542 result.successful =
false;
546 }
else if (param_name ==
"min_velocity" || param_name ==
"max_decel") {
547 for (
auto val : parameter.as_double_array()) {
550 get_logger(),
"The value of parameter '%s' is incorrectly set to %f, "
551 "it should be <=0. Ignoring parameter update.",
552 param_name.c_str(), val);
553 result.successful =
false;
558 }
else if (param_type == ParameterType::PARAMETER_STRING) {
559 if (param_name ==
"feedback") {
560 if (parameter.as_string() !=
"OPEN_LOOP" && parameter.as_string() !=
"CLOSED_LOOP") {
563 "Invalid feedback_type, options are OPEN_LOOP and CLOSED_LOOP. "
564 "Ignoring parameter update.");
565 result.successful =
false;
576 std::lock_guard<std::mutex> lock(mutex_);
578 for (
const auto & parameter : parameters) {
579 const auto & param_type = parameter.get_type();
580 const auto & param_name = parameter.get_name();
581 if (param_name.find(
'.') != std::string::npos) {
585 if (param_type == ParameterType::PARAMETER_DOUBLE) {
586 if (param_name ==
"smoothing_frequency") {
587 smoothing_frequency_ = parameter.as_double();
593 double timer_duration_ms = 1000.0 / smoothing_frequency_;
595 std::chrono::milliseconds(
static_cast<int>(timer_duration_ms)),
597 }
else if (param_name ==
"velocity_timeout") {
598 velocity_timeout_ = rclcpp::Duration::from_seconds(parameter.as_double());
599 }
else if (param_name ==
"odom_duration") {
600 odom_duration_ = parameter.as_double();
602 std::make_unique<nav2_util::OdomSmoother>(
605 }
else if (param_type == ParameterType::PARAMETER_DOUBLE_ARRAY) {
606 if (param_name ==
"max_velocity") {
607 max_velocities_ = parameter.as_double_array();
608 }
else if (param_name ==
"min_velocity") {
609 min_velocities_ = parameter.as_double_array();
610 }
else if (param_name ==
"max_accel") {
611 max_accels_ = parameter.as_double_array();
612 }
else if (param_name ==
"max_decel") {
613 max_decels_ = parameter.as_double_array();
614 }
else if (param_name ==
"deadband_velocity") {
615 deadband_velocities_ = parameter.as_double_array();
617 }
else if (param_type == ParameterType::PARAMETER_STRING) {
618 if (param_name ==
"feedback") {
619 if (parameter.as_string() ==
"OPEN_LOOP") {
621 odom_smoother_.reset();
622 }
else if (parameter.as_string() ==
"CLOSED_LOOP") {
625 std::make_unique<nav2_util::OdomSmoother>(
635 #include "rclcpp_components/register_node_macro.hpp"
void destroyBond()
Destroy bond connection to lifecycle manager.
nav2::LifecycleNode::SharedPtr shared_from_this()
Get a shared pointer of this.
rclcpp::GenericTimer< CallbackT >::SharedPtr create_timer(std::chrono::duration< DurationRepT, DurationT > period, CallbackT callback, rclcpp::CallbackGroup::SharedPtr group=nullptr)
Create a sim-time-aware timer for Nav2 lifecycle nodes.
void createBond()
Create bond connection to lifecycle manager.
This class that smooths cmd_vel velocities for robot bases.
void updateParametersCallback(const std::vector< rclcpp::Parameter > ¶meters)
Apply parameter updates after validation This callback is executed when parameters have been successf...
nav2::CallbackReturn on_shutdown(const rclcpp_lifecycle::State &state) override
Called when in Shutdown state.
rcl_interfaces::msg::SetParametersResult validateParameterUpdatesCallback(const std::vector< rclcpp::Parameter > ¶meters)
Validate incoming parameter updates before applying them. This callback is triggered when one or more...
~VelocitySmoother()
Destructor for nav2_velocity_smoother::VelocitySmoother.
nav2::CallbackReturn on_cleanup(const rclcpp_lifecycle::State &state) override
Calls clean up states and resets member variables.
nav2::CallbackReturn on_activate(const rclcpp_lifecycle::State &state) override
Activates member variables.
nav2::CallbackReturn on_configure(const rclcpp_lifecycle::State &state) override
Configures parameters and member variables.
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.
nav2::CallbackReturn on_deactivate(const rclcpp_lifecycle::State &state) override
Deactivates member variables.
void smootherTimer()
Main worker timer function.
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.
void inputCommandCallback(const geometry_msgs::msg::Twist::ConstSharedPtr &msg)
Callback for incoming velocity commands.