15 #include "nav2_collision_monitor/collision_monitor_node.hpp"
21 #include "tf2_ros/create_timer_ros.h"
23 #include "nav2_util/node_utils.hpp"
24 #include "nav2_util/robot_utils.hpp"
26 #include "nav2_collision_monitor/kinematics.hpp"
28 using namespace std::placeholders;
30 namespace nav2_collision_monitor
33 CollisionMonitor::CollisionMonitor(
const rclcpp::NodeOptions & options)
34 : nav2_util::LifecycleNode(
"collision_monitor",
"", options),
35 enabled_{true}, process_active_(false), robot_action_prev_{DO_NOTHING, {-1.0, -1.0, -1.0},
""},
36 stop_stamp_{0, 0, get_clock()->get_clock_type()}, stop_pub_timeout_(1.0, 0.0)
40 CollisionMonitor::~CollisionMonitor()
46 nav2_util::CallbackReturn
47 CollisionMonitor::on_configure(
const rclcpp_lifecycle::State & state)
49 RCLCPP_INFO(get_logger(),
"Configuring");
52 tf_buffer_ = std::make_shared<tf2_ros::Buffer>(this->get_clock());
53 auto timer_interface = std::make_shared<tf2_ros::CreateTimerROS>(
54 this->get_node_base_interface(),
55 this->get_node_timers_interface());
56 tf_buffer_->setCreateTimerInterface(timer_interface);
57 tf_listener_ = std::make_shared<tf2_ros::TransformListener>(*tf_buffer_,
this,
true);
59 std::string cmd_vel_in_topic;
60 std::string cmd_vel_out_topic;
61 std::string state_topic;
64 if (!getParameters(cmd_vel_in_topic, cmd_vel_out_topic, state_topic)) {
66 return nav2_util::CallbackReturn::FAILURE;
69 cmd_vel_in_sub_ = std::make_unique<nav2_util::TwistSubscriber>(
73 std::bind(&CollisionMonitor::cmdVelInCallbackUnstamped,
this, std::placeholders::_1),
74 std::bind(&CollisionMonitor::cmdVelInCallbackStamped,
this, std::placeholders::_1));
76 auto node = shared_from_this();
77 cmd_vel_out_pub_ = std::make_unique<nav2_util::TwistPublisher>(node, cmd_vel_out_topic, 1);
79 if (!state_topic.empty()) {
80 state_pub_ = this->create_publisher<nav2_msgs::msg::CollisionMonitorState>(
84 collision_points_marker_pub_ = this->create_publisher<visualization_msgs::msg::MarkerArray>(
85 "~/collision_points_marker", 1);
88 toggle_cm_service_ = create_service<nav2_msgs::srv::Toggle>(
90 std::bind(&CollisionMonitor::toggleCMServiceCallback,
this, _1, _2, _3));
92 nav2_util::declare_parameter_if_not_declared(
93 node,
"use_realtime_priority", rclcpp::ParameterValue(
false));
94 bool use_realtime_priority =
false;
95 node->get_parameter(
"use_realtime_priority", use_realtime_priority);
96 if (use_realtime_priority) {
98 nav2_util::setSoftRealTimePriority();
99 }
catch (
const std::runtime_error & e) {
100 RCLCPP_ERROR(get_logger(),
"%s", e.what());
102 return nav2_util::CallbackReturn::FAILURE;
106 return nav2_util::CallbackReturn::SUCCESS;
109 nav2_util::CallbackReturn
110 CollisionMonitor::on_activate(
const rclcpp_lifecycle::State & )
112 RCLCPP_INFO(get_logger(),
"Activating");
115 cmd_vel_out_pub_->on_activate();
117 state_pub_->on_activate();
119 collision_points_marker_pub_->on_activate();
122 for (std::shared_ptr<Polygon> polygon : polygons_) {
131 process_active_ =
true;
136 return nav2_util::CallbackReturn::SUCCESS;
139 nav2_util::CallbackReturn
140 CollisionMonitor::on_deactivate(
const rclcpp_lifecycle::State & )
142 RCLCPP_INFO(get_logger(),
"Deactivating");
145 process_active_ =
false;
148 robot_action_prev_ = {DO_NOTHING, {-1.0, -1.0, -1.0},
""};
151 for (std::shared_ptr<Polygon> polygon : polygons_) {
152 polygon->deactivate();
156 cmd_vel_out_pub_->on_deactivate();
158 state_pub_->on_deactivate();
160 collision_points_marker_pub_->on_deactivate();
165 return nav2_util::CallbackReturn::SUCCESS;
168 nav2_util::CallbackReturn
169 CollisionMonitor::on_cleanup(
const rclcpp_lifecycle::State & )
171 RCLCPP_INFO(get_logger(),
"Cleaning up");
173 cmd_vel_in_sub_.reset();
174 cmd_vel_out_pub_.reset();
176 collision_points_marker_pub_.reset();
181 tf_listener_.reset();
184 return nav2_util::CallbackReturn::SUCCESS;
187 nav2_util::CallbackReturn
188 CollisionMonitor::on_shutdown(
const rclcpp_lifecycle::State & )
190 RCLCPP_INFO(get_logger(),
"Shutting down");
192 return nav2_util::CallbackReturn::SUCCESS;
195 void CollisionMonitor::cmdVelInCallbackStamped(geometry_msgs::msg::TwistStamped::SharedPtr msg)
198 if (!nav2_util::validateTwist(*msg)) {
199 RCLCPP_ERROR(get_logger(),
"Velocity message contains NaNs or Infs! Ignoring as invalid!");
203 process({msg->twist.linear.x, msg->twist.linear.y, msg->twist.angular.z}, msg->header);
206 void CollisionMonitor::cmdVelInCallbackUnstamped(geometry_msgs::msg::Twist::SharedPtr msg)
208 auto twist_stamped = std::make_shared<geometry_msgs::msg::TwistStamped>();
209 twist_stamped->twist = *msg;
210 cmdVelInCallbackStamped(twist_stamped);
213 void CollisionMonitor::publishVelocity(
214 const Action & robot_action,
const std_msgs::msg::Header & header)
216 if (robot_action.req_vel.isZero()) {
217 if (!robot_action_prev_.req_vel.isZero()) {
219 stop_stamp_ = this->now();
220 }
else if (this->now() - stop_stamp_ > stop_pub_timeout_) {
227 auto cmd_vel_out_msg = std::make_unique<geometry_msgs::msg::TwistStamped>();
228 cmd_vel_out_msg->header = header;
229 cmd_vel_out_msg->twist.linear.x = robot_action.req_vel.x;
230 cmd_vel_out_msg->twist.linear.y = robot_action.req_vel.y;
231 cmd_vel_out_msg->twist.angular.z = robot_action.req_vel.tw;
234 cmd_vel_out_pub_->publish(std::move(cmd_vel_out_msg));
237 bool CollisionMonitor::getParameters(
238 std::string & cmd_vel_in_topic,
239 std::string & cmd_vel_out_topic,
240 std::string & state_topic)
242 std::string base_frame_id, odom_frame_id;
243 tf2::Duration transform_tolerance;
244 rclcpp::Duration source_timeout(2.0, 0.0);
246 auto node = shared_from_this();
248 nav2_util::declare_parameter_if_not_declared(
249 node,
"cmd_vel_in_topic", rclcpp::ParameterValue(
"cmd_vel_smoothed"));
250 cmd_vel_in_topic = get_parameter(
"cmd_vel_in_topic").as_string();
251 nav2_util::declare_parameter_if_not_declared(
252 node,
"cmd_vel_out_topic", rclcpp::ParameterValue(
"cmd_vel"));
253 cmd_vel_out_topic = get_parameter(
"cmd_vel_out_topic").as_string();
254 nav2_util::declare_parameter_if_not_declared(
255 node,
"state_topic", rclcpp::ParameterValue(
""));
256 state_topic = get_parameter(
"state_topic").as_string();
258 nav2_util::declare_parameter_if_not_declared(
259 node,
"base_frame_id", rclcpp::ParameterValue(
"base_footprint"));
260 base_frame_id = get_parameter(
"base_frame_id").as_string();
261 nav2_util::declare_parameter_if_not_declared(
262 node,
"odom_frame_id", rclcpp::ParameterValue(
"odom"));
263 odom_frame_id = get_parameter(
"odom_frame_id").as_string();
264 nav2_util::declare_parameter_if_not_declared(
265 node,
"transform_tolerance", rclcpp::ParameterValue(0.1));
266 transform_tolerance =
267 tf2::durationFromSec(get_parameter(
"transform_tolerance").as_double());
268 nav2_util::declare_parameter_if_not_declared(
269 node,
"source_timeout", rclcpp::ParameterValue(2.0));
271 rclcpp::Duration::from_seconds(get_parameter(
"source_timeout").as_double());
272 nav2_util::declare_parameter_if_not_declared(
273 node,
"base_shift_correction", rclcpp::ParameterValue(
true));
274 const bool base_shift_correction =
275 get_parameter(
"base_shift_correction").as_bool();
277 nav2_util::declare_parameter_if_not_declared(
278 node,
"stop_pub_timeout", rclcpp::ParameterValue(1.0));
280 rclcpp::Duration::from_seconds(get_parameter(
"stop_pub_timeout").as_double());
284 base_frame_id, odom_frame_id, transform_tolerance, source_timeout, base_shift_correction))
289 if (!configurePolygons(base_frame_id, transform_tolerance)) {
296 bool CollisionMonitor::configurePolygons(
297 const std::string & base_frame_id,
298 const tf2::Duration & transform_tolerance)
301 auto node = shared_from_this();
304 nav2_util::declare_parameter_if_not_declared(
305 node,
"polygons", rclcpp::PARAMETER_STRING_ARRAY);
306 std::vector<std::string> polygon_names = get_parameter(
"polygons").as_string_array();
307 for (std::string polygon_name : polygon_names) {
309 nav2_util::declare_parameter_if_not_declared(
310 node, polygon_name +
".type", rclcpp::PARAMETER_STRING);
311 const std::string polygon_type = get_parameter(polygon_name +
".type").as_string();
313 if (polygon_type ==
"polygon") {
315 std::make_shared<Polygon>(
316 node, polygon_name, tf_buffer_, base_frame_id, transform_tolerance));
317 }
else if (polygon_type ==
"circle") {
319 std::make_shared<Circle>(
320 node, polygon_name, tf_buffer_, base_frame_id, transform_tolerance));
321 }
else if (polygon_type ==
"velocity_polygon") {
323 std::make_shared<VelocityPolygon>(
324 node, polygon_name, tf_buffer_, base_frame_id, transform_tolerance));
328 "[%s]: Unknown polygon type: %s",
329 polygon_name.c_str(), polygon_type.c_str());
334 if (!polygons_.back()->configure()) {
338 }
catch (
const std::exception & ex) {
339 RCLCPP_ERROR(get_logger(),
"Error while getting parameters: %s", ex.what());
346 bool CollisionMonitor::configureSources(
347 const std::string & base_frame_id,
348 const std::string & odom_frame_id,
349 const tf2::Duration & transform_tolerance,
350 const rclcpp::Duration & source_timeout,
351 const bool base_shift_correction)
354 auto node = shared_from_this();
357 nav2_util::declare_parameter_if_not_declared(
358 node,
"observation_sources", rclcpp::PARAMETER_STRING_ARRAY);
359 std::vector<std::string> source_names = get_parameter(
"observation_sources").as_string_array();
360 for (std::string source_name : source_names) {
361 nav2_util::declare_parameter_if_not_declared(
362 node, source_name +
".type",
363 rclcpp::ParameterValue(
"scan"));
364 const std::string source_type = get_parameter(source_name +
".type").as_string();
366 if (source_type ==
"scan") {
367 std::shared_ptr<Scan> s = std::make_shared<Scan>(
368 node, source_name, tf_buffer_, base_frame_id, odom_frame_id,
369 transform_tolerance, source_timeout, base_shift_correction);
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);
381 sources_.push_back(p);
382 }
else if (source_type ==
"range") {
383 std::shared_ptr<Range> r = std::make_shared<Range>(
384 node, source_name, tf_buffer_, base_frame_id, odom_frame_id,
385 transform_tolerance, source_timeout, base_shift_correction);
389 sources_.push_back(r);
390 }
else if (source_type ==
"polygon") {
391 std::shared_ptr<PolygonSource> ps = std::make_shared<PolygonSource>(
392 node, source_name, tf_buffer_, base_frame_id, odom_frame_id,
393 transform_tolerance, source_timeout, base_shift_correction);
396 sources_.push_back(ps);
400 "[%s]: Unknown source type: %s",
401 source_name.c_str(), source_type.c_str());
405 }
catch (
const std::exception & ex) {
406 RCLCPP_ERROR(get_logger(),
"Error while getting parameters: %s", ex.what());
413 void CollisionMonitor::process(
const Velocity & cmd_vel_in,
const std_msgs::msg::Header & header)
416 rclcpp::Time curr_time = this->now();
419 if (!process_active_) {
424 std::unordered_map<std::string, std::vector<Point>> sources_collision_points_map;
427 Action robot_action{DO_NOTHING, cmd_vel_in,
""};
429 std::shared_ptr<Polygon> action_polygon;
432 auto marker_array = std::make_unique<visualization_msgs::msg::MarkerArray>();
433 for (std::shared_ptr<Source> source : sources_) {
434 auto iter = sources_collision_points_map.insert(
435 {source->getSourceName(), std::vector<Point>()});
437 if (source->getEnabled()) {
438 if (!source->getData(curr_time, iter.first->second) &&
439 source->getSourceTimeout().seconds() != 0.0)
441 action_polygon =
nullptr;
442 robot_action.polygon_name =
"invalid source";
443 robot_action.action_type = STOP;
444 robot_action.req_vel.x = 0.0;
445 robot_action.req_vel.y = 0.0;
446 robot_action.req_vel.tw = 0.0;
451 if (collision_points_marker_pub_->get_subscription_count() > 0) {
453 visualization_msgs::msg::Marker marker;
454 marker.header.frame_id = get_parameter(
"base_frame_id").as_string();
455 marker.header.stamp = rclcpp::Time(0, 0);
456 marker.ns =
"collision_points_" + source->getSourceName();
458 marker.type = visualization_msgs::msg::Marker::POINTS;
459 marker.action = visualization_msgs::msg::Marker::ADD;
460 marker.scale.x = 0.02;
461 marker.scale.y = 0.02;
462 marker.color.r = 1.0;
463 marker.color.a = 1.0;
464 marker.lifetime = rclcpp::Duration(0, 0);
465 marker.frame_locked =
true;
467 for (
const auto & point : iter.first->second) {
468 geometry_msgs::msg::Point p;
472 marker.points.push_back(p);
474 marker_array->markers.push_back(marker);
478 if (collision_points_marker_pub_->get_subscription_count() > 0) {
479 collision_points_marker_pub_->publish(std::move(marker_array));
482 for (std::shared_ptr<Polygon> polygon : polygons_) {
483 if (!polygon->getEnabled() || !enabled_) {
486 if (robot_action.action_type == STOP) {
492 polygon->updatePolygon(cmd_vel_in);
494 const ActionType at = polygon->getActionType();
495 if (at == STOP || at == SLOWDOWN || at == LIMIT) {
497 if (processStopSlowdownLimit(
498 polygon, sources_collision_points_map, cmd_vel_in, robot_action))
500 action_polygon = polygon;
502 }
else if (at == APPROACH) {
504 if (processApproach(polygon, sources_collision_points_map, cmd_vel_in, robot_action)) {
505 action_polygon = polygon;
510 if ((robot_action.polygon_name != robot_action_prev_.polygon_name) && enabled_) {
512 notifyActionState(robot_action, action_polygon);
516 publishVelocity(robot_action, header);
521 robot_action_prev_ = robot_action;
524 bool CollisionMonitor::processStopSlowdownLimit(
525 const std::shared_ptr<Polygon> polygon,
526 const std::unordered_map<std::string, std::vector<Point>> & sources_collision_points_map,
528 Action & robot_action)
const
530 if (!polygon->isShapeSet()) {
534 if (polygon->getPointsInside(sources_collision_points_map) >= polygon->getMinPoints()) {
535 if (polygon->getActionType() == STOP) {
537 robot_action.polygon_name = polygon->getName();
538 robot_action.action_type = STOP;
539 robot_action.req_vel.x = 0.0;
540 robot_action.req_vel.y = 0.0;
541 robot_action.req_vel.tw = 0.0;
543 }
else if (polygon->getActionType() == SLOWDOWN) {
544 const Velocity safe_vel = velocity * polygon->getSlowdownRatio();
547 if (safe_vel < robot_action.req_vel) {
548 robot_action.polygon_name = polygon->getName();
549 robot_action.action_type = SLOWDOWN;
550 robot_action.req_vel = safe_vel;
555 const double linear_vel = std::hypot(velocity.x, velocity.y);
560 if (linear_vel != 0.0) {
561 ratio = std::min(ratio, polygon->getLinearLimit() / linear_vel);
563 if (velocity.tw != 0.0) {
564 ratio = std::min(ratio, polygon->getAngularLimit() / std::abs(velocity.tw));
566 ratio = std::clamp(ratio, 0.0, 1.0);
568 safe_vel = velocity * ratio;
571 if (safe_vel < robot_action.req_vel) {
572 robot_action.polygon_name = polygon->getName();
573 robot_action.action_type = LIMIT;
574 robot_action.req_vel = safe_vel;
583 bool CollisionMonitor::processApproach(
584 const std::shared_ptr<Polygon> polygon,
585 const std::unordered_map<std::string, std::vector<Point>> & sources_collision_points_map,
587 Action & robot_action)
const
589 if (!polygon->isShapeSet()) {
594 const double collision_time = polygon->getCollisionTime(sources_collision_points_map, velocity);
595 if (collision_time >= 0.0) {
597 const double change_ratio = collision_time / polygon->getTimeBeforeCollision();
598 const Velocity safe_vel = velocity * change_ratio;
601 if (safe_vel < robot_action.req_vel) {
602 robot_action.polygon_name = polygon->getName();
603 robot_action.action_type = APPROACH;
604 robot_action.req_vel = safe_vel;
612 void CollisionMonitor::notifyActionState(
613 const Action & robot_action,
const std::shared_ptr<Polygon> action_polygon)
const
615 if (robot_action.action_type == STOP) {
616 if (robot_action.polygon_name ==
"invalid source") {
619 "Robot to stop due to invalid source."
620 " Either due to data not published yet, or to lack of new data received within the"
621 " sensor timeout, or if impossible to transform data to base frame");
625 "Robot to stop due to %s polygon",
626 action_polygon->getName().c_str());
628 }
else if (robot_action.action_type == SLOWDOWN) {
631 "Robot to slowdown for %f percents due to %s polygon",
632 action_polygon->getSlowdownRatio() * 100,
633 action_polygon->getName().c_str());
634 }
else if (robot_action.action_type == LIMIT) {
637 "Robot to limit speed due to %s polygon",
638 action_polygon->getName().c_str());
639 }
else if (robot_action.action_type == APPROACH) {
642 "Robot to approach for %f seconds away from collision",
643 action_polygon->getTimeBeforeCollision());
647 "Robot to continue normal operation");
651 std::unique_ptr<nav2_msgs::msg::CollisionMonitorState> state_msg =
652 std::make_unique<nav2_msgs::msg::CollisionMonitorState>();
653 state_msg->polygon_name = robot_action.polygon_name;
654 state_msg->action_type = robot_action.action_type;
656 state_pub_->publish(std::move(state_msg));
660 void CollisionMonitor::publishPolygons()
const
662 for (std::shared_ptr<Polygon> polygon : polygons_) {
663 if (polygon->getEnabled() || !enabled_) {
669 void CollisionMonitor::toggleCMServiceCallback(
670 const std::shared_ptr<rmw_request_id_t>,
671 const std::shared_ptr<nav2_msgs::srv::Toggle::Request> request,
672 std::shared_ptr<nav2_msgs::srv::Toggle::Response> response)
674 enabled_ = request->enable;
676 std::stringstream message;
677 message <<
"Collision monitor toggled " << (enabled_ ?
"on" :
"off") <<
" successfully";
679 response->success =
true;
680 response->message = message.str();
685 #include "rclcpp_components/register_node_macro.hpp"
Collision Monitor ROS2 node.
Velocity for 2D model of motion.