15 #include "nav2_collision_monitor/collision_monitor_node.hpp"
22 #include "nav2_ros_common/node_utils.hpp"
23 #include "nav2_util/robot_utils.hpp"
25 #include "nav2_collision_monitor/kinematics.hpp"
27 using namespace std::placeholders;
29 namespace nav2_collision_monitor
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)
40 CollisionMonitor::~CollisionMonitor()
47 CollisionMonitor::on_configure(
const rclcpp_lifecycle::State & state)
49 RCLCPP_INFO(get_logger(),
"Configuring");
52 tf_buffer_ = nav2::create_transform_buffer(
this);
53 tf_listener_ = nav2::create_transform_listener(*tf_buffer_,
this,
true);
55 std::string cmd_vel_in_topic;
56 std::string cmd_vel_out_topic;
57 std::string state_topic;
60 if (!getParameters(cmd_vel_in_topic, cmd_vel_out_topic, state_topic)) {
62 return nav2::CallbackReturn::FAILURE;
65 cmd_vel_in_sub_ = std::make_unique<nav2_util::TwistSubscriber>(
68 std::bind(&CollisionMonitor::cmdVelInCallbackUnstamped,
this, std::placeholders::_1),
69 std::bind(&CollisionMonitor::cmdVelInCallbackStamped,
this, std::placeholders::_1));
71 auto node = shared_from_this();
72 cmd_vel_out_pub_ = std::make_unique<nav2_util::TwistPublisher>(node, cmd_vel_out_topic);
74 if (!state_topic.empty()) {
75 state_pub_ = this->create_publisher<nav2_msgs::msg::CollisionMonitorState>(
79 collision_points_marker_pub_ = this->create_publisher<visualization_msgs::msg::MarkerArray>(
80 "~/collision_points_marker");
82 triggering_points_pub_ = this->create_publisher<visualization_msgs::msg::MarkerArray>(
83 "~/triggering_points");
86 toggle_cm_service_ = create_service<nav2_msgs::srv::Toggle>(
88 std::bind(&CollisionMonitor::toggleCMServiceCallback,
this, _1, _2, _3));
90 bool use_realtime_priority = node->declare_or_get_parameter(
"use_realtime_priority",
false);
91 if (use_realtime_priority) {
93 nav2::setSoftRealTimePriority();
94 }
catch (
const std::runtime_error & e) {
95 RCLCPP_ERROR(get_logger(),
"%s", e.what());
97 return nav2::CallbackReturn::FAILURE;
101 enabled_ = node->declare_or_get_parameter(
"enabled",
true);
104 RCLCPP_WARN(get_logger(),
"Collision monitor is disabled at startup.");
106 RCLCPP_INFO(get_logger(),
"Collision monitor is enabled at startup.");
109 return nav2::CallbackReturn::SUCCESS;
113 CollisionMonitor::on_activate(
const rclcpp_lifecycle::State & )
115 RCLCPP_INFO(get_logger(),
"Activating");
118 cmd_vel_out_pub_->on_activate();
120 state_pub_->on_activate();
122 collision_points_marker_pub_->on_activate();
123 triggering_points_pub_->on_activate();
126 for (std::shared_ptr<Polygon> polygon : polygons_) {
131 for (std::shared_ptr<Source> source : sources_) {
137 publishVisualizations();
140 process_active_ =
true;
145 return nav2::CallbackReturn::SUCCESS;
149 CollisionMonitor::on_deactivate(
const rclcpp_lifecycle::State & )
151 RCLCPP_INFO(get_logger(),
"Deactivating");
154 process_active_ =
false;
157 robot_action_prev_ = {DO_NOTHING, {-1.0, -1.0, -1.0},
"", std::vector<Point>()};
160 for (std::shared_ptr<Polygon> polygon : polygons_) {
161 polygon->deactivate();
165 for (std::shared_ptr<Source> source : sources_) {
166 source->deactivate();
170 cmd_vel_out_pub_->on_deactivate();
172 state_pub_->on_deactivate();
174 collision_points_marker_pub_->on_deactivate();
175 triggering_points_pub_->on_deactivate();
180 return nav2::CallbackReturn::SUCCESS;
184 CollisionMonitor::on_cleanup(
const rclcpp_lifecycle::State & )
186 RCLCPP_INFO(get_logger(),
"Cleaning up");
188 cmd_vel_in_sub_.reset();
189 cmd_vel_out_pub_.reset();
191 collision_points_marker_pub_.reset();
192 triggering_points_pub_.reset();
197 tf_listener_.reset();
200 return nav2::CallbackReturn::SUCCESS;
204 CollisionMonitor::on_shutdown(
const rclcpp_lifecycle::State & )
206 RCLCPP_INFO(get_logger(),
"Shutting down");
208 return nav2::CallbackReturn::SUCCESS;
211 void CollisionMonitor::cmdVelInCallbackStamped(
212 const geometry_msgs::msg::TwistStamped::ConstSharedPtr & msg)
215 if (!nav2_util::validateTwist(msg->twist)) {
216 RCLCPP_ERROR(get_logger(),
"Velocity message contains NaNs or Infs! Ignoring as invalid!");
220 process({msg->twist.linear.x, msg->twist.linear.y, msg->twist.angular.z}, msg->header);
223 void CollisionMonitor::cmdVelInCallbackUnstamped(
224 const geometry_msgs::msg::Twist::ConstSharedPtr & msg)
226 auto twist_stamped = std::make_shared<geometry_msgs::msg::TwistStamped>();
227 twist_stamped->twist = *msg;
228 cmdVelInCallbackStamped(twist_stamped);
231 void CollisionMonitor::publishVelocity(
232 const Action & robot_action,
const std_msgs::msg::Header & header)
234 if (robot_action.req_vel.isZero()) {
235 if (!robot_action_prev_.req_vel.isZero()) {
237 stop_stamp_ = this->now();
238 }
else if (this->now() - stop_stamp_ > stop_pub_timeout_) {
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;
252 cmd_vel_out_pub_->publish(std::move(cmd_vel_out_msg));
255 bool CollisionMonitor::getParameters(
256 std::string & cmd_vel_in_topic,
257 std::string & cmd_vel_out_topic,
258 std::string & state_topic)
260 std::string odom_frame_id;
261 tf2::Duration transform_tolerance;
262 rclcpp::Duration source_timeout(2.0, 0.0);
264 auto node = shared_from_this();
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(
""));
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);
282 stop_pub_timeout_ = rclcpp::Duration::from_seconds(
283 node->declare_or_get_parameter(
"stop_pub_timeout", 1.0));
287 base_frame_id_, odom_frame_id, transform_tolerance, source_timeout, base_shift_correction))
292 if (!configurePolygons(base_frame_id_, transform_tolerance)) {
299 bool CollisionMonitor::configurePolygons(
300 const std::string & base_frame_id,
301 const tf2::Duration & transform_tolerance)
304 auto node = shared_from_this();
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) {
311 const std::string polygon_type =
312 node->declare_or_get_parameter<std::string>(polygon_name +
".type");
314 if (polygon_type ==
"polygon") {
316 std::make_shared<Polygon>(
317 node, polygon_name, tf_buffer_, base_frame_id, transform_tolerance));
318 }
else if (polygon_type ==
"circle") {
320 std::make_shared<Circle>(
321 node, polygon_name, tf_buffer_, base_frame_id, transform_tolerance));
322 }
else if (polygon_type ==
"velocity_polygon") {
324 std::make_shared<VelocityPolygon>(
325 node, polygon_name, tf_buffer_, base_frame_id, transform_tolerance));
329 "[%s]: Unknown polygon type: %s",
330 polygon_name.c_str(), polygon_type.c_str());
335 if (!polygons_.back()->configure()) {
339 }
catch (
const std::exception & ex) {
340 RCLCPP_ERROR(get_logger(),
"Error while getting parameters: %s", ex.what());
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)
355 auto node = shared_from_this();
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"));
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);
369 if (!s->configure()) {
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);
379 if (!p->configure()) {
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);
389 if (!r->configure()) {
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()) {
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);
408 if (!src->configure()) {
412 sources_.push_back(src);
416 "[%s]: Unknown source type: %s",
417 source_name.c_str(), source_type.c_str());
421 }
catch (
const std::exception & ex) {
422 RCLCPP_ERROR(get_logger(),
"Error while getting parameters: %s", ex.what());
429 void CollisionMonitor::process(
const Velocity & cmd_vel_in,
const std_msgs::msg::Header & header)
432 rclcpp::Time curr_time = this->now();
435 if (!process_active_) {
440 std::unordered_map<std::string, std::vector<Point>> sources_collision_points_map;
443 Action robot_action{DO_NOTHING, cmd_vel_in,
"", std::vector<Point>()};
445 std::shared_ptr<Polygon> action_polygon;
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>()});
453 if (source->getEnabled()) {
454 if (!source->getData(curr_time, iter.first->second) &&
455 source->getSourceTimeout().seconds() != 0.0)
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;
467 if (collision_points_marker_pub_->get_subscription_count() > 0) {
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();
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;
483 for (
const auto & point : iter.first->second) {
484 geometry_msgs::msg::Point p;
487 p.z = collision_points_marker_3d_ ? point.z : 0.0;
488 marker.points.push_back(p);
490 marker_array->markers.push_back(marker);
494 if (collision_points_marker_pub_->get_subscription_count() > 0) {
495 collision_points_marker_pub_->publish(std::move(marker_array));
498 for (std::shared_ptr<Polygon> polygon : polygons_) {
499 if (!polygon->getEnabled() || !enabled_) {
502 if (robot_action.action_type == STOP) {
508 polygon->updatePolygon(cmd_vel_in);
510 const ActionType at = polygon->getActionType();
511 if (at == STOP || at == SLOWDOWN || at == LIMIT) {
513 if (processStopSlowdownLimit(
514 polygon, sources_collision_points_map, cmd_vel_in, robot_action))
516 action_polygon = polygon;
518 }
else if (at == APPROACH) {
520 if (processApproach(polygon, sources_collision_points_map, cmd_vel_in, robot_action)) {
521 action_polygon = polygon;
526 if (triggering_points_pub_->get_subscription_count() > 0) {
527 publishTriggeringPoints(robot_action);
530 if ((robot_action.polygon_name != robot_action_prev_.polygon_name) && enabled_) {
532 notifyActionState(robot_action, action_polygon);
536 publishVelocity(robot_action, header);
539 publishVisualizations();
541 robot_action_prev_ = robot_action;
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,
548 Action & robot_action)
const
550 if (!polygon->isShapeSet()) {
555 std::vector<Point> triggering_points;
556 if (polygon->isTriggered(sources_collision_points_map, triggering_points)) {
557 if (polygon->getActionType() == STOP) {
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);
566 }
else if (polygon->getActionType() == SLOWDOWN) {
567 const Velocity safe_vel = velocity * polygon->getSlowdownRatio();
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);
579 const double linear_vel = std::hypot(velocity.x, velocity.y);
584 if (linear_vel != 0.0) {
585 ratio = std::min(ratio, polygon->getLinearLimit() / linear_vel);
587 if (velocity.tw != 0.0) {
588 ratio = std::min(ratio, polygon->getAngularLimit() / std::abs(velocity.tw));
590 ratio = std::clamp(ratio, 0.0, 1.0);
592 safe_vel = velocity * ratio;
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);
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,
612 Action & robot_action)
const
614 if (!polygon->isShapeSet()) {
619 std::vector<Point> triggering_points;
620 const double collision_time = polygon->getCollisionTime(sources_collision_points_map, velocity,
622 if (collision_time >= 0.0) {
624 const double change_ratio = collision_time / polygon->getTimeBeforeCollision();
625 const Velocity safe_vel = velocity * change_ratio;
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);
640 void CollisionMonitor::notifyActionState(
641 const Action & robot_action,
const std::shared_ptr<Polygon> action_polygon)
const
643 if (robot_action.action_type == STOP) {
644 if (robot_action.polygon_name ==
"invalid source") {
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");
653 "Robot to stop due to %s polygon",
654 action_polygon->getName().c_str());
656 }
else if (robot_action.action_type == SLOWDOWN) {
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) {
665 "Robot to limit speed due to %s polygon",
666 action_polygon->getName().c_str());
667 }
else if (robot_action.action_type == APPROACH) {
670 "Robot to approach for %f seconds away from collision",
671 action_polygon->getTimeBeforeCollision());
675 "Robot to continue normal operation");
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;
684 state_pub_->publish(std::move(state_msg));
688 void CollisionMonitor::publishTriggeringPoints(
const Action & action)
690 auto marker_array = std::make_unique<visualization_msgs::msg::MarkerArray>();
693 visualization_msgs::msg::Marker clear;
694 clear.action = visualization_msgs::msg::Marker::DELETEALL;
695 marker_array->markers.push_back(clear);
697 if (!action.triggering_points.empty()) {
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;
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());
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;
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;
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));
730 geometry_msgs::msg::Point gp;
734 marker_array->markers[it->second].points.push_back(gp);
737 triggering_points_pub_->publish(std::move(marker_array));
740 void CollisionMonitor::publishVisualizations()
const
742 for (std::shared_ptr<Polygon> polygon : polygons_) {
743 if (polygon->getEnabled() || !enabled_) {
748 for (std::shared_ptr<Source> source : sources_) {
749 source->publishExclusionZones();
753 void CollisionMonitor::toggleCMServiceCallback(
754 const std::shared_ptr<rmw_request_id_t>,
755 const std::shared_ptr<nav2_msgs::srv::Toggle::Request> request,
756 std::shared_ptr<nav2_msgs::srv::Toggle::Response> response)
758 enabled_ = request->enable;
760 std::stringstream message;
761 message <<
"Collision monitor toggled " << (enabled_ ?
"on" :
"off") <<
" successfully";
763 response->success =
true;
764 response->message = message.str();
769 #include "rclcpp_components/register_node_macro.hpp"
Collision Monitor ROS2 node.
Velocity for 2D model of motion.