Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
speed_filter.cpp
1 /*********************************************************************
2  *
3  * Software License Agreement (BSD License)
4  *
5  * Copyright (c) 2020 Samsung Research Russia
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * * Redistributions of source code must retain the above copyright
13  * notice, this list of conditions and the following disclaimer.
14  * * Redistributions in binary form must reproduce the above
15  * copyright notice, this list of conditions and the following
16  * disclaimer in the documentation and/or other materials provided
17  * with the distribution.
18  * * Neither the name of the <ORGANIZATION> nor the names of its
19  * contributors may be used to endorse or promote products derived
20  * from this software without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33  * POSSIBILITY OF SUCH DAMAGE.
34  *
35  * Author: Alexey Merzlyakov
36  *********************************************************************/
37 
38 #include "nav2_costmap_2d/costmap_filters/speed_filter.hpp"
39 
40 #include <cmath>
41 #include <limits>
42 #include <utility>
43 #include <memory>
44 #include <string>
45 
46 #include "nav2_costmap_2d/costmap_filters/filter_values.hpp"
47 #include "nav2_util/occ_grid_utils.hpp"
48 
49 namespace nav2_costmap_2d
50 {
51 
53 : filter_info_sub_(nullptr), mask_sub_(nullptr),
54  speed_limit_pub_(nullptr), filter_mask_(nullptr), global_frame_(""),
55  speed_limit_(NO_SPEED_LIMIT), speed_limit_prev_(NO_SPEED_LIMIT)
56 {
57 }
58 
60  const std::string & filter_info_topic)
61 {
62  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
63 
64  nav2::LifecycleNode::SharedPtr node = node_.lock();
65  if (!node) {
66  throw std::runtime_error{"Failed to lock node"};
67  }
68 
69  // Declare "speed_limit_topic" parameter specific to SpeedFilter only
70  std::string speed_limit_topic = node->declare_or_get_parameter(name_ + "." + "speed_limit_topic",
71  std::string("speed_limit"));
72  speed_limit_topic = joinWithParentNamespace(speed_limit_topic);
73 
74  // Path lookahead parameters
75  enable_path_lookahead_ = node->declare_or_get_parameter(
76  name_ + "." + "enable_path_lookahead", false);
77  max_decel_ = node->declare_or_get_parameter(
78  name_ + "." + "max_decel", -0.5);
79  min_lookahead_ = node->declare_or_get_parameter(
80  name_ + "." + "min_lookahead", 0.3);
81  max_lookahead_ = node->declare_or_get_parameter(
82  name_ + "." + "max_lookahead", 5.0);
83  std::string path_topic = node->declare_or_get_parameter(
84  name_ + "." + "path_topic", std::string("plan"));
85  std::string odom_topic = node->declare_or_get_parameter(
86  name_ + "." + "odom_topic", std::string("odom"));
87 
88  // Check params
89  if (enable_path_lookahead_) {
90  if (max_decel_ >= 0.0) {
91  RCLCPP_WARN(
92  logger_,
93  "SpeedFilter: max_decel should be negative,"
94  "lookahead distance will be set to max_lookahead instead");
95  }
96  if (min_lookahead_ < 0.0) {
97  RCLCPP_WARN(
98  logger_,
99  "SpeedFilter: min_lookahead = %f is negative,"
100  "clamping to 0.0m", min_lookahead_);
101  min_lookahead_ = 0.0;
102  }
103  if (max_lookahead_ < min_lookahead_) {
104  RCLCPP_WARN(
105  logger_,
106  "SpeedFilter: max_lookahead = %f is less than min_lookahead = %f,"
107  "clamping to min_lookahead.",
108  max_lookahead_, min_lookahead_);
109  max_lookahead_ = min_lookahead_;
110  }
111  }
112 
113  filter_info_topic_ = joinWithParentNamespace(filter_info_topic);
114  // Setting new costmap filter info subscriber
115  RCLCPP_INFO(
116  logger_,
117  "SpeedFilter: Subscribing to \"%s\" topic for filter info...",
118  filter_info_topic_.c_str());
119  filter_info_sub_ = node->create_subscription<nav2_msgs::msg::CostmapFilterInfo>(
121  std::bind(&SpeedFilter::filterInfoCallback, this, std::placeholders::_1),
123 
124  // Get global frame required for speed limit publisher
125  global_frame_ = layered_costmap_->getGlobalFrameID();
126 
127  // Create new speed limit publisher
128  speed_limit_pub_ = node->create_publisher<nav2_msgs::msg::SpeedLimit>(
129  speed_limit_topic);
130  speed_limit_pub_->on_activate();
131 
132  // Path subscriptions and odom smoother if lookahead enabled
133  if (enable_path_lookahead_) {
134  std::string resolved_path_topic = joinWithParentNamespace(path_topic);
135  RCLCPP_INFO(
136  logger_,
137  "SpeedFilter: Path lookahead enabled. Subscribing to \"%s\" topic for path...",
138  resolved_path_topic.c_str());
139  path_sub_ = node->create_subscription<nav_msgs::msg::Path>(
140  resolved_path_topic,
141  std::bind(&SpeedFilter::pathCallback, this, std::placeholders::_1));
142 
143  odom_smoother_ = std::make_shared<nav2_util::OdomSmoother>(
144  node, 0.3, odom_topic);
145 
146  lookahead_pub_ = node->create_publisher<geometry_msgs::msg::PointStamped>(
147  name_ + "/lookahead_point");
148  lookahead_pub_->on_activate();
149  }
150 
151  // Reset speed conversion states
152  base_ = BASE_DEFAULT;
153  multiplier_ = MULTIPLIER_DEFAULT;
154  percentage_ = false;
155 
156  // Reset path lookahead states
157  held_lookahead_dist_ = 0.0;
158  lookahead_start_idx_ = 0;
159 }
160 
162  const nav2_msgs::msg::CostmapFilterInfo::ConstSharedPtr & msg)
163 {
164  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
165 
166  nav2::LifecycleNode::SharedPtr node = node_.lock();
167  if (!node) {
168  throw std::runtime_error{"Failed to lock node"};
169  }
170 
171  if (!mask_sub_) {
172  RCLCPP_INFO(
173  logger_,
174  "SpeedFilter: Received filter info from %s topic.", filter_info_topic_.c_str());
175  } else {
176  RCLCPP_WARN(
177  logger_,
178  "SpeedFilter: New costmap filter info arrived from %s topic. Updating old filter info.",
179  filter_info_topic_.c_str());
180  // Resetting previous subscriber each time when new costmap filter information arrives
181  mask_sub_.reset();
182  }
183 
184  // Set base_/multiplier_ or use speed limit in % of maximum speed
185  base_ = msg->base;
186  multiplier_ = msg->multiplier;
187  if (msg->type == SPEED_FILTER_PERCENT) {
188  // Using speed limit in % of maximum speed
189  percentage_ = true;
190  RCLCPP_INFO(
191  logger_,
192  "SpeedFilter: Using expressed in a percent from maximum speed"
193  "speed_limit = %f + filter_mask_data * %f",
194  base_, multiplier_);
195  } else if (msg->type == SPEED_FILTER_ABSOLUTE) {
196  // Using speed limit in m/s
197  percentage_ = false;
198  RCLCPP_INFO(
199  logger_,
200  "SpeedFilter: Using absolute speed_limit = %f + filter_mask_data * %f",
201  base_, multiplier_);
202  } else {
203  RCLCPP_ERROR(logger_, "SpeedFilter: Mode is not supported");
204  return;
205  }
206 
207  mask_topic_ = joinWithParentNamespace(msg->filter_mask_topic);
208 
209  // Setting new filter mask subscriber
210  RCLCPP_INFO(
211  logger_,
212  "SpeedFilter: Subscribing to \"%s\" topic for filter mask...",
213  mask_topic_.c_str());
214  mask_sub_ = node->create_subscription<nav_msgs::msg::OccupancyGrid>(
215  mask_topic_,
216  std::bind(&SpeedFilter::maskCallback, this, std::placeholders::_1),
218 }
219 
221  const nav_msgs::msg::OccupancyGrid::ConstSharedPtr & msg)
222 {
223  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
224 
225  if (!filter_mask_) {
226  RCLCPP_INFO(
227  logger_,
228  "SpeedFilter: Received filter mask from %s topic.", mask_topic_.c_str());
229  } else {
230  RCLCPP_WARN(
231  logger_,
232  "SpeedFilter: New filter mask arrived from %s topic. Updating old filter mask.",
233  mask_topic_.c_str());
234  filter_mask_.reset();
235  }
236 
237  filter_mask_ = msg;
238 }
239 
241  const nav_msgs::msg::Path::ConstSharedPtr & msg)
242 {
243  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
244 
245  current_path_ = msg;
246 
247  // Reset cached start index when new path is received
248  lookahead_start_idx_ = 0;
249 }
250 
252  const geometry_msgs::msg::Pose & pose,
253  double & speed_limit)
254 {
255  geometry_msgs::msg::Pose mask_pose; // robot coordinates in mask frame
256 
257  // Transforming robot pose from current layer frame to mask frame
258  if (!transformPose(global_frame_, pose, filter_mask_->header.frame_id, mask_pose)) {
259  return false;
260  }
261 
262  // Converting mask_pose robot position to filter_mask_ indexes (mask_robot_i, mask_robot_j)
263  unsigned int mask_robot_i, mask_robot_j;
264  if (!nav2_util::worldToMap(
265  filter_mask_, mask_pose.position.x, mask_pose.position.y,
266  mask_robot_i, mask_robot_j))
267  {
268  return false;
269  }
270 
271  // Getting filter_mask data from cell where the robot placed and
272  // calculating speed limit value
273  int8_t speed_mask_data = getMaskData(filter_mask_, mask_robot_i, mask_robot_j);
274  if (speed_mask_data == SPEED_MASK_NO_LIMIT) {
275  // Corresponding filter mask cell is free.
276  // Setting no speed limit there.
277  speed_limit = NO_SPEED_LIMIT;
278  } else if (speed_mask_data == SPEED_MASK_UNKNOWN) {
279  // Corresponding filter mask cell is unknown.
280  // Do nothing.
281  RCLCPP_ERROR(
282  logger_,
283  "SpeedFilter: Found unknown cell in filter_mask[%i, %i], "
284  "which is invalid for this kind of filter",
285  mask_robot_i, mask_robot_j);
286  return false;
287  } else {
288  // Normal case: speed_mask_data in range of [1..100]
289  speed_limit = speed_mask_data * multiplier_ + base_;
290  if (percentage_) {
291  if (speed_limit < 0.0 || speed_limit > 100.0) {
292  RCLCPP_WARN(
293  logger_,
294  "SpeedFilter: Speed limit in filter_mask[%i, %i] is %f%%, "
295  "out of bounds of [0, 100]. Setting it to no-limit value.",
296  mask_robot_i, mask_robot_j, speed_limit);
297  speed_limit = NO_SPEED_LIMIT;
298  }
299  } else {
300  if (speed_limit < 0.0) {
301  RCLCPP_WARN(
302  logger_,
303  "SpeedFilter: Speed limit in filter_mask[%i, %i] is less than 0 m/s, "
304  "which can not be true. Setting it to no-limit value.",
305  mask_robot_i, mask_robot_j);
306  speed_limit = NO_SPEED_LIMIT;
307  }
308  }
309  }
310  return true;
311 }
312 
314  const geometry_msgs::msg::Pose & robot_pose,
315  double lookahead_dist,
316  double & speed_limit)
317 {
318  double min_speed_limit = std::numeric_limits<double>::max();
319 
320  // Release the hold by default, a stricter limit ahead re-arms it
321  held_lookahead_dist_ = 0.0;
322 
323  // Lookahead endpoint for visualization
324  auto lookahead_point_msg = std::make_unique<geometry_msgs::msg::PointStamped>();
325  lookahead_point_msg->header.frame_id = global_frame_;
326  lookahead_point_msg->header.stamp = clock_->now();
327  lookahead_point_msg->point = robot_pose.position;
328 
329  // Transform path if not in the global frame
330  nav_msgs::msg::Path transformed_path;
331  if(current_path_->header.frame_id != global_frame_) {
332  if(!nav2_util::transformPathInTargetFrame(*current_path_, transformed_path, *tf_,
333  global_frame_))
334  {
335  RCLCPP_ERROR_THROTTLE(logger_, *(clock_), 5000,
336  "SpeedFilter: Failed to transform path to global frame, "
337  "no speed limit will be published");
338  return false;
339  }
340  } else {
341  transformed_path = *current_path_;
342  }
343 
344  const auto & poses = transformed_path.poses;
345  const size_t pose_search_start =
346  (lookahead_start_idx_ < poses.size()) ? lookahead_start_idx_ : 0;
347 
348  // Update cached start index
349  lookahead_start_idx_ = nav2_util::distance_from_path(
350  transformed_path, robot_pose, pose_search_start).closest_segment_index;
351 
352  // Check robot's current pose
353  double limit_at_robot_pose = NO_SPEED_LIMIT;
354  if (!getSpeedLimitAtPose(robot_pose, limit_at_robot_pose)) {
355  // Pose mapped outside mask or transform failed
356  RCLCPP_ERROR_THROTTLE(logger_, *(clock_), 5000,
357  "SpeedFilter: Failed to get speed limit at robot pose");
358  return false;
359  }
360 
361  if (limit_at_robot_pose != NO_SPEED_LIMIT) {
362  min_speed_limit = limit_at_robot_pose;
363  }
364 
365  // Walk poses from the lookahead start index forward, sampling the speed limit at each pose.
366  double dist_along_path = 0.0;
367  for (size_t i = lookahead_start_idx_; i < poses.size(); ++i) {
368  if (dist_along_path > lookahead_dist) {
369  break;
370  }
371 
372  // Update lookahead endpoint for visualization
373  lookahead_point_msg->point = poses[i].pose.position;
374 
375  double sampled_speed_limit = NO_SPEED_LIMIT;
376  if (getSpeedLimitAtPose(poses[i].pose, sampled_speed_limit) &&
377  sampled_speed_limit != NO_SPEED_LIMIT)
378  {
379  min_speed_limit = std::min(min_speed_limit, sampled_speed_limit);
380  }
381 
382  // Accumulate distance to the next pose for the following iteration's check
383  if (i + 1 < poses.size()) {
384  dist_along_path += nav2_util::geometry_utils::euclidean_distance(
385  poses[i].pose.position, poses[i + 1].pose.position);
386  }
387  }
388 
389  if (lookahead_pub_ && lookahead_pub_->get_subscription_count() > 0) {
390  lookahead_pub_->publish(std::move(lookahead_point_msg));
391  }
392 
393  // No limit found anywhere along the lookahead, fall back to no-limit
394  if (min_speed_limit == std::numeric_limits<double>::max()) {
395  min_speed_limit = NO_SPEED_LIMIT;
396  }
397 
398  // Hold the lookahead distance if upcoming speed limit is different from the current one
399  if (limit_at_robot_pose != min_speed_limit) {
400  held_lookahead_dist_ = lookahead_dist;
401  }
402 
403  speed_limit = min_speed_limit;
404  return true;
405 }
406 
408  nav2_costmap_2d::Costmap2D & /*master_grid*/,
409  int /*min_i*/, int /*min_j*/, int /*max_i*/, int /*max_j*/,
410  const geometry_msgs::msg::Pose & pose)
411 {
412  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
413 
414  if (!filter_mask_) {
415  // Show warning message every 2 seconds to not litter an output
416  RCLCPP_WARN_THROTTLE(
417  logger_, *(clock_), 2000,
418  "SpeedFilter: Filter mask was not received");
419  return;
420  }
421 
422  // Decide path lookahead vs just checking at robot pose.
423  // Path lookahead requires a non-empty path received
424  const bool use_path_lookahead =
425  enable_path_lookahead_ &&
426  current_path_ && !current_path_->poses.empty();
427 
428  if (use_path_lookahead) {
429  const auto twist = odom_smoother_->getTwist();
430  const double linear_vel = std::abs(twist.linear.x);
431 
432  // Calculate lookahead distance at current velocity
433  double d_lookahead = max_lookahead_;
434  if (max_decel_ < 0.0) {
435  d_lookahead = (linear_vel * linear_vel) / (2.0 * std::abs(max_decel_));
436  d_lookahead = std::clamp(d_lookahead, min_lookahead_, max_lookahead_);
437 
438  // If lookahead distance is being held, don't let it shrink below the held value
439  d_lookahead = std::max(d_lookahead, held_lookahead_dist_);
440  }
441 
442  if (!getSpeedLimitFromLookahead(pose, d_lookahead, speed_limit_)) {
443  RCLCPP_ERROR(logger_, "SpeedFilter: Failed to get speed limit from lookahead");
444  return;
445  }
446  } else {
447  if (!getSpeedLimitAtPose(pose, speed_limit_)) {
448  RCLCPP_ERROR(logger_, "SpeedFilter: Failed to get speed limit at pose");
449  return;
450  }
451  }
452 
453  if (speed_limit_ != speed_limit_prev_) {
454  if (speed_limit_ != NO_SPEED_LIMIT) {
455  RCLCPP_DEBUG(logger_, "SpeedFilter: Speed limit is set to %f", speed_limit_);
456  } else {
457  RCLCPP_DEBUG(logger_, "SpeedFilter: Speed limit is set to its default value");
458  }
459 
460  // Forming and publishing new SpeedLimit message
461  std::unique_ptr<nav2_msgs::msg::SpeedLimit> msg =
462  std::make_unique<nav2_msgs::msg::SpeedLimit>();
463  msg->header.frame_id = global_frame_;
464  msg->header.stamp = clock_->now();
465  msg->percentage = percentage_;
466  msg->speed_limit = speed_limit_;
467  speed_limit_pub_->publish(std::move(msg));
468 
469  speed_limit_prev_ = speed_limit_;
470  }
471 }
472 
474 {
475  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
476 
477  filter_info_sub_.reset();
478  mask_sub_.reset();
479  if (speed_limit_pub_) {
480  speed_limit_pub_->on_deactivate();
481  speed_limit_pub_.reset();
482  }
483  if (lookahead_pub_) {
484  lookahead_pub_->on_deactivate();
485  lookahead_pub_.reset();
486  }
487 }
488 
490 {
491  std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
492 
493  if (filter_mask_) {
494  return true;
495  }
496  return false;
497 }
498 
499 } // namespace nav2_costmap_2d
500 
501 #include "pluginlib/class_list_macros.hpp"
A QoS profile for latched, reliable topics with a history of 10 messages.
A 2D costmap provides a mapping between points in the world and their associated "costs".
Definition: costmap_2d.hpp:69
int8_t getMaskData(nav_msgs::msg::OccupancyGrid::ConstSharedPtr filter_mask, const unsigned int mx, const unsigned int my) const
Get the data of a cell in the filter mask.
bool transformPose(const std::string global_frame, const geometry_msgs::msg::Pose &global_pose, const std::string mask_frame, geometry_msgs::msg::Pose &mask_pose) const
: Transforms robot pose from current layer frame to mask frame
std::string filter_info_topic_
: Name of costmap filter info topic
std::string mask_topic_
: Name of filter mask topic
mutex_t * getMutex()
: returns pointer to a mutex
Abstract class for layered costmap plugin implementations.
Definition: layer.hpp:60
std::string joinWithParentNamespace(const std::string &topic)
Definition: layer.cpp:83
Reads in a speed restriction mask and enables a robot to dynamically adjust speed based on pose in ma...
void resetFilter() override
Reset the costmap filter / topic / info.
bool getSpeedLimitFromLookahead(const geometry_msgs::msg::Pose &robot_pose, double lookahead_dist, double &speed_limit)
Get the speed limit from the path lookahead.
void filterInfoCallback(const nav2_msgs::msg::CostmapFilterInfo::ConstSharedPtr &msg)
Callback for the filter information.
bool getSpeedLimitAtPose(const geometry_msgs::msg::Pose &pose, double &speed_limit)
Look up the speed limit at a single pose in the costmap's global frame.
void process(nav2_costmap_2d::Costmap2D &master_grid, int min_i, int min_j, int max_i, int max_j, const geometry_msgs::msg::Pose &pose) override
Process the keepout layer at the current pose / bounds / grid.
void pathCallback(const nav_msgs::msg::Path::ConstSharedPtr &msg)
Callback for the planned path used by path lookahead.
void maskCallback(const nav_msgs::msg::OccupancyGrid::ConstSharedPtr &msg)
Callback for the filter mask.
void initializeFilter(const std::string &filter_info_topic) override
Initialize the filter and subscribe to the info topic.
bool isActive()
If this filter is active.