17#include <tracetools/tracetools.h>
34 "Frame ID of local vehicle frame in which the trajectory is planned");
37 "Frame ID of frame that is fixed over time for finding temporal transforms");
40 "Time after which a received route is considered invalid (s) (use -1 for no timeout)");
42 "Time after which a received ego vehicle data is considered invalid (s) (use -1 for no timeout)");
44 "Time after which a received object list is considered invalid (s) (use -1 for no timeout)");
46 "Time after which a received grid map is considered invalid (s) (use -1 for no timeout)");
52 "Reference velocity (m/s); set for all states in the trajectory. Set to '-1.0' to use velocity from route.");
54 "Desired deceleration for braking at stop lines or end of route (m/s^2) - must be < 0.0");
56 "Maximum deceleration for safe-stop trajectories (m/s^2) - must be < 0.0 and <= a_decel");
58 "True: planner will trigger turn signal services; false: planner will not request turn signals");
60 "True: planner will consider grid map; false: planner will ignore grid map");
62 "Minimum occupancy value that is considered as blocked within the grid map",
true,
false,
false,
65 "True: path points falling outside the grid map are treated as blocked; false: points outside "
66 "the grid map are treated as free to drive");
68 "Longitudinal ego-box margin for occupied-cell collision checks; negative values shrink the box "
70 true,
false,
false, -20.0, 20.0, 0.1);
72 "Lateral ego-box margin for occupied-cell collision checks; negative values shrink the box (m)",
73 true,
false,
false, -10.0, 10.0, 0.1);
75 "True: planner will consider traffic lights; false: planner will ignore traffic lights");
77 "Additional distance to stop in front of a stop line (m) (default: 0.0 -> stops with "
78 "front of vehicle at stop line)");
81 "A stop line will be ignored if the front of the vehicle has already passed the stop line by more than this threshold (m)");
83 "True: trajectory will consider forecast of traffic light states; false: trajectory will only "
84 "consider current traffic light state");
86 "True: planner will consider perceived objects on the route; false: planner will ignore objects");
88 "Minimum probability for considering an object prediction branch");
90 "Longitudinal ego-box margin for object conflict detection; negative values shrink the box (m)",
91 true,
false,
false, -20.0, 20.0, 0.1);
93 "Lateral ego-box margin for object conflict detection; negative values shrink the box (m)",
true,
94 false,
false, -10.0, 10.0, 0.1);
96 "Maximum time offset for counting a spatial overlap as interaction (s)");
98 "Velocity decrement per object-avoidance iteration (m/s)",
true,
false,
false, 1e-3, 40.0, 1e-3);
100 "Maximum velocity increase per cycle after hysteresis cleared object conflicts (m/s)",
true,
101 false,
false, 1e-3, 40.0, 1e-3);
103 "Publish standstill if object avoidance would require a lower speed cap (m/s)",
true,
false,
104 false, 0.0, 10.0, 1e-3);
106 "Number of conflict-free cycles required before increasing the remembered object speed cap",
true,
107 false,
false, 0.0, 100.0, 1.0);
109 "Publish RViz markers for the conflict explaining the final speed reduction");
111 "Factor multiplied with the current velocity to determine the lane change distance (m)");
113 "Factor multiplied with the vehicle length to determine the minimum lane change distance (m)");
117 RCLCPP_ERROR(this->get_logger(),
"Invalid parameter: a_decel must be < 0.0");
121 RCLCPP_ERROR(this->get_logger(),
"Invalid parameter: a_max_decel must be < 0.0 and <= a_decel");
128 "Minimum frequency for incoming ego-data messages",
false);
131 "Maximum frequency for incoming ego-data messages",
false);
134 "Minimum acceptable timestamp delta for incoming ego-data messages",
false);
137 "Maximum acceptable timestamp delta for incoming ego-data messages",
false);
140 "Minimum frequency for incoming object-list messages",
false);
143 "Maximum frequency for incoming object-list messages",
false);
144 this->
declareAndLoadParameter(
"diagnostic_updater.topic_diagnostics.object_list.min_acceptable_timestamp_delta",
146 "Minimum acceptable timestamp delta for incoming object-list messages",
false);
147 this->
declareAndLoadParameter(
"diagnostic_updater.topic_diagnostics.object_list.max_acceptable_timestamp_delta",
149 "Maximum acceptable timestamp delta for incoming object-list messages",
false);
158 "Minimum acceptable timestamp delta for incoming route messages",
false);
161 "Maximum acceptable timestamp delta for incoming route messages",
false);
164 "Minimum frequency for incoming grid-map messages",
false);
167 "Maximum frequency for incoming grid-map messages",
false);
170 "Minimum acceptable timestamp delta for incoming grid-map messages",
false);
173 "Maximum acceptable timestamp delta for incoming grid-map messages",
false);
180 this->
declareAndLoadParameter(
"diagnostic_updater.diagnosed_publishers.trajectory.min_acceptable_timestamp_delta",
182 "Minimum acceptable timestamp delta for published trajectory messages",
false);
183 this->
declareAndLoadParameter(
"diagnostic_updater.diagnosed_publishers.trajectory.max_acceptable_timestamp_delta",
185 "Maximum acceptable timestamp delta for published trajectory messages",
false);
195 tf2_buffer_ = std::make_unique<tf2_ros::Buffer>(this->get_clock());
202 pub_ = this->create_publisher<trajectory_planning_msgs::msg::Trajectory>(
"~/trajectory", 10);
203 RCLCPP_INFO(this->get_logger(),
"Publishing to '%s'",
pub_->get_topic_name());
205 this->create_publisher<visualization_msgs::msg::MarkerArray>(
"~/viz/object_interaction_markers", 10);
206 RCLCPP_INFO(this->get_logger(),
"Publishing object interaction markers to '%s'",
212 RCLCPP_INFO(this->get_logger(),
"Publishing trajectory at '%f' hz",
freq_);
215 sub_egoData_ = this->create_subscription<perception_msgs::msg::EgoData>(
217 RCLCPP_INFO(this->get_logger(),
"Subscribed to '%s'",
sub_egoData_->get_topic_name());
219 sub_object_list_ = this->create_subscription<perception_msgs::msg::ObjectList>(
221 RCLCPP_INFO(this->get_logger(),
"Subscribed to '%s'",
sub_object_list_->get_topic_name());
224 sub_route_ = this->create_subscription<route_planning_msgs::msg::Route>(
226 RCLCPP_INFO(this->get_logger(),
"Subscribed to '%s'",
sub_route_->get_topic_name());
228 sub_grid_map_ = this->create_subscription<nav_msgs::msg::OccupancyGrid>(
230 RCLCPP_INFO(this->get_logger(),
"Subscribed to '%s'",
sub_grid_map_->get_topic_name());
245 std::vector<const void*> link_subs = {
static_cast<const void*
>(
sub_egoData_->get_subscription_handle().get()),
246 static_cast<const void*
>(
sub_object_list_->get_subscription_handle().get()),
247 static_cast<const void*
>(
sub_route_->get_subscription_handle().get()),
248 static_cast<const void*
>(
sub_grid_map_->get_subscription_handle().get())};
249 std::vector<const void*> link_pubs = {
static_cast<const void*
>(
pub_->get_publisher_handle().get())};
250 TRACETOOLS_TRACEPOINT(message_link_periodic_async, link_subs.data(), link_subs.size(), link_pubs.data(), link_pubs.size());
256 const int ego_data_topic_diagnostic_frequency_window_size =
262 ego_data_topic_diagnostic_frequency_window_size),
266 const int route_topic_diagnostic_frequency_window_size =
272 route_topic_diagnostic_frequency_window_size),
277 const int object_list_topic_diagnostic_frequency_window_size =
283 object_list_topic_diagnostic_frequency_window_size),
289 const int grid_map_topic_diagnostic_frequency_window_size =
295 grid_map_topic_diagnostic_frequency_window_size),
300 const int diagnosed_publisher_frequency_window_size =
302 diagnosed_publisher_ = std::make_unique<diagnostic_updater::DiagnosedPublisher<trajectory_planning_msgs::msg::Trajectory>>(
306 diagnosed_publisher_frequency_window_size),
324 RCLCPP_INFO(this->get_logger(),
"Received first ego data message, initialized global variable");
336 RCLCPP_INFO(this->get_logger(),
"Received first object list message, initialized global variable");
352 RCLCPP_INFO(this->get_logger(),
"Received new route message, initialized global variable");
366 RCLCPP_INFO(this->get_logger(),
"Received first grid map message, initialized global variable");
373 setHealth(diagnostic_msgs::msg::DiagnosticStatus::STALE,
"No ego data received yet",
382 "EgoData is older than " + std::to_string(
ego_data_timeout_) +
" seconds. Skip publishing until fresh ego data arrives.";
383 RCLCPP_DEBUG(this->get_logger(),
"%s", msg.c_str());
384 setHealth(diagnostic_msgs::msg::DiagnosticStatus::WARN, msg,
391 setHealth(diagnostic_msgs::msg::DiagnosticStatus::STALE,
"No route received and no ongoing safe stop",
398 if (perception_msgs::object_access::getStandstill(
ego_data_)) {
402 std::string msg =
"Route is older than " + std::to_string(
route_timeout_) +
403 " seconds and ego vehicle is stationary. Publishing standstill trajectory.";
404 RCLCPP_DEBUG(this->get_logger(),
"%s", msg.c_str());
405 setHealth(diagnostic_msgs::msg::DiagnosticStatus::OK, msg,
412 std::string msg =
"Route is older than " + std::to_string(
route_timeout_) +
413 " seconds but ego vehicle is still moving. Executing safe stop trajectory.";
414 RCLCPP_DEBUG(this->get_logger(),
"%s", msg.c_str());
415 setHealth(diagnostic_msgs::msg::DiagnosticStatus::WARN, msg,
425 std::string msg =
"Received route has no route elements. Publishing standstill trajectory.";
426 RCLCPP_DEBUG(this->get_logger(),
"%s", msg.c_str());
427 setHealth(diagnostic_msgs::msg::DiagnosticStatus::OK, msg,
434 if (perception_msgs::object_access::getStandstill(
ego_data_)) {
437 std::string msg =
"Safe stop finished. Ego vehicle is considered stationary. Publishing standstill trajectory.";
438 RCLCPP_DEBUG(this->get_logger(),
"%s", msg.c_str());
439 setHealth(diagnostic_msgs::msg::DiagnosticStatus::OK, msg,
443 std::string msg =
"No fresh route available, but safe stop already started. Executing safe stop trajectory.";
444 RCLCPP_DEBUG(this->get_logger(),
"%s", msg.c_str());
445 setHealth(diagnostic_msgs::msg::DiagnosticStatus::WARN, msg,
452 if (perception_msgs::object_access::getStandstill(
ego_data_)) {
454 "Grid map is missing, outdated, or invalid and ego vehicle is stationary. Publishing standstill trajectory.";
455 RCLCPP_WARN(this->get_logger(),
"%s", msg.c_str());
456 setHealth(diagnostic_msgs::msg::DiagnosticStatus::WARN, msg,
461 std::string msg =
"Grid map is missing, outdated, or invalid. Executing safe stop trajectory.";
462 RCLCPP_WARN(this->get_logger(),
"%s", msg.c_str());
463 setHealth(diagnostic_msgs::msg::DiagnosticStatus::WARN, msg,
469 setHealth(diagnostic_msgs::msg::DiagnosticStatus::OK,
"Input information up to date. Following route.",
475 if (timeout == -1.0) {
478 return (stamp - rclcpp::Time(header.stamp)) > rclcpp::Duration::from_seconds(timeout);
491 rclcpp::Time begin = rclcpp::Clock(RCL_SYSTEM_TIME).now();
493 trajectory_planning_msgs::msg::Trajectory tra;
494 tra.header.stamp = stamp;
509 RCLCPP_DEBUG(this->get_logger(),
"Executing safe stop.");
520 throw std::runtime_error(
"createTrajectory called for non-publish state");
526 }
catch (tf2::TransformException& ex) {
528 "' is not available: " + std::string(ex.what()));
532 rclcpp::Time end = rclcpp::Clock(RCL_SYSTEM_TIME).now();
533 RCLCPP_DEBUG(this->get_logger(),
"Trajectory creation took %f ms", (end - begin).seconds() * 1e3);
538 const std_msgs::msg::Header& target_header) {
539 int type_id = trajectory_planning_msgs::REFERENCE::TYPE_ID;
540 trajectory_planning_msgs::msg::Trajectory tra;
541 trajectory_planning_msgs::trajectory_access::initializeTrajectory(tra, type_id, 1);
542 tra.header = target_header;
543 trajectory_planning_msgs::trajectory_access::setStandstill(tra,
true);
551 if (usable_path.
points.empty()) {
554 std::string msg =
"No usable forward path remains. Publishing standstill trajectory.";
555 RCLCPP_WARN(this->get_logger(),
"%s", msg.c_str());
563 const auto n_states_size =
static_cast<size_t>(
n_states_);
564 if (n_states_size < path_points.size()) {
565 path_points.resize(n_states_size);
568 int type_id = trajectory_planning_msgs::REFERENCE::TYPE_ID;
569 trajectory_planning_msgs::msg::Trajectory tra;
570 trajectory_planning_msgs::trajectory_access::initializeTrajectory(tra, type_id,
n_states_);
571 tra.header = usable_path.
header;
574 const size_t idx =
static_cast<size_t>(i) < path_points.size() ?
static_cast<size_t>(i) : path_points.size() - 1;
575 trajectory_planning_msgs::trajectory_access::setT(tra,
dt_ * i, i);
576 trajectory_planning_msgs::trajectory_access::setX(tra, path_points[idx].position.x(), i);
577 trajectory_planning_msgs::trajectory_access::setY(tra, path_points[idx].position.y(), i);
578 trajectory_planning_msgs::trajectory_access::setV(tra, path_points[idx].v, i);
579 RCLCPP_DEBUG(this->get_logger(),
"Debug: i: %d, t: %f, x: %f, y: %f, v: %f, s: %f", i,
dt_ * i,
580 path_points[idx].position.x(), path_points[idx].position.y(), path_points[idx].v, path_points[idx].s);
583 trajectory_planning_msgs::trajectory_access::setStandstill(tra,
false);
584 RCLCPP_DEBUG(this->get_logger(),
"Standstill = %d", tra.standstill);
589 double current_velocity = perception_msgs::object_access::getVelocityMagnitude(
ego_data_);
594 if (safe_stop_path.
points.empty()) {
597 "No latest path available. Initialize safe stop along ego heading. Current velocity: %f m/s, safe stop distance: %f m",
601 RCLCPP_WARN(this->get_logger(),
"Initialize safe stop along latest path. Current velocity: %f m/s, safe stop distance: %f m",
609 RCLCPP_DEBUG(this->get_logger(),
"Default case: route is up to date, creating path from route.");
613 route_planning_msgs::msg::Route tf_route =
route_;
616 tf_route =
tf2_buffer_->transform(
route_, target_header.frame_id, tf2_ros::fromMsg(target_header.stamp),
618 }
catch (tf2::TransformException& ex) {
619 std::string msg =
"Route transformation is not available: " + std::string(ex.what()) +
".";
621 RCLCPP_WARN(this->get_logger(),
"%s", msg.c_str());
627 std::map<uint64_t, uint64_t> lane_change_indices_map;
645 std::map<uint64_t, uint64_t>& lane_change_indices_map) {
646 double t_total = 0.0;
647 RCLCPP_DEBUG(this->get_logger(),
"Number of remaining route elements: %zu",
648 tf_route.destination_route_element_idx - tf_route.current_route_element_idx);
650 {
"RemainingRouteElements", std::to_string(tf_route.destination_route_element_idx - tf_route.current_route_element_idx)});
651 for (
size_t j = tf_route.current_route_element_idx; j < tf_route.destination_route_element_idx; ++j) {
652 const auto& route_element = tf_route.route_elements[j];
653 if (!route_element.is_enriched) {
654 RCLCPP_DEBUG(this->get_logger(),
"Route element %zu is not enriched. Skipping.", j);
658 const auto& suggested_lane = route_planning_msgs::route_access::getSuggestedLaneElement(route_element);
661 Eigen::Vector2d(suggested_lane.reference_pose.position.x, suggested_lane.reference_pose.position.y);
662 simple_path_point.
s = route_element.s;
665 simple_path_point.
v = suggested_lane.speed_limit / 3.6;
669 double v_average = (route_plan.
path.
points.back().v + simple_path_point.
v) / 2.0;
671 if (v_average != 0.0) {
672 dt = (simple_path_point.
s - route_plan.
path.
points.back().s) / v_average;
675 std::string msg =
"Negative time difference " + std::to_string(dt) +
676 " between points at s=" + std::to_string(route_plan.
path.
points.back().s) +
677 " and s=" + std::to_string(simple_path_point.
s) +
". Could lead to unexpected behavior.";
679 RCLCPP_WARN(this->get_logger(),
"%s", msg.c_str());
684 if (j == tf_route.current_route_element_idx) {
688 if (route_element.will_change_suggested_lane &&
699 route_plan.
path.
points.push_back(simple_path_point);
701 if (j == tf_route.destination_route_element_idx - 1) {
703 destination_point.
position = Eigen::Vector2d(tf_route.destination.x, tf_route.destination.y);
704 destination_point.
s = simple_path_point.
s + (destination_point.
position - simple_path_point.
position).norm();
705 destination_point.
v = simple_path_point.
v;
706 route_plan.
path.
points.push_back(destination_point);
717 size_t route_element_idx,
718 std::map<uint64_t, uint64_t>& lane_change_indices_map,
719 uint8_t& suggested_turn_signal) {
720 if (route_element_idx + 1 >= tf_route.route_elements.size()) {
721 std::string msg =
"Route element " + std::to_string(route_element_idx) +
" is the last element. Cannot change lane.";
723 RCLCPP_WARN(this->get_logger(),
"%s", msg.c_str());
727 const size_t i_end = route_element_idx + 1;
728 const auto& route_element = tf_route.route_elements[route_element_idx];
729 size_t current_lane_idx = route_element.suggested_lane_idx;
730 int lane_change_direction = 0;
732 lane_change_direction =
733 route_planning_msgs::route_access::getLaneChangeDirection(route_element, tf_route.route_elements[route_element_idx + 1]);
734 }
catch (
const std::exception& ex) {
735 RCLCPP_WARN(this->get_logger(),
"Could not determine lane change direction at route element %zu: %s", route_element_idx,
739 if (lane_change_direction < 0) {
740 suggested_turn_signal = route_planning_msgs::msg::LaneElement::SUGGESTED_TURN_SIGNAL_LEFT;
741 }
else if (lane_change_direction > 0) {
742 suggested_turn_signal = route_planning_msgs::msg::LaneElement::SUGGESTED_TURN_SIGNAL_RIGHT;
745 double ego_velocity = perception_msgs::object_access::getVelocityMagnitude(
ego_data_);
746 double lane_change_distance =
748 RCLCPP_DEBUG(this->get_logger(),
"Lane change direction: %d, lane change distance: %f", lane_change_direction,
749 lane_change_distance);
750 if (lane_change_direction == 0) {
751 RCLCPP_WARN(this->get_logger(),
752 "Route element %zu is marked as lane change, but suggested lane does not change. Ignoring lane change marker.",
758 size_t i_start = route_element_idx;
759 while (ds < lane_change_distance && i_start > 0) {
760 if (
auto result = route_planning_msgs::route_access::getPrecedingLaneElementIdx(current_lane_idx,
761 tf_route.route_elements[i_start - 1])) {
762 current_lane_idx = *result;
765 "No preceding lane element found for route element " + std::to_string(i_start) +
". Cannot extend lane change.";
766 RCLCPP_WARN(this->get_logger(),
"%s", msg.c_str());
770 if (i_start >= 2 && tf_route.route_elements[i_start - 2].will_change_suggested_lane) {
771 std::string msg =
"Found previous lane change in route element " + std::to_string(i_start - 2) +
772 ". Could not extend lane change over this element.";
773 RCLCPP_WARN(this->get_logger(),
"%s", msg.c_str());
777 if (!route_planning_msgs::route_access::hasAdjacentLane(tf_route.route_elements[i_start - 1], current_lane_idx,
778 lane_change_direction)) {
779 std::string msg =
"No adjacent lane found for route element " + std::to_string(i_start - 1) +
".";
780 RCLCPP_WARN(this->get_logger(),
"%s", msg.c_str());
784 ds += std::abs(tf_route.route_elements[i_start].s - tf_route.route_elements[i_start - 1].s);
788 if (!tf_route.route_elements[i_start].is_enriched || !tf_route.route_elements[i_end].is_enriched) {
790 "Not enough enriched route elements (" + std::to_string(i_start) +
", " + std::to_string(i_end) +
") for lane change.";
791 RCLCPP_WARN(this->get_logger(),
"%s", msg.c_str());
796 lane_change_indices_map[route_element_idx] = i_start;
801 size_t route_element_idx,
802 const route_planning_msgs::msg::LaneElement& suggested_lane,
806 double& offset_to_stop_line) {
807 const auto& route_element = tf_route.route_elements[route_element_idx];
808 const auto& reg_elems =
809 route_planning_msgs::route_access::getRegulatoryElementsOfLaneElement(suggested_lane, route_element.regulatory_elements);
810 for (
size_t k = 0; k < reg_elems.size(); ++k) {
811 if (reg_elems[k].type != route_planning_msgs::msg::RegulatoryElement::TYPE_TRAFFIC_LIGHT) {
814 if (reg_elems[k].meta_value == route_planning_msgs::msg::RegulatoryElement::META_VALUE_MOVEMENT_ALLOWED &&
819 offset_to_stop_line =
821 double dt_offset_to_stop_line = 0.0;
822 if (simple_path_point.
v != 0.0) {
823 dt_offset_to_stop_line = offset_to_stop_line / simple_path_point.
v;
825 if (dt_offset_to_stop_line <= 0.0) {
826 std::string msg =
"Negative time difference 'dt_offset_to_stop_line' (" + std::to_string(dt_offset_to_stop_line) +
827 " s) for traffic light at route element " + std::to_string(route_element_idx) +
828 ". Could lead to unexpected behavior.";
830 RCLCPP_WARN(this->get_logger(),
"%s", msg.c_str());
834 double validity_duration =
835 rclcpp::Time(reg_elems[k].validity_stamp).seconds() - rclcpp::Time(
route_.header.stamp).seconds();
836 if (validity_duration < (t_total - dt_offset_to_stop_line)) {
837 if (reg_elems[k].meta_value == route_planning_msgs::msg::RegulatoryElement::META_VALUE_MOVEMENT_ALLOWED) {
843 if (reg_elems[k].meta_value == route_planning_msgs::msg::RegulatoryElement::META_VALUE_MOVEMENT_ALLOWED) {
853 double distance_to_stop_point = tf_route.route_elements[route_element_idx].s -
854 tf_route.route_elements[tf_route.current_route_element_idx].s - offset_to_stop_line;
855 double v_ego = perception_msgs::object_access::getVelocityMagnitude(
ego_data_);
856 double min_distance_to_stop = -0.5 * std::pow(v_ego, 2) /
a_max_decel_;
859 "Traffic light stop point is behind ego vehicle (distance to stop point: " + std::to_string(distance_to_stop_point) +
860 " m). Ignoring traffic light.";
862 RCLCPP_WARN(this->get_logger(),
"%s", msg.c_str());
864 }
else if ((distance_to_stop_point < min_distance_to_stop) && stop_at_end) {
866 "Traffic light requires stop, but distance to stop point is smaller than minimum distance to stop. Ignoring traffic "
869 RCLCPP_WARN(this->get_logger(),
"%s", msg.c_str());
876 const route_planning_msgs::msg::Route& tf_route,
877 const std::vector<SimplePathPoint>& route_points,
878 const std::map<uint64_t, uint64_t>& lane_change_indices_map) {
880 std::vector<SimplePathPoint> merged_points;
881 for (
const auto& lane_change_indices : lane_change_indices_map) {
882 uint64_t lane_change_idx_route = lane_change_indices.first;
883 uint64_t start_idx_route = lane_change_indices.second;
885 start_idx_route > tf_route.current_route_element_idx ? start_idx_route - tf_route.current_route_element_idx : 0;
886 size_t end_idx = lane_change_idx_route + 1 > tf_route.current_route_element_idx
887 ? lane_change_idx_route + 1 - tf_route.current_route_element_idx
890 if (start_idx < current || current > route_points.size()) {
891 RCLCPP_WARN(this->get_logger(),
"Skipping overlapping lane change window (%zu, %zu), current path index: %zu.", start_idx,
896 start_idx = std::min(start_idx, route_points.size());
897 const auto current_offset =
static_cast<std::vector<SimplePathPoint>::difference_type
>(current);
898 const auto start_offset =
static_cast<std::vector<SimplePathPoint>::difference_type
>(start_idx);
899 merged_points.insert(merged_points.end(), route_points.begin() + current_offset, route_points.begin() + start_offset);
900 std::vector<SimplePathPoint> lane_change_points =
generateLaneChangePath(start_idx_route, lane_change_idx_route, tf_route);
901 merged_points.insert(merged_points.end(), lane_change_points.begin(), lane_change_points.end());
902 current = std::min(end_idx + 1, route_points.size());
904 if (current <= route_points.size()) {
905 const auto current_offset =
static_cast<std::vector<SimplePathPoint>::difference_type
>(current);
906 merged_points.insert(merged_points.end(), route_points.begin() + current_offset, route_points.end());
908 return merged_points;
912 if (suggested_turn_signal == route_planning_msgs::msg::LaneElement::SUGGESTED_TURN_SIGNAL_NONE &&
915 auto request = std::make_shared<std_srvs::srv::SetBool::Request>();
916 request->data =
false;
920 }
else if (suggested_turn_signal == route_planning_msgs::msg::LaneElement::SUGGESTED_TURN_SIGNAL_LEFT &&
922 auto request = std::make_shared<std_srvs::srv::SetBool::Request>();
923 request->data =
true;
925 }
else if (suggested_turn_signal == route_planning_msgs::msg::LaneElement::SUGGESTED_TURN_SIGNAL_RIGHT &&
927 auto request = std::make_shared<std_srvs::srv::SetBool::Request>();
928 request->data =
true;
930 }
else if (suggested_turn_signal == route_planning_msgs::msg::LaneElement::SUGGESTED_TURN_SIGNAL_HAZARD &&
932 auto request = std::make_shared<std_srvs::srv::SetBool::Request>();
933 request->data =
true;
936 std::string msg =
"Cannot apply suggested turn signal " + std::to_string(suggested_turn_signal) +
937 " because the corresponding indicator service is not ready.";
938 RCLCPP_WARN(this->get_logger(),
"%s", msg.c_str());
944 while (!path.
points.empty() && path.
points[0].position.x() < 0.0) {
951 double start_s = safe_stop_path.
points[0].s;
952 for (
size_t i = 0; i < safe_stop_path.
points.size(); i++) {
953 if (safe_stop_path.
points[i].s > start_s + safe_stop_distance) {
955 const auto erase_offset =
static_cast<std::vector<SimplePathPoint>::difference_type
>(i);
956 safe_stop_path.
points.erase(safe_stop_path.
points.begin() + erase_offset, safe_stop_path.
points.end());
961 return safe_stop_path;
965 const double safe_stop_distance,
966 const std_msgs::msg::Header& target_header) {
968 safe_stop_path.
header = ego_data.header;
970 double v_ego = perception_msgs::object_access::getVelocityMagnitude(ego_data);
971 geometry_msgs::msg::Point point = perception_msgs::object_access::getPosition(ego_data);
972 double yaw = perception_msgs::object_access::getYaw(ego_data);
973 Eigen::Vector2d start_position(point.x, point.y);
974 Eigen::Vector2d heading(std::cos(yaw), std::sin(yaw));
976 RCLCPP_WARN(this->get_logger(),
"Initializing minimal safe stop along ego heading. Frame: %s, yaw: %f rad",
977 safe_stop_path.
header.frame_id.c_str(), yaw);
981 safe_stop_path.
points.push_back(start_point);
982 SimplePathPoint mid_point(start_position + heading * (safe_stop_distance / 2.0), safe_stop_distance / 2.0, v_ego);
983 safe_stop_path.
points.push_back(mid_point);
984 SimplePathPoint end_point(start_position + heading * safe_stop_distance, safe_stop_distance, 0.0);
985 safe_stop_path.
points.push_back(end_point);
988 return safe_stop_path;
993 const route_planning_msgs::msg::Route& route) {
994 std::vector<SimplePathPoint> lane_change_path;
995 const size_t end_idx = turn_idx + 1;
996 if (start_idx >= end_idx || end_idx >= route.route_elements.size()) {
997 std::string msg =
"Invalid lane change indices: start_idx=" + std::to_string(start_idx) +
998 ", end_idx=" + std::to_string(end_idx) +
". Cannot generate lane change path.";
999 RCLCPP_WARN(this->get_logger(),
"%s", msg.c_str());
1001 return lane_change_path;
1004 int lane_change_direction = route_planning_msgs::route_access::getLaneChangeDirection(route.route_elements[turn_idx],
1005 route.route_elements[turn_idx + 1]);
1008 for (
size_t i = start_idx; i <= end_idx; ++i) {
1009 if (i < route.current_route_element_idx)
continue;
1011 const auto& route_element = route.route_elements[i];
1012 const auto& suggested_lane = route_planning_msgs::route_access::getSuggestedLaneElement(route_element);
1013 if (i > turn_idx) lane_change_direction = 0;
1014 const auto& adjacent_lane = route_planning_msgs::route_access::getAdjacentLane(
1015 route_element, route.route_elements[i].suggested_lane_idx, lane_change_direction);
1016 Eigen::Vector2d suggested_lane_pos(suggested_lane.reference_pose.position.x, suggested_lane.reference_pose.position.y);
1017 Eigen::Vector2d adjacent_lane_pos(adjacent_lane.reference_pose.position.x, adjacent_lane.reference_pose.position.y);
1018 const double lane_change_progress =
static_cast<double>(i - start_idx) /
static_cast<double>(end_idx - start_idx);
1019 double alpha = 0.5 * (1.0 + std::cos(M_PI * lane_change_progress));
1020 Eigen::Vector2d interpolated_pos = alpha * suggested_lane_pos + (1.0 - alpha) * adjacent_lane_pos;
1021 lane_change_path.push_back(
1022 SimplePathPoint(interpolated_pos, route_element.s, suggested_lane.speed_limit / 3.6));
1025 return lane_change_path;
1033 if (stop_s <= path.front().s) {
1035 stop_point.
s = stop_s;
1036 return {stop_point};
1039 if (stop_s >= path.back().s) {
1043 const auto stop_it =
1044 std::lower_bound(path.begin(), path.end(), stop_s, [](
const auto& point,
double s) { return point.s < s; });
1045 if (stop_it == path.begin()) {
1048 if (stop_it == path.end()) {
1052 std::vector<SimplePathPoint> truncated_path(path.begin(), stop_it);
1053 if (std::abs(stop_it->s - stop_s) <= 1e-6) {
1054 truncated_path.push_back(*stop_it);
1055 return truncated_path;
1058 const auto& previous = *(stop_it - 1);
1059 const double segment_ds = stop_it->s - previous.s;
1060 const double alpha = segment_ds > 1e-9 ? std::clamp((stop_s - previous.s) / segment_ds, 0.0, 1.0) : 0.0;
1062 stop_point.
position = previous.position + alpha * (stop_it->position - previous.position);
1063 stop_point.
s = stop_s;
1064 stop_point.
v = previous.v + alpha * (stop_it->v - previous.v);
1065 truncated_path.push_back(stop_point);
1066 return truncated_path;
1070 if (path.empty())
return;
1073 for (
size_t i = 1; i < path.size(); ++i) {
1074 double ds = (path[i].position - path[i - 1].position).norm();
1075 path[i].s = path[i - 1].s + ds;
1081 double offset_to_stop_line,
1082 const double* speed_cap) {
1083 rclcpp::Time begin = rclcpp::Clock(RCL_SYSTEM_TIME).now();
1085 std::string msg =
"Route is empty. No resampling possible.";
1086 RCLCPP_WARN(this->get_logger(),
"%s", msg.c_str());
1091 std::vector<SimplePathPoint> resampled_path;
1092 double s = path[0].s;
1094 tk::spline x_spline, y_spline;
1096 std::vector<double> s_vector, x_vector, y_vector;
1097 for (
size_t j = 0; j < path.size(); ++j) {
1098 s_vector.push_back(path[j].s);
1099 x_vector.push_back(path[j].position.x());
1100 y_vector.push_back(path[j].position.y());
1102 x_spline.set_points(s_vector, x_vector);
1103 y_spline.set_points(s_vector, y_vector);
1106 while (s < path.back().s) {
1109 for (
size_t j = 0; j < path.size() - 1; ++j) {
1110 if (s >= path[j].s && s <= path[j + 1].s) {
1111 idx =
static_cast<int>(j);
1118 v = path[idx].v + (path[idx + 1].v - path[idx].v) / (path[idx + 1].s - path[idx].s) * (s - path[idx].s);
1120 if (speed_cap !=
nullptr) {
1121 v = std::min(v, std::max(*speed_cap, 0.0));
1124 double distance_to_stop = -0.5 * std::pow(v, 2) /
a_decel_ + offset_to_stop_line;
1125 distance_to_stop = std::max(distance_to_stop, 0.0);
1126 double brake_point = path.back().s - distance_to_stop;
1127 double ds = v *
dt_;
1128 if (s + ds > brake_point && stop_at_end) {
1129 if (s < brake_point) {
1130 double ds_1 = brake_point - s;
1131 double dt_1 = ds_1 / v;
1132 double dt_2 =
dt_ - dt_1;
1133 double ds_2 = std::max(0.5 *
a_decel_ * std::pow(dt_2, 2) + v * dt_2, 0.0);
1136 v = std::sqrt(std::max(std::pow(v, 2) + 2 *
a_decel_ * (s - brake_point), 0.0));
1138 if (ds < 0.0) ds = path.back().s - s;
1144 if (path.size() == 1) {
1145 simple_path_point.
position = path[0].position;
1147 simple_path_point.
position.x() = x_spline(s);
1148 simple_path_point.
position.y() = y_spline(s);
1154 simple_path_point.
position.x() = path[idx].position.x() + (path[idx + 1].position.x() - path[idx].position.x()) /
1155 (path[idx + 1].s - path[idx].s) * (s - path[idx].s);
1156 simple_path_point.
position.y() = path[idx].position.y() + (path[idx + 1].position.y() - path[idx].position.y()) /
1157 (path[idx + 1].s - path[idx].s) * (s - path[idx].s);
1159 RCLCPP_ERROR(this->get_logger(),
"Unsupported interpolation type value %d",
interpolation_type_);
1160 throw std::runtime_error(
"Unsupported interpolation type value");
1162 simple_path_point.
s = s;
1163 simple_path_point.
v = v;
1164 resampled_path.push_back(simple_path_point);
1167 if (v <= 1e-6 && ds <= 1e-6)
break;
1169 if (s == path.back().s && v == 0.0)
break;
1172 rclcpp::Time end = rclcpp::Clock(RCL_SYSTEM_TIME).now();
1173 RCLCPP_DEBUG(this->get_logger(),
"Resampling route took %f ms", (end - begin).seconds() * 1e3);
1177 if (!resampled_path.empty() && (offset_to_stop_line > 0.0 || (speed_cap !=
nullptr && *speed_cap <= 1e-6))) {
1178 stop_point = resampled_path.back();
1181 if (resampled_path.empty() || resampled_path.back().s < stop_point.
s || resampled_path.back().v != 0.0) {
1182 resampled_path.push_back(stop_point);
1186 return resampled_path;
1194 const rclcpp::Time stamp = now();
1197 std_msgs::msg::Header marker_header;
1198 marker_header.stamp = stamp;
1205 trajectory_planning_msgs::msg::Trajectory msg =
createTrajectory(planner_state, stamp);
1207 RCLCPP_DEBUG(this->get_logger(),
"Published Trajectory!");
1208 }
catch (
const std::runtime_error& e) {
1209 std::string msg =
"Error while creating trajectory: " + std::string(e.what());
1210 RCLCPP_ERROR(this->get_logger(),
"%s", msg.c_str());
1225 rclcpp::init(argc, argv);
1226 rclcpp::spin(std::make_shared<simple_planner::SimplePlannerNode>());
void setup()
Sets up subscribers, publishers, etc. to configure the node.
int object_velocity_release_hysteresis_cycles_
SimplePath buildSafeStopPath(const std_msgs::msg::Header &target_header)
Builds the initial safe-stop path for the current cycle.
double object_lateral_safety_distance_
std::unique_ptr< diagnostic_updater::TopicDiagnostic > ego_data_topic_diagnostic_
void declareAndLoadParameter(const std::string &name, T ¶m, const std::string &description, const bool add_to_auto_reconfigurable_params=true, const bool is_required=false, const bool read_only=false, const std::optional< double > &from_value=std::nullopt, const std::optional< double > &to_value=std::nullopt, const std::optional< double > &step_value=std::nullopt, const std::string &additional_constraints="")
Declares a ROS parameter, loads its value and optionally registers it for runtime updates.
static void trimPathBehindEgo(SimplePath &path)
Removes path points that lie behind the ego vehicle in vehicle frame.
void applyGridMapConstraints(const std_msgs::msg::Header &target_header, std::vector< SimplePathPoint > &base_path_points, FollowRoutePlan &route_plan)
Applies occupancy-grid-based stop constraints to the base route path.
bool consider_traffic_lights_
double object_velocity_release_step_
std::optional< double > safe_stop_distance_
bool hasValidGridMap(const rclcpp::Time &stamp) const
Checks whether a fresh, structurally valid grid map is currently available.
double object_standstill_speed_threshold_
double ignore_stop_line_threshold_
std::vector< SimplePathPoint > resamplePath(const std::vector< SimplePathPoint > &path, bool stop_at_end, double offset_to_stop_line=0.0, const double *speed_cap=nullptr)
Resamples a path into trajectory time steps and applies optional stopping behavior.
rclcpp::Publisher< trajectory_planning_msgs::msg::Trajectory >::SharedPtr pub_
rclcpp::Client< std_srvs::srv::SetBool >::SharedPtr hazard_lights_service_client_
int grid_occupied_threshold_
static void recalculateS(std::vector< SimplePathPoint > &path)
Recomputes accumulated path distance from point positions.
diagnostic_updater::Updater diagnostic_updater_
Diagnostic updater.
std::shared_ptr< tf2_ros::TransformListener > tf2_listener_
bool tryRegisterLaneChange(const route_planning_msgs::msg::Route &tf_route, size_t route_element_idx, std::map< uint64_t, uint64_t > &lane_change_indices_map, uint8_t &suggested_turn_signal)
Detects and stores the start/end window of a lane change.
std::unique_ptr< tf2_ros::Buffer > tf2_buffer_
double offset_to_stop_line_
rclcpp::Subscription< perception_msgs::msg::ObjectList >::SharedPtr sub_object_list_
void applyObjectConstraints(const std_msgs::msg::Header &target_header, const std::vector< SimplePathPoint > &base_path_points, FollowRoutePlan &route_plan)
Applies trajectory-based object conflict constraints to a follow-route plan.
double object_interaction_time_window_
double lane_change_distance_factor_
std::unique_ptr< diagnostic_updater::TopicDiagnostic > grid_map_topic_diagnostic_
void publishTimerCallback()
This callback is invoked every period seconds by the timer.
double object_velocity_reduction_step_
TopicDiagnosticConfig grid_map_topic_diagnostic_config_
std::vector< SimplePathPoint > generateLaneChangePath(size_t start_idx, size_t turn_idx, const route_planning_msgs::msg::Route &route)
Generates interpolated points for a lane-change section of the route.
perception_msgs::msg::ObjectList object_list_
TopicDiagnosticConfig ego_data_topic_diagnostic_config_
std::unique_ptr< diagnostic_updater::DiagnosedPublisher< trajectory_planning_msgs::msg::Trajectory > > diagnosed_publisher_
FollowRoutePlan buildRoutePlan(const std_msgs::msg::Header &target_header)
Builds the complete route-following plan including stop and turn information.
double trajectory_horizon_
void appendRoutePoints(const route_planning_msgs::msg::Route &tf_route, FollowRoutePlan &route_plan, std::map< uint64_t, uint64_t > &lane_change_indices_map)
Appends route-derived path points and stop metadata for the follow-route case.
double min_prediction_prob_
rcl_interfaces::msg::SetParametersResult parametersCallback(const std::vector< rclcpp::Parameter > ¶meters)
Handles reconfiguration when a parameter value is changed.
TopicDiagnosticConfig object_list_topic_diagnostic_config_
void health(diagnostic_updater::DiagnosticStatusWrapper &stat)
Function called by diagnostic updater to populate diagnostics status.
bool consider_out_of_grid_
nav_msgs::msg::OccupancyGrid grid_map_
rclcpp::Client< std_srvs::srv::SetBool >::SharedPtr right_turn_indicator_service_client_
static std::vector< SimplePathPoint > truncatePathAtS(const std::vector< SimplePathPoint > &path, double stop_s)
Returns a path ending exactly at the requested accumulated path coordinate.
std::unique_ptr< diagnostic_updater::TopicDiagnostic > object_list_topic_diagnostic_
static std::string plannerStateToString(const PlannerState &state)
Converts a PlannerState enum to a string representation.
rclcpp::Publisher< visualization_msgs::msg::MarkerArray >::SharedPtr object_interaction_marker_pub_
rclcpp::Subscription< perception_msgs::msg::EgoData >::SharedPtr sub_egoData_
static std::string turnSignalToString(const uint8_t &turn_signal)
Converts a turn signal value to a string representation.
double grid_lateral_safety_distance_
uint8_t interpolation_type_
std::unique_ptr< diagnostic_updater::TopicDiagnostic > route_topic_diagnostic_
std::string vehicle_frame_id_
double grid_longitudinal_safety_distance_
void objectListCallback(const perception_msgs::msg::ObjectList::UniquePtr msg)
Stores the latest perceived object list including object predictions.
rclcpp::TimerBase::SharedPtr publish_timer_
OnSetParametersCallbackHandle::SharedPtr parameters_callback_
Callback handle for dynamic parameter reconfiguration.
void routeCallback(const route_planning_msgs::msg::Route::UniquePtr msg)
Stores the latest route message and extracts the route path.
rclcpp::Subscription< route_planning_msgs::msg::Route >::SharedPtr sub_route_
bool publish_object_interaction_markers_
SimplePath calculateSafeStopAlongEgoHeading(const perception_msgs::msg::EgoData &ego_data, const double safe_stop_distance, const std_msgs::msg::Header &target_header)
Creates a minimal safe-stop path along the current ego heading.
void setHealth(const unsigned char status, const std::string &msg, const std::map< std::string, std::string > &key_value_pairs={})
Sets the health information.
bool trigger_turn_signals_
void updateForTrafficLights(const route_planning_msgs::msg::Route &tf_route, size_t route_element_idx, const route_planning_msgs::msg::LaneElement &suggested_lane, const SimplePathPoint &simple_path_point, double t_total, bool &stop_at_end, double &offset_to_stop_line)
Updates stop-at-end and stop-line offset state for traffic-light regulatory elements.
TopicDiagnosticConfig diagnosed_publisher_config_
struct simple_planner::SimplePlannerNode::DiagnosticStatus health_
TopicDiagnosticConfig route_topic_diagnostic_config_
SimplePath calculateSafeStopAlongRoute(const SimplePath &path, const double safe_stop_distance)
Truncates and resamples an existing path to stop within the safe-stop distance.
void applyIndicatorRequest(uint8_t suggested_turn_signal)
Requests the appropriate indicator state for the current route plan.
void egoDataCallback(const perception_msgs::msg::EgoData::UniquePtr msg)
Stores the latest ego data message.
double lane_change_min_distance_factor_
rclcpp::Client< std_srvs::srv::SetBool >::SharedPtr left_turn_indicator_service_client_
rclcpp::Subscription< nav_msgs::msg::OccupancyGrid >::SharedPtr sub_grid_map_
PlannerState determinePlannerState(const rclcpp::Time &stamp)
Determines the current planner state from input freshness and route availability.
void clearObjectInteractionMarkers(const std_msgs::msg::Header &target_header)
Deletes the currently published object interaction markers.
bool consider_future_states_
double object_longitudinal_safety_distance_
SimplePath transformPath(const SimplePath &path, const std_msgs::msg::Header &target_header)
Transforms a simple path into the requested target frame and timestamp.
trajectory_planning_msgs::msg::Trajectory createTrajectory(PlannerState state, const rclcpp::Time &stamp)
Creates a trajectory for the already determined planner state.
route_planning_msgs::msg::Route route_
perception_msgs::msg::EgoData ego_data_
trajectory_planning_msgs::msg::Trajectory buildTrajectoryFromSimplePath(const SimplePath &path)
Builds a trajectory message from a simple path.
static bool requiresTransform(const std_msgs::msg::Header &source_header, const std_msgs::msg::Header &target_header)
Checks whether a transform between two stamped frames is required.
std::vector< SimplePathPoint > mergeLaneChangeSegments(const route_planning_msgs::msg::Route &tf_route, const std::vector< SimplePathPoint > &route_points, const std::map< uint64_t, uint64_t > &lane_change_indices_map)
Merges interpolated lane-change segments into the base route path.
SimplePlannerNode()
Creates a SimplePlannerNode node.
static trajectory_planning_msgs::msg::Trajectory buildStandstillTrajectory(const std_msgs::msg::Header &target_header)
Creates a standstill trajectory for the current planning cycle.
std::string trajectory_frame_id_
std::string fixed_over_time_frame_id_
void resetObjectState(const std_msgs::msg::Header &target_header)
Resets the remembered object speed cap / hysteresis state and clears interaction markers.
void gridMapCallback(const nav_msgs::msg::OccupancyGrid::UniquePtr msg)
Stores the latest occupancy grid map message.
static bool isMessageOutdated(const std_msgs::msg::Header &header, double timeout, const rclcpp::Time &stamp)
Checks whether an input message is older than the configured timeout.
Namespace for simple_planner package.
int main(int argc, char *argv[])
Starts the simple planner ROS node.
std::vector< SimplePathPoint > points
std_msgs::msg::Header header
std::map< std::string, std::string > key_value_pairs
uint8_t suggested_turn_signal
double offset_to_stop_line
std::string reason_to_stop
double max_acceptable_timestamp_delta
Maximum acceptable difference between message timestamp and receipt time (in seconds)
double min_frequency
Minimum acceptable frequency.
double max_frequency
Maximum acceptable frequency.
double min_acceptable_timestamp_delta
Minimum acceptable difference between message timestamp and receipt time (in seconds)