Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
collision_detector_node.cpp
1 // Copyright (c) 2022 Samsung R&D Institute Russia
2 // Copyright (c) 2023 Pixel Robotics GmbH
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 "nav2_collision_monitor/collision_detector_node.hpp"
17 
18 #include <exception>
19 #include <utility>
20 #include <functional>
21 
22 #include "nav2_ros_common/node_utils.hpp"
23 
24 using namespace std::chrono_literals;
25 
26 namespace nav2_collision_monitor
27 {
28 
29 CollisionDetector::CollisionDetector(const rclcpp::NodeOptions & options)
30 : nav2::LifecycleNode("collision_detector", options)
31 {
32 }
33 
35 {
36  polygons_.clear();
37  sources_.clear();
38 }
39 
40 nav2::CallbackReturn
41 CollisionDetector::on_configure(const rclcpp_lifecycle::State & state)
42 {
43  RCLCPP_INFO(get_logger(), "Configuring");
44 
45  // Transform buffer and listener initialization
46  tf_buffer_ = nav2::create_transform_buffer(this);
47  tf_listener_ = nav2::create_transform_listener(*tf_buffer_, this, true);
48 
49  state_pub_ = this->create_publisher<nav2_msgs::msg::CollisionDetectorState>(
50  "collision_detector_state");
51 
52  collision_points_marker_pub_ = this->create_publisher<visualization_msgs::msg::MarkerArray>(
53  "~/collision_points_marker");
54 
55  triggering_points_pub_ = this->create_publisher<visualization_msgs::msg::MarkerArray>(
56  "~/triggering_points");
57 
58  // Obtaining ROS parameters
59  if (!getParameters()) {
60  on_cleanup(state);
61  return nav2::CallbackReturn::FAILURE;
62  }
63 
64  return nav2::CallbackReturn::SUCCESS;
65 }
66 
67 nav2::CallbackReturn
68 CollisionDetector::on_activate(const rclcpp_lifecycle::State & /*state*/)
69 {
70  RCLCPP_INFO(get_logger(), "Activating");
71 
72  // Activating lifecycle publisher
73  state_pub_->on_activate();
74  collision_points_marker_pub_->on_activate();
75  triggering_points_pub_->on_activate();
76 
77  // Activating polygons
78  for (std::shared_ptr<Polygon> polygon : polygons_) {
79  polygon->activate();
80  }
81 
82  // Activating sources
83  for (std::shared_ptr<Source> source : sources_) {
84  source->activate();
85  }
86 
87  // Creating timer
88  timer_ = this->create_timer(
89  std::chrono::duration<double>{1.0 / frequency_},
90  std::bind(&CollisionDetector::process, this));
91 
92  // Creating bond connection
93  createBond();
94 
95  return nav2::CallbackReturn::SUCCESS;
96 }
97 
98 nav2::CallbackReturn
99 CollisionDetector::on_deactivate(const rclcpp_lifecycle::State & /*state*/)
100 {
101  RCLCPP_INFO(get_logger(), "Deactivating");
102 
103  // Resetting timer
104  timer_.reset();
105 
106  // Deactivating lifecycle publishers
107  state_pub_->on_deactivate();
108  collision_points_marker_pub_->on_deactivate();
109  triggering_points_pub_->on_deactivate();
110 
111  // Deactivating polygons
112  for (std::shared_ptr<Polygon> polygon : polygons_) {
113  polygon->deactivate();
114  }
115 
116  // Deactivating sources
117  for (std::shared_ptr<Source> source : sources_) {
118  source->deactivate();
119  }
120 
121  // Destroying bond connection
122  destroyBond();
123 
124  return nav2::CallbackReturn::SUCCESS;
125 }
126 
127 nav2::CallbackReturn
128 CollisionDetector::on_cleanup(const rclcpp_lifecycle::State & /*state*/)
129 {
130  RCLCPP_INFO(get_logger(), "Cleaning up");
131 
132  state_pub_.reset();
134  triggering_points_pub_.reset();
135 
136  polygons_.clear();
137  sources_.clear();
138 
139  tf_listener_.reset();
140  tf_buffer_.reset();
141 
142  return nav2::CallbackReturn::SUCCESS;
143 }
144 
145 nav2::CallbackReturn
146 CollisionDetector::on_shutdown(const rclcpp_lifecycle::State & /*state*/)
147 {
148  RCLCPP_INFO(get_logger(), "Shutting down");
149  return nav2::CallbackReturn::SUCCESS;
150 }
151 
153 {
154  std::string odom_frame_id;
155  tf2::Duration transform_tolerance;
156  rclcpp::Duration source_timeout(2.0, 0.0);
157 
158  auto node = shared_from_this();
159 
160  frequency_ = node->declare_or_get_parameter("frequency", 10.0);
161  base_frame_id_ = node->declare_or_get_parameter("base_frame_id", std::string("base_footprint"));
162  odom_frame_id = node->declare_or_get_parameter("odom_frame_id", std::string("odom"));
163  transform_tolerance = tf2::durationFromSec(
164  node->declare_or_get_parameter("transform_tolerance", 0.1));
165  source_timeout = rclcpp::Duration::from_seconds(
166  node->declare_or_get_parameter("source_timeout", 2.0));
167  const bool base_shift_correction = node->declare_or_get_parameter("base_shift_correction", true);
168  collision_points_marker_3d_ = node->declare_or_get_parameter("collision_points_marker_3d", false);
169 
170  if (!configureSources(
171  base_frame_id_, odom_frame_id, transform_tolerance, source_timeout,
172  base_shift_correction))
173  {
174  return false;
175  }
176 
177  if (!configurePolygons(base_frame_id_, transform_tolerance)) {
178  return false;
179  }
180 
181  return true;
182 }
183 
185  const std::string & base_frame_id,
186  const tf2::Duration & transform_tolerance)
187 {
188  try {
189  auto node = shared_from_this();
190 
191  // Leave it to be not initialized: to intentionally cause an error if it will not set
192  std::vector<std::string> polygon_names =
193  node->declare_or_get_parameter<std::vector<std::string>>("polygons");
194  for (std::string polygon_name : polygon_names) {
195  // Leave it not initialized: the will cause an error if it will not set
196  const std::string polygon_type =
197  node->declare_or_get_parameter<std::string>(polygon_name + ".type");
198 
199  if (polygon_type == "polygon") {
200  polygons_.push_back(
201  std::make_shared<Polygon>(
202  node, polygon_name, tf_buffer_, base_frame_id, transform_tolerance));
203  } else if (polygon_type == "circle") {
204  polygons_.push_back(
205  std::make_shared<Circle>(
206  node, polygon_name, tf_buffer_, base_frame_id, transform_tolerance));
207  } else if (polygon_type == "velocity_polygon") {
208  polygons_.push_back(
209  std::make_shared<VelocityPolygon>(
210  node, polygon_name, tf_buffer_, base_frame_id, transform_tolerance));
211  } else { // Error if something else
212  RCLCPP_ERROR(
213  get_logger(),
214  "[%s]: Unknown polygon type: %s",
215  polygon_name.c_str(), polygon_type.c_str());
216  return false;
217  }
218 
219  // Configure last added polygon
220  if (!polygons_.back()->configure()) {
221  return false;
222  }
223 
224  // warn if the added polygon's action_type_ is not different than "none"
225  auto action_type = polygons_.back()->getActionType();
226  if (action_type != DO_NOTHING) {
227  RCLCPP_ERROR(
228  get_logger(),
229  "[%s]: The action_type of the polygon is different than \"none\" which is "
230  "not supported in the collision detector.",
231  polygon_name.c_str());
232  return false;
233  }
234  }
235  } catch (const std::exception & ex) {
236  RCLCPP_ERROR(get_logger(), "Error while getting parameters: %s", ex.what());
237  return false;
238  }
239 
240  return true;
241 }
242 
244  const std::string & base_frame_id,
245  const std::string & odom_frame_id,
246  const tf2::Duration & transform_tolerance,
247  const rclcpp::Duration & source_timeout,
248  const bool base_shift_correction)
249 {
250  try {
251  auto node = shared_from_this();
252 
253  // Leave it to be not initialized to intentionally cause an error if it will not set
254  std::vector<std::string> source_names =
255  node->declare_or_get_parameter<std::vector<std::string>>("observation_sources");
256  for (std::string source_name : source_names) {
257  const std::string source_type = node->declare_or_get_parameter(
258  source_name + ".type", std::string("scan")); // Laser scanner by default
259 
260  if (source_type == "scan") {
261  std::shared_ptr<Scan> s = std::make_shared<Scan>(
262  node, source_name, tf_buffer_, base_frame_id, odom_frame_id,
263  transform_tolerance, source_timeout, base_shift_correction);
264 
265  if (!s->configure()) {
266  return false;
267  }
268 
269  sources_.push_back(s);
270  } else if (source_type == "pointcloud") {
271  std::shared_ptr<PointCloud> p = std::make_shared<PointCloud>(
272  node, source_name, tf_buffer_, base_frame_id, odom_frame_id,
273  transform_tolerance, source_timeout, base_shift_correction);
274 
275  if (!p->configure()) {
276  return false;
277  }
278 
279  sources_.push_back(p);
280  } else if (source_type == "range") {
281  std::shared_ptr<Range> r = std::make_shared<Range>(
282  node, source_name, tf_buffer_, base_frame_id, odom_frame_id,
283  transform_tolerance, source_timeout, base_shift_correction);
284 
285  if (!r->configure()) {
286  return false;
287  }
288 
289  sources_.push_back(r);
290  } else if (source_type == "polygon") {
291  std::shared_ptr<PolygonSource> ps = std::make_shared<PolygonSource>(
292  node, source_name, tf_buffer_, base_frame_id, odom_frame_id,
293  transform_tolerance, source_timeout, base_shift_correction);
294  if (!ps->configure()) {
295  return false;
296  }
297 
298  sources_.push_back(ps);
299  } else if (source_type == "costmap") {
300  auto src = std::make_shared<CostmapSource>(
301  node, source_name, tf_buffer_, base_frame_id, odom_frame_id,
302  transform_tolerance, source_timeout, base_shift_correction);
303 
304  if (!src->configure()) {
305  return false;
306  }
307 
308  sources_.push_back(src);
309  } else { // Error if something else
310  RCLCPP_ERROR(
311  get_logger(),
312  "[%s]: Unknown source type: %s",
313  source_name.c_str(), source_type.c_str());
314  return false;
315  }
316  }
317  } catch (const std::exception & ex) {
318  RCLCPP_ERROR(get_logger(), "Error while getting parameters: %s", ex.what());
319  return false;
320  }
321 
322  return true;
323 }
324 
326 {
327  // Current timestamp for all inner routines prolongation
328  rclcpp::Time curr_time = this->now();
329 
330  // Points array collected from different data sources in a robot base frame
331  std::unordered_map<std::string, std::vector<Point>> sources_collision_points_map;
332 
333  std::unique_ptr<nav2_msgs::msg::CollisionDetectorState> state_msg =
334  std::make_unique<nav2_msgs::msg::CollisionDetectorState>();
335 
336  // Fill collision_points map from different data sources
337  for (std::shared_ptr<Source> source : sources_) {
338  auto iter = sources_collision_points_map.insert(
339  {source->getSourceName(), std::vector<Point>()});
340 
341  if (source->getEnabled()) {
342  if (!source->getData(curr_time, iter.first->second) &&
343  source->getSourceTimeout().seconds() != 0.0)
344  {
345  RCLCPP_WARN(
346  get_logger(),
347  "Invalid source %s detected."
348  " Either due to data not published yet, or to lack of new data received within the"
349  " sensor timeout, or if impossible to transform data to base frame",
350  source->getSourceName().c_str());
351  }
352  }
353  }
354 
355  if (collision_points_marker_pub_->get_subscription_count() > 0) {
356  // visualize collision points with markers
357  auto marker_array = std::make_unique<visualization_msgs::msg::MarkerArray>();
358  visualization_msgs::msg::Marker marker;
359  marker.header.frame_id = base_frame_id_;
360  marker.header.stamp = rclcpp::Time(0, 0);
361  marker.ns = "collision_points";
362  marker.id = 0;
363  marker.type = visualization_msgs::msg::Marker::POINTS;
364  marker.action = visualization_msgs::msg::Marker::ADD;
365  marker.scale.x = 0.02;
366  marker.scale.y = 0.02;
367  marker.color.r = 1.0;
368  marker.color.a = 1.0;
369  marker.lifetime = rclcpp::Duration(0, 0);
370  marker.frame_locked = true;
371 
372  for (const auto & [_, points] : sources_collision_points_map) {
373  for (const auto & point : points) {
374  geometry_msgs::msg::Point p;
375  p.x = point.x;
376  p.y = point.y;
377  p.z = collision_points_marker_3d_ ? point.z : 0.0;
378  marker.points.push_back(p);
379  }
380  }
381  marker_array->markers.push_back(marker);
382  collision_points_marker_pub_->publish(std::move(marker_array));
383  }
384 
385  // Per-polygon triggering points; populated only for polygons that detect.
386  std::unordered_map<std::string, std::vector<Point>> all_triggering_points;
387 
388  for (std::shared_ptr<Polygon> polygon : polygons_) {
389  if (!polygon->getEnabled()) {
390  continue;
391  }
392  std::vector<Point> triggering_points;
393  const bool detected = polygon->isTriggered(sources_collision_points_map, triggering_points);
394  state_msg->polygons.push_back(polygon->getName());
395  state_msg->detections.push_back(detected);
396  if (detected) {
397  all_triggering_points[polygon->getName()] = std::move(triggering_points);
398  }
399  }
400 
401  state_pub_->publish(std::move(state_msg));
402 
403  if (triggering_points_pub_->get_subscription_count() > 0) {
404  publishTriggeringPoints(all_triggering_points);
405  }
406 
407  // Publish polygons and exclusion zones for better visualization
409 }
410 
412  const std::unordered_map<std::string, std::vector<Point>> & all_triggering_points)
413 {
414  auto marker_array = std::make_unique<visualization_msgs::msg::MarkerArray>();
415 
416  // Clear markers from previous cycle.
417  visualization_msgs::msg::Marker clear;
418  clear.action = visualization_msgs::msg::Marker::DELETEALL;
419  marker_array->markers.push_back(clear);
420 
421  std::unordered_map<std::string, size_t> marker_index;
422  for (const auto & [polygon_name, points] : all_triggering_points) {
423  for (const auto & p : points) {
424  const std::string key = polygon_name + "/" + p.source;
425  auto [it, new_source] = marker_index.try_emplace(key, marker_array->markers.size());
426 
427  if (new_source) {
428  visualization_msgs::msg::Marker marker;
429  marker.header.frame_id = base_frame_id_;
430  marker.header.stamp = rclcpp::Time(0, 0);
431  marker.ns = key;
432  marker.id = 0;
433  marker.type = visualization_msgs::msg::Marker::POINTS;
434  marker.action = visualization_msgs::msg::Marker::ADD;
435  marker.scale.x = 0.05;
436  marker.scale.y = 0.05;
437  marker.color.r = 1.0f;
438  marker.color.g = 0.0f;
439  marker.color.b = 0.0f;
440  marker.color.a = 1.0f;
441  marker.lifetime = rclcpp::Duration(0, 0);
442  marker.frame_locked = true;
443  marker_array->markers.push_back(std::move(marker));
444  }
445 
446  geometry_msgs::msg::Point gp;
447  gp.x = p.x;
448  gp.y = p.y;
449  gp.z = p.z;
450  marker_array->markers[it->second].points.push_back(gp);
451  }
452  }
453 
454  triggering_points_pub_->publish(std::move(marker_array));
455 }
456 
458 {
459  for (std::shared_ptr<Polygon> polygon : polygons_) {
460  if (polygon->getEnabled()) {
461  polygon->publish();
462  }
463  }
464 
465  for (std::shared_ptr<Source> source : sources_) {
466  source->publishExclusionZones();
467  }
468 }
469 
470 } // namespace nav2_collision_monitor
471 
472 #include "rclcpp_components/register_node_macro.hpp"
473 
474 // Register the component with class_loader.
475 // This acts as a sort of entry point, allowing the component to be discoverable when its library
476 // is being loaded into a running process.
477 RCLCPP_COMPONENTS_REGISTER_NODE(nav2_collision_monitor::CollisionDetector)
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.
nav2::CallbackReturn on_activate(const rclcpp_lifecycle::State &state) override
: Activates LifecyclePublishers, polygons and main processor, creates bond connection
nav2::TransformBuffer::SharedPtr tf_buffer_
TF buffer.
nav2::Publisher< visualization_msgs::msg::MarkerArray >::SharedPtr collision_points_marker_pub_
Collision points marker publisher.
bool getParameters()
Supporting routine obtaining all ROS-parameters.
nav2::TransformListener::SharedPtr tf_listener_
TF listener.
bool configureSources(const std::string &base_frame_id, const std::string &odom_frame_id, const tf2::Duration &transform_tolerance, const rclcpp::Duration &source_timeout, const bool base_shift_correction)
Supporting routine creating and configuring all data sources.
nav2::CallbackReturn on_shutdown(const rclcpp_lifecycle::State &state) override
Called in shutdown state.
void publishTriggeringPoints(const std::unordered_map< std::string, std::vector< Point >> &all_triggering_points)
Publishes the points inside each detected polygon as markers, bucketed by polygon name and per-point ...
nav2::Publisher< nav2_msgs::msg::CollisionDetectorState >::SharedPtr state_pub_
collision monitor state publisher
~CollisionDetector()
Destructor for the nav2_collision_monitor::CollisionDetector.
nav2::CallbackReturn on_cleanup(const rclcpp_lifecycle::State &state) override
: Resets all subscribers/publishers, polygons/data sources arrays
std::vector< std::shared_ptr< Source > > sources_
Data sources array.
void publishVisualizations() const
Publishes all visualization topics (polygons and exclusion zones).
bool configurePolygons(const std::string &base_frame_id, const tf2::Duration &transform_tolerance)
Supporting routine creating and configuring all polygons.
rclcpp::TimerBase::SharedPtr timer_
timer that runs actions
nav2::Publisher< visualization_msgs::msg::MarkerArray >::SharedPtr triggering_points_pub_
Triggering points marker publisher (points inside each detected polygon)
nav2::CallbackReturn on_deactivate(const rclcpp_lifecycle::State &state) override
: Deactivates LifecyclePublishers, polygons and main processor, destroys bond connection
nav2::CallbackReturn on_configure(const rclcpp_lifecycle::State &state) override
: Initializes and obtains ROS-parameters, creates main subscribers and publishers,...
std::vector< std::shared_ptr< Polygon > > polygons_
Polygons array.
bool collision_points_marker_3d_
Whether to include z in the collision_points_marker.