Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
collision_monitor_node.cpp
1 // Copyright (c) 2022 Samsung R&D Institute Russia
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 "nav2_collision_monitor/collision_monitor_node.hpp"
16 
17 #include <algorithm>
18 #include <exception>
19 #include <utility>
20 #include <functional>
21 
22 #include "nav2_ros_common/node_utils.hpp"
23 #include "nav2_util/robot_utils.hpp"
24 
25 #include "nav2_collision_monitor/kinematics.hpp"
26 
27 using namespace std::placeholders;
28 
29 namespace nav2_collision_monitor
30 {
31 
32 CollisionMonitor::CollisionMonitor(const rclcpp::NodeOptions & options)
33 : nav2::LifecycleNode("collision_monitor", options),
34  enabled_{true}, process_active_(false),
35  robot_action_prev_{DO_NOTHING, {-1.0, -1.0, -1.0}, "", std::vector<Point>()},
36  stop_stamp_{0, 0, get_clock()->get_clock_type()}, stop_pub_timeout_(1.0, 0.0)
37 {
38 }
39 
40 CollisionMonitor::~CollisionMonitor()
41 {
42  polygons_.clear();
43  sources_.clear();
44 }
45 
46 nav2::CallbackReturn
47 CollisionMonitor::on_configure(const rclcpp_lifecycle::State & state)
48 {
49  RCLCPP_INFO(get_logger(), "Configuring");
50 
51  // Transform buffer and listener initialization
52  tf_buffer_ = nav2::create_transform_buffer(this);
53  tf_listener_ = nav2::create_transform_listener(*tf_buffer_, this, true);
54 
55  std::string cmd_vel_in_topic;
56  std::string cmd_vel_out_topic;
57  std::string state_topic;
58 
59  // Obtaining ROS parameters
60  if (!getParameters(cmd_vel_in_topic, cmd_vel_out_topic, state_topic)) {
61  on_cleanup(state);
62  return nav2::CallbackReturn::FAILURE;
63  }
64 
65  cmd_vel_in_sub_ = std::make_unique<nav2_util::TwistSubscriber>(
66  shared_from_this(),
67  cmd_vel_in_topic,
68  std::bind(&CollisionMonitor::cmdVelInCallbackUnstamped, this, std::placeholders::_1),
69  std::bind(&CollisionMonitor::cmdVelInCallbackStamped, this, std::placeholders::_1));
70 
71  auto node = shared_from_this();
72  cmd_vel_out_pub_ = std::make_unique<nav2_util::TwistPublisher>(node, cmd_vel_out_topic);
73 
74  if (!state_topic.empty()) {
75  state_pub_ = this->create_publisher<nav2_msgs::msg::CollisionMonitorState>(
76  state_topic);
77  }
78 
79  collision_points_marker_pub_ = this->create_publisher<visualization_msgs::msg::MarkerArray>(
80  "~/collision_points_marker");
81 
82  triggering_points_pub_ = this->create_publisher<visualization_msgs::msg::MarkerArray>(
83  "~/triggering_points");
84 
85  // Toggle service initialization
86  toggle_cm_service_ = create_service<nav2_msgs::srv::Toggle>(
87  "~/toggle",
88  std::bind(&CollisionMonitor::toggleCMServiceCallback, this, _1, _2, _3));
89 
90  bool use_realtime_priority = node->declare_or_get_parameter("use_realtime_priority", false);
91  if (use_realtime_priority) {
92  try {
93  nav2::setSoftRealTimePriority();
94  } catch (const std::runtime_error & e) {
95  RCLCPP_ERROR(get_logger(), "%s", e.what());
96  on_cleanup(state);
97  return nav2::CallbackReturn::FAILURE;
98  }
99  }
100 
101  enabled_ = node->declare_or_get_parameter("enabled", true);
102 
103  if (!enabled_) {
104  RCLCPP_WARN(get_logger(), "Collision monitor is disabled at startup.");
105  } else {
106  RCLCPP_INFO(get_logger(), "Collision monitor is enabled at startup.");
107  }
108 
109  return nav2::CallbackReturn::SUCCESS;
110 }
111 
112 nav2::CallbackReturn
113 CollisionMonitor::on_activate(const rclcpp_lifecycle::State & /*state*/)
114 {
115  RCLCPP_INFO(get_logger(), "Activating");
116 
117  // Activating lifecycle publisher
118  cmd_vel_out_pub_->on_activate();
119  if (state_pub_) {
120  state_pub_->on_activate();
121  }
122  collision_points_marker_pub_->on_activate();
123  triggering_points_pub_->on_activate();
124 
125  // Activating polygons
126  for (std::shared_ptr<Polygon> polygon : polygons_) {
127  polygon->activate();
128  }
129 
130  // Activating exclusion zone visualization publishers
131  for (std::shared_ptr<Source> source : sources_) {
132  source->activate();
133  }
134 
135  // Since polygons are being published when cmd_vel_in appears,
136  // we need to publish polygons and exclusion zones first time to display them at startup
137  publishVisualizations();
138 
139  // Activating main worker
140  process_active_ = true;
141 
142  // Creating bond connection
143  createBond();
144 
145  return nav2::CallbackReturn::SUCCESS;
146 }
147 
148 nav2::CallbackReturn
149 CollisionMonitor::on_deactivate(const rclcpp_lifecycle::State & /*state*/)
150 {
151  RCLCPP_INFO(get_logger(), "Deactivating");
152 
153  // Deactivating main worker
154  process_active_ = false;
155 
156  // Reset action type to default after worker deactivating
157  robot_action_prev_ = {DO_NOTHING, {-1.0, -1.0, -1.0}, "", std::vector<Point>()};
158 
159  // Deactivating polygons
160  for (std::shared_ptr<Polygon> polygon : polygons_) {
161  polygon->deactivate();
162  }
163 
164  // Deactivating exclusion zone visualization publishers
165  for (std::shared_ptr<Source> source : sources_) {
166  source->deactivate();
167  }
168 
169  // Deactivating lifecycle publishers
170  cmd_vel_out_pub_->on_deactivate();
171  if (state_pub_) {
172  state_pub_->on_deactivate();
173  }
174  collision_points_marker_pub_->on_deactivate();
175  triggering_points_pub_->on_deactivate();
176 
177  // Destroying bond connection
178  destroyBond();
179 
180  return nav2::CallbackReturn::SUCCESS;
181 }
182 
183 nav2::CallbackReturn
184 CollisionMonitor::on_cleanup(const rclcpp_lifecycle::State & /*state*/)
185 {
186  RCLCPP_INFO(get_logger(), "Cleaning up");
187 
188  cmd_vel_in_sub_.reset();
189  cmd_vel_out_pub_.reset();
190  state_pub_.reset();
191  collision_points_marker_pub_.reset();
192  triggering_points_pub_.reset();
193 
194  polygons_.clear();
195  sources_.clear();
196 
197  tf_listener_.reset();
198  tf_buffer_.reset();
199 
200  return nav2::CallbackReturn::SUCCESS;
201 }
202 
203 nav2::CallbackReturn
204 CollisionMonitor::on_shutdown(const rclcpp_lifecycle::State & /*state*/)
205 {
206  RCLCPP_INFO(get_logger(), "Shutting down");
207 
208  return nav2::CallbackReturn::SUCCESS;
209 }
210 
211 void CollisionMonitor::cmdVelInCallbackStamped(
212  const geometry_msgs::msg::TwistStamped::ConstSharedPtr & msg)
213 {
214  // If message contains NaN or Inf, ignore
215  if (!nav2_util::validateTwist(msg->twist)) {
216  RCLCPP_ERROR(get_logger(), "Velocity message contains NaNs or Infs! Ignoring as invalid!");
217  return;
218  }
219 
220  process({msg->twist.linear.x, msg->twist.linear.y, msg->twist.angular.z}, msg->header);
221 }
222 
223 void CollisionMonitor::cmdVelInCallbackUnstamped(
224  const geometry_msgs::msg::Twist::ConstSharedPtr & msg)
225 {
226  auto twist_stamped = std::make_shared<geometry_msgs::msg::TwistStamped>();
227  twist_stamped->twist = *msg;
228  cmdVelInCallbackStamped(twist_stamped);
229 }
230 
231 void CollisionMonitor::publishVelocity(
232  const Action & robot_action, const std_msgs::msg::Header & header)
233 {
234  if (robot_action.req_vel.isZero()) {
235  if (!robot_action_prev_.req_vel.isZero()) {
236  // Robot just stopped: saving stop timestamp and continue
237  stop_stamp_ = this->now();
238  } else if (this->now() - stop_stamp_ > stop_pub_timeout_) {
239  // More than stop_pub_timeout_ passed after robot has been stopped.
240  // Cease publishing output cmd_vel.
241  return;
242  }
243  }
244 
245  auto cmd_vel_out_msg = std::make_unique<geometry_msgs::msg::TwistStamped>();
246  cmd_vel_out_msg->header = header;
247  cmd_vel_out_msg->twist.linear.x = robot_action.req_vel.x;
248  cmd_vel_out_msg->twist.linear.y = robot_action.req_vel.y;
249  cmd_vel_out_msg->twist.angular.z = robot_action.req_vel.tw;
250  // linear.z, angular.x and angular.y will remain 0.0
251 
252  cmd_vel_out_pub_->publish(std::move(cmd_vel_out_msg));
253 }
254 
255 bool CollisionMonitor::getParameters(
256  std::string & cmd_vel_in_topic,
257  std::string & cmd_vel_out_topic,
258  std::string & state_topic)
259 {
260  std::string odom_frame_id;
261  tf2::Duration transform_tolerance;
262  rclcpp::Duration source_timeout(2.0, 0.0);
263 
264  auto node = shared_from_this();
265 
266  cmd_vel_in_topic = node->declare_or_get_parameter(
267  "cmd_vel_in_topic", std::string("cmd_vel_smoothed"));
268  cmd_vel_out_topic = node->declare_or_get_parameter(
269  "cmd_vel_out_topic", std::string("cmd_vel"));
270  state_topic = node->declare_or_get_parameter("state_topic", std::string(""));
271 
272  base_frame_id_ = node->declare_or_get_parameter(
273  "base_frame_id", std::string("base_footprint"));
274  odom_frame_id = node->declare_or_get_parameter("odom_frame_id", std::string("odom"));
275  transform_tolerance = tf2::durationFromSec(
276  node->declare_or_get_parameter("transform_tolerance", 0.1));
277  source_timeout = rclcpp::Duration::from_seconds(
278  node->declare_or_get_parameter("source_timeout", 2.0));
279  const bool base_shift_correction = node->declare_or_get_parameter("base_shift_correction", true);
280  collision_points_marker_3d_ = node->declare_or_get_parameter("collision_points_marker_3d", false);
281 
282  stop_pub_timeout_ = rclcpp::Duration::from_seconds(
283  node->declare_or_get_parameter("stop_pub_timeout", 1.0));
284 
285  if (
286  !configureSources(
287  base_frame_id_, odom_frame_id, transform_tolerance, source_timeout, base_shift_correction))
288  {
289  return false;
290  }
291 
292  if (!configurePolygons(base_frame_id_, transform_tolerance)) {
293  return false;
294  }
295 
296  return true;
297 }
298 
299 bool CollisionMonitor::configurePolygons(
300  const std::string & base_frame_id,
301  const tf2::Duration & transform_tolerance)
302 {
303  try {
304  auto node = shared_from_this();
305 
306  // Leave it to be not initialized: to intentionally cause an error if it will not set
307  std::vector<std::string> polygon_names =
308  node->declare_or_get_parameter<std::vector<std::string>>("polygons");
309  for (std::string polygon_name : polygon_names) {
310  // Leave it not initialized: the will cause an error if it will not set
311  const std::string polygon_type =
312  node->declare_or_get_parameter<std::string>(polygon_name + ".type");
313 
314  if (polygon_type == "polygon") {
315  polygons_.push_back(
316  std::make_shared<Polygon>(
317  node, polygon_name, tf_buffer_, base_frame_id, transform_tolerance));
318  } else if (polygon_type == "circle") {
319  polygons_.push_back(
320  std::make_shared<Circle>(
321  node, polygon_name, tf_buffer_, base_frame_id, transform_tolerance));
322  } else if (polygon_type == "velocity_polygon") {
323  polygons_.push_back(
324  std::make_shared<VelocityPolygon>(
325  node, polygon_name, tf_buffer_, base_frame_id, transform_tolerance));
326  } else { // Error if something else
327  RCLCPP_ERROR(
328  get_logger(),
329  "[%s]: Unknown polygon type: %s",
330  polygon_name.c_str(), polygon_type.c_str());
331  return false;
332  }
333 
334  // Configure last added polygon
335  if (!polygons_.back()->configure()) {
336  return false;
337  }
338  }
339  } catch (const std::exception & ex) {
340  RCLCPP_ERROR(get_logger(), "Error while getting parameters: %s", ex.what());
341  return false;
342  }
343 
344  return true;
345 }
346 
347 bool CollisionMonitor::configureSources(
348  const std::string & base_frame_id,
349  const std::string & odom_frame_id,
350  const tf2::Duration & transform_tolerance,
351  const rclcpp::Duration & source_timeout,
352  const bool base_shift_correction)
353 {
354  try {
355  auto node = shared_from_this();
356 
357  // Leave it to be not initialized: to intentionally cause an error if it will not set
358  std::vector<std::string> source_names =
359  node->declare_or_get_parameter<std::vector<std::string>>("observation_sources");
360  for (std::string source_name : source_names) {
361  const std::string source_type = node->declare_or_get_parameter(
362  source_name + ".type", std::string("scan")); // Laser scanner by default
363 
364  if (source_type == "scan") {
365  std::shared_ptr<Scan> s = std::make_shared<Scan>(
366  node, source_name, tf_buffer_, base_frame_id, odom_frame_id,
367  transform_tolerance, source_timeout, base_shift_correction);
368 
369  if (!s->configure()) {
370  return false;
371  }
372 
373  sources_.push_back(s);
374  } else if (source_type == "pointcloud") {
375  std::shared_ptr<PointCloud> p = std::make_shared<PointCloud>(
376  node, source_name, tf_buffer_, base_frame_id, odom_frame_id,
377  transform_tolerance, source_timeout, base_shift_correction);
378 
379  if (!p->configure()) {
380  return false;
381  }
382 
383  sources_.push_back(p);
384  } else if (source_type == "range") {
385  std::shared_ptr<Range> r = std::make_shared<Range>(
386  node, source_name, tf_buffer_, base_frame_id, odom_frame_id,
387  transform_tolerance, source_timeout, base_shift_correction);
388 
389  if (!r->configure()) {
390  return false;
391  }
392 
393  sources_.push_back(r);
394  } else if (source_type == "polygon") {
395  std::shared_ptr<PolygonSource> ps = std::make_shared<PolygonSource>(
396  node, source_name, tf_buffer_, base_frame_id, odom_frame_id,
397  transform_tolerance, source_timeout, base_shift_correction);
398  if (!ps->configure()) {
399  return false;
400  }
401 
402  sources_.push_back(ps);
403  } else if (source_type == "costmap") {
404  auto src = std::make_shared<CostmapSource>(
405  node, source_name, tf_buffer_, base_frame_id, odom_frame_id,
406  transform_tolerance, source_timeout, base_shift_correction);
407 
408  if (!src->configure()) {
409  return false;
410  }
411 
412  sources_.push_back(src);
413  } else { // Error if something else
414  RCLCPP_ERROR(
415  get_logger(),
416  "[%s]: Unknown source type: %s",
417  source_name.c_str(), source_type.c_str());
418  return false;
419  }
420  }
421  } catch (const std::exception & ex) {
422  RCLCPP_ERROR(get_logger(), "Error while getting parameters: %s", ex.what());
423  return false;
424  }
425 
426  return true;
427 }
428 
429 void CollisionMonitor::process(const Velocity & cmd_vel_in, const std_msgs::msg::Header & header)
430 {
431  // Current timestamp for all inner routines prolongation
432  rclcpp::Time curr_time = this->now();
433 
434  // Do nothing if main worker in non-active state
435  if (!process_active_) {
436  return;
437  }
438 
439  // Points array collected from different data sources in a robot base frame
440  std::unordered_map<std::string, std::vector<Point>> sources_collision_points_map;
441 
442  // By default - there is no action
443  Action robot_action{DO_NOTHING, cmd_vel_in, "", std::vector<Point>()};
444  // Polygon causing robot action (if any)
445  std::shared_ptr<Polygon> action_polygon;
446 
447  // Fill collision points array from different data sources
448  auto marker_array = std::make_unique<visualization_msgs::msg::MarkerArray>();
449  for (std::shared_ptr<Source> source : sources_) {
450  auto iter = sources_collision_points_map.insert(
451  {source->getSourceName(), std::vector<Point>()});
452 
453  if (source->getEnabled()) {
454  if (!source->getData(curr_time, iter.first->second) &&
455  source->getSourceTimeout().seconds() != 0.0)
456  {
457  action_polygon = nullptr;
458  robot_action.polygon_name = "invalid source";
459  robot_action.action_type = STOP;
460  robot_action.req_vel.x = 0.0;
461  robot_action.req_vel.y = 0.0;
462  robot_action.req_vel.tw = 0.0;
463  break;
464  }
465  }
466 
467  if (collision_points_marker_pub_->get_subscription_count() > 0) {
468  // visualize collision points with markers
469  visualization_msgs::msg::Marker marker;
470  marker.header.frame_id = base_frame_id_;
471  marker.header.stamp = rclcpp::Time(0, 0);
472  marker.ns = "collision_points_" + source->getSourceName();
473  marker.id = 0;
474  marker.type = visualization_msgs::msg::Marker::POINTS;
475  marker.action = visualization_msgs::msg::Marker::ADD;
476  marker.scale.x = 0.02;
477  marker.scale.y = 0.02;
478  marker.color.r = 1.0;
479  marker.color.a = 1.0;
480  marker.lifetime = rclcpp::Duration(0, 0);
481  marker.frame_locked = true;
482 
483  for (const auto & point : iter.first->second) {
484  geometry_msgs::msg::Point p;
485  p.x = point.x;
486  p.y = point.y;
487  p.z = collision_points_marker_3d_ ? point.z : 0.0;
488  marker.points.push_back(p);
489  }
490  marker_array->markers.push_back(marker);
491  }
492  }
493 
494  if (collision_points_marker_pub_->get_subscription_count() > 0) {
495  collision_points_marker_pub_->publish(std::move(marker_array));
496  }
497 
498  for (std::shared_ptr<Polygon> polygon : polygons_) {
499  if (!polygon->getEnabled() || !enabled_) {
500  continue;
501  }
502  if (robot_action.action_type == STOP) {
503  // If robot already should stop, do nothing
504  break;
505  }
506 
507  // Update polygon coordinates
508  polygon->updatePolygon(cmd_vel_in);
509 
510  const ActionType at = polygon->getActionType();
511  if (at == STOP || at == SLOWDOWN || at == LIMIT) {
512  // Process STOP/SLOWDOWN for the selected polygon
513  if (processStopSlowdownLimit(
514  polygon, sources_collision_points_map, cmd_vel_in, robot_action))
515  {
516  action_polygon = polygon;
517  }
518  } else if (at == APPROACH) {
519  // Process APPROACH for the selected polygon
520  if (processApproach(polygon, sources_collision_points_map, cmd_vel_in, robot_action)) {
521  action_polygon = polygon;
522  }
523  }
524  }
525 
526  if (triggering_points_pub_->get_subscription_count() > 0) {
527  publishTriggeringPoints(robot_action);
528  }
529 
530  if ((robot_action.polygon_name != robot_action_prev_.polygon_name) && enabled_) {
531  // Report changed robot behavior
532  notifyActionState(robot_action, action_polygon);
533  }
534 
535  // Publish required robot velocity
536  publishVelocity(robot_action, header);
537 
538  // Publish polygons and exclusion zones for better visualization
539  publishVisualizations();
540 
541  robot_action_prev_ = robot_action;
542 }
543 
544 bool CollisionMonitor::processStopSlowdownLimit(
545  const std::shared_ptr<Polygon> polygon,
546  const std::unordered_map<std::string, std::vector<Point>> & sources_collision_points_map,
547  const Velocity & velocity,
548  Action & robot_action) const
549 {
550  if (!polygon->isShapeSet()) {
551  return false;
552  }
553 
554  // Single pass: collect in-polygon points while isTriggered counts them.
555  std::vector<Point> triggering_points;
556  if (polygon->isTriggered(sources_collision_points_map, triggering_points)) {
557  if (polygon->getActionType() == STOP) {
558  // Setting up zero velocity for STOP model
559  robot_action.polygon_name = polygon->getName();
560  robot_action.action_type = STOP;
561  robot_action.req_vel.x = 0.0;
562  robot_action.req_vel.y = 0.0;
563  robot_action.req_vel.tw = 0.0;
564  robot_action.triggering_points = std::move(triggering_points);
565  return true;
566  } else if (polygon->getActionType() == SLOWDOWN) {
567  const Velocity safe_vel = velocity * polygon->getSlowdownRatio();
568  // Check that currently calculated velocity is safer than
569  // chosen for previous shapes one
570  if (safe_vel < robot_action.req_vel) {
571  robot_action.polygon_name = polygon->getName();
572  robot_action.action_type = SLOWDOWN;
573  robot_action.req_vel = safe_vel;
574  robot_action.triggering_points = std::move(triggering_points);
575  return true;
576  }
577  } else { // Limit
578  // Compute linear velocity
579  const double linear_vel = std::hypot(velocity.x, velocity.y); // absolute
580  Velocity safe_vel;
581  double ratio = 1.0;
582 
583  // Calculate the most restrictive ratio to preserve curvature
584  if (linear_vel != 0.0) {
585  ratio = std::min(ratio, polygon->getLinearLimit() / linear_vel);
586  }
587  if (velocity.tw != 0.0) {
588  ratio = std::min(ratio, polygon->getAngularLimit() / std::abs(velocity.tw));
589  }
590  ratio = std::clamp(ratio, 0.0, 1.0);
591  // Apply the same ratio to all components to preserve curvature
592  safe_vel = velocity * ratio;
593  // Check that currently calculated velocity is safer than
594  // chosen for previous shapes one
595  if (safe_vel < robot_action.req_vel) {
596  robot_action.polygon_name = polygon->getName();
597  robot_action.action_type = LIMIT;
598  robot_action.req_vel = safe_vel;
599  robot_action.triggering_points = std::move(triggering_points);
600  return true;
601  }
602  }
603  }
604 
605  return false;
606 }
607 
608 bool CollisionMonitor::processApproach(
609  const std::shared_ptr<Polygon> polygon,
610  const std::unordered_map<std::string, std::vector<Point>> & sources_collision_points_map,
611  const Velocity & velocity,
612  Action & robot_action) const
613 {
614  if (!polygon->isShapeSet()) {
615  return false;
616  }
617 
618  // Obtain time before a collision, capturing the responsible points at the collision step.
619  std::vector<Point> triggering_points;
620  const double collision_time = polygon->getCollisionTime(sources_collision_points_map, velocity,
621  triggering_points);
622  if (collision_time >= 0.0) {
623  // If collision will occur, reduce robot speed
624  const double change_ratio = collision_time / polygon->getTimeBeforeCollision();
625  const Velocity safe_vel = velocity * change_ratio;
626  // Check that currently calculated velocity is safer than
627  // chosen for previous shapes one
628  if (safe_vel < robot_action.req_vel) {
629  robot_action.polygon_name = polygon->getName();
630  robot_action.action_type = APPROACH;
631  robot_action.req_vel = safe_vel;
632  robot_action.triggering_points = std::move(triggering_points);
633  return true;
634  }
635  }
636 
637  return false;
638 }
639 
640 void CollisionMonitor::notifyActionState(
641  const Action & robot_action, const std::shared_ptr<Polygon> action_polygon) const
642 {
643  if (robot_action.action_type == STOP) {
644  if (robot_action.polygon_name == "invalid source") {
645  RCLCPP_WARN(
646  get_logger(),
647  "Robot to stop due to invalid source."
648  " Either due to data not published yet, or to lack of new data received within the"
649  " sensor timeout, or if impossible to transform data to base frame");
650  } else {
651  RCLCPP_INFO(
652  get_logger(),
653  "Robot to stop due to %s polygon",
654  action_polygon->getName().c_str());
655  }
656  } else if (robot_action.action_type == SLOWDOWN) {
657  RCLCPP_INFO(
658  get_logger(),
659  "Robot to slowdown for %f percents due to %s polygon",
660  action_polygon->getSlowdownRatio() * 100,
661  action_polygon->getName().c_str());
662  } else if (robot_action.action_type == LIMIT) {
663  RCLCPP_INFO(
664  get_logger(),
665  "Robot to limit speed due to %s polygon",
666  action_polygon->getName().c_str());
667  } else if (robot_action.action_type == APPROACH) {
668  RCLCPP_INFO(
669  get_logger(),
670  "Robot to approach for %f seconds away from collision",
671  action_polygon->getTimeBeforeCollision());
672  } else { // robot_action.action_type == DO_NOTHING
673  RCLCPP_INFO(
674  get_logger(),
675  "Robot to continue normal operation");
676  }
677 
678  if (state_pub_) {
679  std::unique_ptr<nav2_msgs::msg::CollisionMonitorState> state_msg =
680  std::make_unique<nav2_msgs::msg::CollisionMonitorState>();
681  state_msg->polygon_name = robot_action.polygon_name;
682  state_msg->action_type = robot_action.action_type;
683 
684  state_pub_->publish(std::move(state_msg));
685  }
686 }
687 
688 void CollisionMonitor::publishTriggeringPoints(const Action & action)
689 {
690  auto marker_array = std::make_unique<visualization_msgs::msg::MarkerArray>();
691 
692  // Clear markers from previous cycle.
693  visualization_msgs::msg::Marker clear;
694  clear.action = visualization_msgs::msg::Marker::DELETEALL;
695  marker_array->markers.push_back(clear);
696 
697  if (!action.triggering_points.empty()) {
698  // Colour by action type: STOP=red, SLOWDOWN=yellow, APPROACH=blue, LIMIT=orange
699  float r = 0.0f, g = 0.0f, b = 0.0f;
700  switch (action.action_type) {
701  case STOP: r = 1.0f; g = 0.0f; b = 0.0f; break;
702  case SLOWDOWN: r = 1.0f; g = 1.0f; b = 0.0f; break;
703  case APPROACH: r = 0.0f; g = 0.5f; b = 1.0f; break;
704  case LIMIT: r = 1.0f; g = 0.5f; b = 0.0f; break;
705  default: break;
706  }
707 
708  std::unordered_map<std::string, size_t> marker_index;
709  for (const auto & p : action.triggering_points) {
710  auto [it, new_source] = marker_index.try_emplace(p.source, marker_array->markers.size());
711 
712  if (new_source) {
713  visualization_msgs::msg::Marker marker;
714  marker.header.frame_id = base_frame_id_;
715  marker.header.stamp = rclcpp::Time(0, 0);
716  marker.ns = action.polygon_name + "/" + p.source;
717  marker.id = 0;
718  marker.type = visualization_msgs::msg::Marker::POINTS;
719  marker.action = visualization_msgs::msg::Marker::ADD;
720  marker.scale.x = 0.05;
721  marker.scale.y = 0.05;
722  marker.color.r = r;
723  marker.color.g = g;
724  marker.color.b = b;
725  marker.color.a = 1.0f;
726  marker.lifetime = rclcpp::Duration(0, 0);
727  marker.frame_locked = true;
728  marker_array->markers.push_back(std::move(marker));
729  }
730  geometry_msgs::msg::Point gp;
731  gp.x = p.x;
732  gp.y = p.y;
733  gp.z = p.z;
734  marker_array->markers[it->second].points.push_back(gp);
735  }
736  }
737  triggering_points_pub_->publish(std::move(marker_array));
738 }
739 
740 void CollisionMonitor::publishVisualizations() const
741 {
742  for (std::shared_ptr<Polygon> polygon : polygons_) {
743  if (polygon->getEnabled() || !enabled_) {
744  polygon->publish();
745  }
746  }
747 
748  for (std::shared_ptr<Source> source : sources_) {
749  source->publishExclusionZones();
750  }
751 }
752 
753 void CollisionMonitor::toggleCMServiceCallback(
754  const std::shared_ptr<rmw_request_id_t>/*request_header*/,
755  const std::shared_ptr<nav2_msgs::srv::Toggle::Request> request,
756  std::shared_ptr<nav2_msgs::srv::Toggle::Response> response)
757 {
758  enabled_ = request->enable;
759 
760  std::stringstream message;
761  message << "Collision monitor toggled " << (enabled_ ? "on" : "off") << " successfully";
762 
763  response->success = true;
764  response->message = message.str();
765 }
766 
767 } // namespace nav2_collision_monitor
768 
769 #include "rclcpp_components/register_node_macro.hpp"
770 
771 // Register the component with class_loader.
772 // This acts as a sort of entry point, allowing the component to be discoverable when its library
773 // is being loaded into a running process.
774 RCLCPP_COMPONENTS_REGISTER_NODE(nav2_collision_monitor::CollisionMonitor)
Action for robot.
Definition: types.hpp:79
Velocity for 2D model of motion.
Definition: types.hpp:26