simple_planner v1.4.0
Loading...
Searching...
No Matches
simple_planner.cpp
Go to the documentation of this file.
1// Copyright Institute for Automotive Engineering (ika), RWTH Aachen University
2// SPDX-License-Identifier: Apache-2.0
3
4#include <algorithm>
5#include <array>
6#include <chrono>
7#include <cmath>
8#include <functional>
9#include <optional>
10#include <sstream>
11#include <thread>
12#include <vector>
13
14#include <Eigen/Dense>
15
16#include <tk/spline.h>
17#include <tracetools/tracetools.h>
18
21
26namespace simple_planner {
27
32SimplePlannerNode::SimplePlannerNode() : Node("simple_planner_node") {
33 this->declareAndLoadParameter("vehicle_frame_id", vehicle_frame_id_,
34 "Frame ID of local vehicle frame in which the trajectory is planned");
35 this->declareAndLoadParameter("trajectory_frame_id", trajectory_frame_id_, "Frame ID of published reference trajectory");
36 this->declareAndLoadParameter("fixed_over_time_frame_id", fixed_over_time_frame_id_,
37 "Frame ID of frame that is fixed over time for finding temporal transforms");
38 this->declareAndLoadParameter("frequency", freq_, "Frequency of reference planning cycle (Hz)");
39 this->declareAndLoadParameter("route_timeout", route_timeout_,
40 "Time after which a received route is considered invalid (s) (use -1 for no timeout)");
41 this->declareAndLoadParameter("ego_data_timeout", ego_data_timeout_,
42 "Time after which a received ego vehicle data is considered invalid (s) (use -1 for no timeout)");
43 this->declareAndLoadParameter("object_timeout", object_timeout_,
44 "Time after which a received object list is considered invalid (s) (use -1 for no timeout)");
45 this->declareAndLoadParameter("grid_map_timeout", grid_map_timeout_,
46 "Time after which a received grid map is considered invalid (s) (use -1 for no timeout)");
47 this->declareAndLoadParameter("trajectory_horizon", trajectory_horizon_, "Time horizon of the reference trajectory (s)");
48 this->declareAndLoadParameter("n_states", n_states_, "Number of states in the output trajectory");
49 this->declareAndLoadParameter("interpolation_type", interpolation_type_, "0: linear, 1: cubic spline");
51 "v_ref", v_ref_,
52 "Reference velocity (m/s); set for all states in the trajectory. Set to '-1.0' to use velocity from route.");
53 this->declareAndLoadParameter("a_decel", a_decel_,
54 "Desired deceleration for braking at stop lines or end of route (m/s^2) - must be < 0.0");
55 this->declareAndLoadParameter("a_max_decel", a_max_decel_,
56 "Maximum deceleration for safe-stop trajectories (m/s^2) - must be < 0.0 and <= a_decel");
57 this->declareAndLoadParameter("trigger_turn_signals", trigger_turn_signals_,
58 "True: planner will trigger turn signal services; false: planner will not request turn signals");
59 this->declareAndLoadParameter("consider_grid_map", consider_grid_map_,
60 "True: planner will consider grid map; false: planner will ignore grid map");
61 this->declareAndLoadParameter("grid_occupied_threshold", grid_occupied_threshold_,
62 "Minimum occupancy value that is considered as blocked within the grid map", true, false, false,
63 0.0, 100.0, 1.0);
64 this->declareAndLoadParameter("consider_out_of_grid", consider_out_of_grid_,
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");
67 this->declareAndLoadParameter("grid_longitudinal_safety_distance", grid_longitudinal_safety_distance_,
68 "Longitudinal ego-box margin for occupied-cell collision checks; negative values shrink the box "
69 "(m)",
70 true, false, false, -20.0, 20.0, 0.1);
71 this->declareAndLoadParameter("grid_lateral_safety_distance", grid_lateral_safety_distance_,
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);
74 this->declareAndLoadParameter("consider_traffic_lights", consider_traffic_lights_,
75 "True: planner will consider traffic lights; false: planner will ignore traffic lights");
76 this->declareAndLoadParameter("offset_to_stop_line", offset_to_stop_line_,
77 "Additional distance to stop in front of a stop line (m) (default: 0.0 -> stops with "
78 "front of vehicle at stop line)");
80 "ignore_stop_line_threshold", ignore_stop_line_threshold_,
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)");
82 this->declareAndLoadParameter("consider_future_states", consider_future_states_,
83 "True: trajectory will consider forecast of traffic light states; false: trajectory will only "
84 "consider current traffic light state");
85 this->declareAndLoadParameter("consider_objects", consider_objects_,
86 "True: planner will consider perceived objects on the route; false: planner will ignore objects");
87 this->declareAndLoadParameter("min_prediction_prob", min_prediction_prob_,
88 "Minimum probability for considering an object prediction branch");
89 this->declareAndLoadParameter("object_longitudinal_safety_distance", object_longitudinal_safety_distance_,
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);
92 this->declareAndLoadParameter("object_lateral_safety_distance", object_lateral_safety_distance_,
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);
95 this->declareAndLoadParameter("object_interaction_time_window", object_interaction_time_window_,
96 "Maximum time offset for counting a spatial overlap as interaction (s)");
97 this->declareAndLoadParameter("object_velocity_reduction_step", object_velocity_reduction_step_,
98 "Velocity decrement per object-avoidance iteration (m/s)", true, false, false, 1e-3, 40.0, 1e-3);
99 this->declareAndLoadParameter("object_velocity_release_step", object_velocity_release_step_,
100 "Maximum velocity increase per cycle after hysteresis cleared object conflicts (m/s)", true,
101 false, false, 1e-3, 40.0, 1e-3);
102 this->declareAndLoadParameter("object_standstill_speed_threshold", object_standstill_speed_threshold_,
103 "Publish standstill if object avoidance would require a lower speed cap (m/s)", true, false,
104 false, 0.0, 10.0, 1e-3);
105 this->declareAndLoadParameter("object_velocity_release_hysteresis_cycles", object_velocity_release_hysteresis_cycles_,
106 "Number of conflict-free cycles required before increasing the remembered object speed cap", true,
107 false, false, 0.0, 100.0, 1.0);
108 this->declareAndLoadParameter("publish_object_interaction_markers", publish_object_interaction_markers_,
109 "Publish RViz markers for the conflict explaining the final speed reduction");
110 this->declareAndLoadParameter("lane_change_distance_factor", lane_change_distance_factor_,
111 "Factor multiplied with the current velocity to determine the lane change distance (m)");
112 this->declareAndLoadParameter("lane_change_min_distance_factor", lane_change_min_distance_factor_,
113 "Factor multiplied with the vehicle length to determine the minimum lane change distance (m)");
114
115 // check parameters
116 if (a_decel_ >= 0.0) {
117 RCLCPP_ERROR(this->get_logger(), "Invalid parameter: a_decel must be < 0.0");
118 exit(EXIT_FAILURE);
119 }
120 if (a_max_decel_ > a_decel_) {
121 RCLCPP_ERROR(this->get_logger(), "Invalid parameter: a_max_decel must be < 0.0 and <= a_decel");
122 exit(EXIT_FAILURE);
123 }
124
125 // diagnostics parameters
126 this->declareAndLoadParameter("diagnostic_updater.topic_diagnostics.ego_data.min_frequency",
128 "Minimum frequency for incoming ego-data messages", false);
129 this->declareAndLoadParameter("diagnostic_updater.topic_diagnostics.ego_data.max_frequency",
131 "Maximum frequency for incoming ego-data messages", false);
132 this->declareAndLoadParameter("diagnostic_updater.topic_diagnostics.ego_data.min_acceptable_timestamp_delta",
134 "Minimum acceptable timestamp delta for incoming ego-data messages", false);
135 this->declareAndLoadParameter("diagnostic_updater.topic_diagnostics.ego_data.max_acceptable_timestamp_delta",
137 "Maximum acceptable timestamp delta for incoming ego-data messages", false);
138 this->declareAndLoadParameter("diagnostic_updater.topic_diagnostics.object_list.min_frequency",
140 "Minimum frequency for incoming object-list messages", false);
141 this->declareAndLoadParameter("diagnostic_updater.topic_diagnostics.object_list.max_frequency",
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);
150 this->declareAndLoadParameter("diagnostic_updater.topic_diagnostics.route.min_frequency",
151 route_topic_diagnostic_config_.min_frequency, "Minimum frequency for incoming route messages",
152 false);
153 this->declareAndLoadParameter("diagnostic_updater.topic_diagnostics.route.max_frequency",
154 route_topic_diagnostic_config_.max_frequency, "Maximum frequency for incoming route messages",
155 false);
156 this->declareAndLoadParameter("diagnostic_updater.topic_diagnostics.route.min_acceptable_timestamp_delta",
158 "Minimum acceptable timestamp delta for incoming route messages", false);
159 this->declareAndLoadParameter("diagnostic_updater.topic_diagnostics.route.max_acceptable_timestamp_delta",
161 "Maximum acceptable timestamp delta for incoming route messages", false);
162 this->declareAndLoadParameter("diagnostic_updater.topic_diagnostics.grid_map.min_frequency",
164 "Minimum frequency for incoming grid-map messages", false);
165 this->declareAndLoadParameter("diagnostic_updater.topic_diagnostics.grid_map.max_frequency",
167 "Maximum frequency for incoming grid-map messages", false);
168 this->declareAndLoadParameter("diagnostic_updater.topic_diagnostics.grid_map.min_acceptable_timestamp_delta",
170 "Minimum acceptable timestamp delta for incoming grid-map messages", false);
171 this->declareAndLoadParameter("diagnostic_updater.topic_diagnostics.grid_map.max_acceptable_timestamp_delta",
173 "Maximum acceptable timestamp delta for incoming grid-map messages", false);
174 this->declareAndLoadParameter("diagnostic_updater.diagnosed_publishers.trajectory.min_frequency",
175 diagnosed_publisher_config_.min_frequency, "Minimum frequency for published trajectory messages",
176 false);
177 this->declareAndLoadParameter("diagnostic_updater.diagnosed_publishers.trajectory.max_frequency",
178 diagnosed_publisher_config_.max_frequency, "Maximum frequency for published trajectory messages",
179 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);
186
187 this->setup();
188}
189
195 tf2_buffer_ = std::make_unique<tf2_ros::Buffer>(this->get_clock());
196 tf2_listener_ = std::make_shared<tf2_ros::TransformListener>(*tf2_buffer_);
197
198 // calculate dt
200
201 // create a publisher for publishing output trajectory
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'",
207 object_interaction_marker_pub_->get_topic_name());
208
209 // create a timer for repeatedly invoking a callback to publish messages
210 publish_timer_ = this->create_wall_timer(std::chrono::duration<double>(1.0 / freq_),
212 RCLCPP_INFO(this->get_logger(), "Publishing trajectory at '%f' hz", freq_);
213
214 // create subscriber for egoData
215 sub_egoData_ = this->create_subscription<perception_msgs::msg::EgoData>(
216 "~/ego_data", 10, std::bind(&SimplePlannerNode::egoDataCallback, this, std::placeholders::_1));
217 RCLCPP_INFO(this->get_logger(), "Subscribed to '%s'", sub_egoData_->get_topic_name());
218
219 sub_object_list_ = this->create_subscription<perception_msgs::msg::ObjectList>(
220 "~/object_list", 10, std::bind(&SimplePlannerNode::objectListCallback, this, std::placeholders::_1));
221 RCLCPP_INFO(this->get_logger(), "Subscribed to '%s'", sub_object_list_->get_topic_name());
222
223 // create subscriber for route
224 sub_route_ = this->create_subscription<route_planning_msgs::msg::Route>(
225 "~/route", 10, std::bind(&SimplePlannerNode::routeCallback, this, std::placeholders::_1));
226 RCLCPP_INFO(this->get_logger(), "Subscribed to '%s'", sub_route_->get_topic_name());
227
228 sub_grid_map_ = this->create_subscription<nav_msgs::msg::OccupancyGrid>(
229 "~/grid_map", 10, std::bind(&SimplePlannerNode::gridMapCallback, this, std::placeholders::_1));
230 RCLCPP_INFO(this->get_logger(), "Subscribed to '%s'", sub_grid_map_->get_topic_name());
231
232 // create service clients for turn indicators and hazard lights
233 left_turn_indicator_service_client_ = this->create_client<std_srvs::srv::SetBool>("~/enable_left_turn_indicator");
234 RCLCPP_INFO(this->get_logger(), "Prepared service client for '%s'", left_turn_indicator_service_client_->get_service_name());
235 right_turn_indicator_service_client_ = this->create_client<std_srvs::srv::SetBool>("~/enable_right_turn_indicator");
236 RCLCPP_INFO(this->get_logger(), "Prepared service client for '%s'", right_turn_indicator_service_client_->get_service_name());
237 hazard_lights_service_client_ = this->create_client<std_srvs::srv::SetBool>("~/enable_hazard_lights");
238 RCLCPP_INFO(this->get_logger(), "Prepared service client for '%s'", hazard_lights_service_client_->get_service_name());
239
240 // create a callback for dynamic parameter configuration
242 this->add_on_set_parameters_callback(std::bind(&SimplePlannerNode::parametersCallback, this, std::placeholders::_1));
243
244 // Annotate message links for tracing: Trajectory is published periodically based on planning input subscriptions.
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());
251
252 // setup diagnostic updater
253 diagnostic_updater_.setHardwareID(this->get_name());
255
256 const int ego_data_topic_diagnostic_frequency_window_size =
257 std::ceil(5 / (diagnostic_updater_.getPeriod().seconds() * ego_data_topic_diagnostic_config_.min_frequency));
258 ego_data_topic_diagnostic_ = std::make_unique<diagnostic_updater::TopicDiagnostic>(
259 "~/ego_data", diagnostic_updater_,
260 diagnostic_updater::FrequencyStatusParam(&ego_data_topic_diagnostic_config_.min_frequency,
262 ego_data_topic_diagnostic_frequency_window_size),
263 diagnostic_updater::TimeStampStatusParam(ego_data_topic_diagnostic_config_.min_acceptable_timestamp_delta,
265
266 const int route_topic_diagnostic_frequency_window_size =
267 std::ceil(5 / (diagnostic_updater_.getPeriod().seconds() * route_topic_diagnostic_config_.min_frequency));
268 route_topic_diagnostic_ = std::make_unique<diagnostic_updater::TopicDiagnostic>(
269 "~/route", diagnostic_updater_,
270 diagnostic_updater::FrequencyStatusParam(&route_topic_diagnostic_config_.min_frequency,
272 route_topic_diagnostic_frequency_window_size),
273 diagnostic_updater::TimeStampStatusParam(route_topic_diagnostic_config_.min_acceptable_timestamp_delta,
275
276 if (consider_objects_) {
277 const int object_list_topic_diagnostic_frequency_window_size =
278 std::ceil(5 / (diagnostic_updater_.getPeriod().seconds() * object_list_topic_diagnostic_config_.min_frequency));
279 object_list_topic_diagnostic_ = std::make_unique<diagnostic_updater::TopicDiagnostic>(
280 "~/object_list", diagnostic_updater_,
281 diagnostic_updater::FrequencyStatusParam(&object_list_topic_diagnostic_config_.min_frequency,
283 object_list_topic_diagnostic_frequency_window_size),
284 diagnostic_updater::TimeStampStatusParam(object_list_topic_diagnostic_config_.min_acceptable_timestamp_delta,
286 }
287
288 if (consider_grid_map_) {
289 const int grid_map_topic_diagnostic_frequency_window_size =
290 std::ceil(5 / (diagnostic_updater_.getPeriod().seconds() * grid_map_topic_diagnostic_config_.min_frequency));
291 grid_map_topic_diagnostic_ = std::make_unique<diagnostic_updater::TopicDiagnostic>(
292 "~/grid_map", diagnostic_updater_,
293 diagnostic_updater::FrequencyStatusParam(&grid_map_topic_diagnostic_config_.min_frequency,
295 grid_map_topic_diagnostic_frequency_window_size),
296 diagnostic_updater::TimeStampStatusParam(grid_map_topic_diagnostic_config_.min_acceptable_timestamp_delta,
298 }
299
300 const int diagnosed_publisher_frequency_window_size =
301 std::ceil(5 / (diagnostic_updater_.getPeriod().seconds() * diagnosed_publisher_config_.min_frequency));
302 diagnosed_publisher_ = std::make_unique<diagnostic_updater::DiagnosedPublisher<trajectory_planning_msgs::msg::Trajectory>>(
304 diagnostic_updater::FrequencyStatusParam(&diagnosed_publisher_config_.min_frequency,
306 diagnosed_publisher_frequency_window_size),
307 diagnostic_updater::TimeStampStatusParam(diagnosed_publisher_config_.min_acceptable_timestamp_delta,
309}
310
316void SimplePlannerNode::egoDataCallback(const perception_msgs::msg::EgoData::UniquePtr msg) {
317 if (ego_data_topic_diagnostic_ != nullptr) {
318 ego_data_topic_diagnostic_->tick(msg->header.stamp);
319 }
320 ego_data_ = *msg;
321
322 if (!ego_data_init_) {
323 ego_data_init_ = true;
324 RCLCPP_INFO(this->get_logger(), "Received first ego data message, initialized global variable");
325 }
326}
327
328void SimplePlannerNode::objectListCallback(const perception_msgs::msg::ObjectList::UniquePtr msg) {
329 if (object_list_topic_diagnostic_ != nullptr) {
330 object_list_topic_diagnostic_->tick(msg->header.stamp);
331 }
332 object_list_ = *msg;
333
334 if (!object_list_init_) {
335 object_list_init_ = true;
336 RCLCPP_INFO(this->get_logger(), "Received first object list message, initialized global variable");
337 }
338}
339
345void SimplePlannerNode::routeCallback(const route_planning_msgs::msg::Route::UniquePtr msg) {
346 if (route_topic_diagnostic_ != nullptr) {
347 route_topic_diagnostic_->tick(msg->header.stamp);
348 }
349 route_ = *msg;
350
351 if (!route_init_) {
352 RCLCPP_INFO(this->get_logger(), "Received new route message, initialized global variable");
353 route_init_ = true;
354 safe_stop_distance_.reset();
355 }
356}
357
358void SimplePlannerNode::gridMapCallback(const nav_msgs::msg::OccupancyGrid::UniquePtr msg) {
359 if (grid_map_topic_diagnostic_ != nullptr) {
360 grid_map_topic_diagnostic_->tick(msg->header.stamp);
361 }
362 grid_map_ = *msg;
363
364 if (!grid_map_init_) {
365 grid_map_init_ = true;
366 RCLCPP_INFO(this->get_logger(), "Received first grid map message, initialized global variable");
367 }
368}
369
371 // ego data missing -> no publish
372 if (!ego_data_init_) {
373 setHealth(diagnostic_msgs::msg::DiagnosticStatus::STALE, "No ego data received yet",
374 {{"PlannerState", plannerStateToString(PlannerState::NoPublish)}});
376 }
377
378 // ego data outdated -> no publish
379 if (isMessageOutdated(ego_data_.header, ego_data_timeout_, stamp)) {
380 ego_data_init_ = false;
381 std::string msg =
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,
385 {{"PlannerState", plannerStateToString(PlannerState::NoPublish)}});
387 }
388
389 // no route received and no ongoing safe stop -> no publish
390 if (!route_init_ && !safe_stop_distance_.has_value()) {
391 setHealth(diagnostic_msgs::msg::DiagnosticStatus::STALE, "No route received and no ongoing safe stop",
392 {{"PlannerState", plannerStateToString(PlannerState::NoPublish)}});
394 }
395
396 // route outdated and vehicle in standstill -> standstill
397 if (route_init_ && isMessageOutdated(route_.header, route_timeout_, stamp)) {
398 if (perception_msgs::object_access::getStandstill(ego_data_)) {
399 route_init_ = false;
400 safe_stop_distance_.reset();
401 latest_path_.points.clear();
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,
408 }
409 route_init_ = false;
410
411 // route outdated and vehicle still moving -> safe stop
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,
416 {{"PlannerState", plannerStateToString(PlannerState::SafeStop)}});
418 }
419
420 // route received, but empty -> standstill
421 if (route_init_ && route_.route_elements.empty()) {
422 route_init_ = false;
423 safe_stop_distance_.reset();
424 latest_path_.points.clear();
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,
430 }
431
432 // no fresh route, but safe stop already started -> safe stop
433 if (!route_init_ && safe_stop_distance_.has_value()) {
434 if (perception_msgs::object_access::getStandstill(ego_data_)) {
435 safe_stop_distance_.reset();
436 latest_path_.points.clear();
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,
442 }
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,
446 {{"PlannerState", plannerStateToString(PlannerState::SafeStop)}});
448 }
449
450 // grid map enabled but missing, outdated, or invalid -> safe stop or standstill
451 if (consider_grid_map_ && !hasValidGridMap(stamp)) {
452 if (perception_msgs::object_access::getStandstill(ego_data_)) {
453 std::string msg =
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,
459 }
460
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,
464 {{"PlannerState", plannerStateToString(PlannerState::SafeStop)}});
466 }
467
468 // fresh ego data and valid route available -> follow route
469 setHealth(diagnostic_msgs::msg::DiagnosticStatus::OK, "Input information up to date. Following route.",
472}
473
474bool SimplePlannerNode::isMessageOutdated(const std_msgs::msg::Header& header, double timeout, const rclcpp::Time& stamp) {
475 if (timeout == -1.0) {
476 return false;
477 }
478 return (stamp - rclcpp::Time(header.stamp)) > rclcpp::Duration::from_seconds(timeout);
479}
480
490trajectory_planning_msgs::msg::Trajectory SimplePlannerNode::createTrajectory(PlannerState state, const rclcpp::Time& stamp) {
491 rclcpp::Time begin = rclcpp::Clock(RCL_SYSTEM_TIME).now();
492
493 trajectory_planning_msgs::msg::Trajectory tra;
494 tra.header.stamp = stamp;
495 tra.header.frame_id = vehicle_frame_id_;
496
497 if (state != PlannerState::FollowRoute) {
498 resetObjectState(tra.header);
499 }
500
501 switch (state) {
503 tra = buildStandstillTrajectory(tra.header);
504 break;
506 if (!safe_stop_distance_.has_value()) {
508 } else {
509 RCLCPP_DEBUG(this->get_logger(), "Executing safe stop.");
511 }
512 break;
514 safe_stop_distance_.reset();
515 FollowRoutePlan route_plan = buildRoutePlan(tra.header);
516 tra = buildTrajectoryFromSimplePath(route_plan.path);
517 break;
518 }
520 throw std::runtime_error("createTrajectory called for non-publish state");
521 }
522
523 if (tra.header.frame_id != trajectory_frame_id_) {
524 try {
525 tra = tf2_buffer_->transform(tra, trajectory_frame_id_, tf2::durationFromSec(1.0));
526 } catch (tf2::TransformException& ex) {
527 throw std::runtime_error("Transformation into output frame '" + trajectory_frame_id_ +
528 "' is not available: " + std::string(ex.what()));
529 }
530 }
531
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);
534 return tra;
535}
536
537trajectory_planning_msgs::msg::Trajectory SimplePlannerNode::buildStandstillTrajectory(
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);
544 return tra;
545}
546
547trajectory_planning_msgs::msg::Trajectory SimplePlannerNode::buildTrajectoryFromSimplePath(const SimplePath& path) {
548 SimplePath usable_path = path;
549 trimPathBehindEgo(usable_path);
550
551 if (usable_path.points.empty()) {
552 safe_stop_distance_.reset();
553 latest_path_.points.clear();
554 std::string msg = "No usable forward path remains. Publishing standstill trajectory.";
555 RCLCPP_WARN(this->get_logger(), "%s", msg.c_str());
556 health_.key_value_pairs.insert_or_assign("PlannerState", plannerStateToString(PlannerState::Standstill));
557 setHealth(diagnostic_msgs::msg::DiagnosticStatus::WARN, msg, health_.key_value_pairs);
559 }
560
561 latest_path_ = usable_path;
562 std::vector<SimplePathPoint> path_points = latest_path_.points;
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);
566 }
567
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;
572
573 for (int i = 0; i < n_states_; i++) {
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);
581 }
582
583 trajectory_planning_msgs::trajectory_access::setStandstill(tra, false);
584 RCLCPP_DEBUG(this->get_logger(), "Standstill = %d", tra.standstill);
585 return tra;
586}
587
588SimplePath SimplePlannerNode::buildSafeStopPath(const std_msgs::msg::Header& target_header) {
589 double current_velocity = perception_msgs::object_access::getVelocityMagnitude(ego_data_);
590 safe_stop_distance_ = -0.5 * std::pow(current_velocity, 2) / a_max_decel_;
591
592 SimplePath safe_stop_path = transformPath(latest_path_, target_header);
593 trimPathBehindEgo(safe_stop_path);
594 if (safe_stop_path.points.empty()) {
595 RCLCPP_WARN(
596 this->get_logger(),
597 "No latest path available. Initialize safe stop along ego heading. Current velocity: %f m/s, safe stop distance: %f m",
598 current_velocity, *safe_stop_distance_);
600 } else {
601 RCLCPP_WARN(this->get_logger(), "Initialize safe stop along latest path. Current velocity: %f m/s, safe stop distance: %f m",
602 current_velocity, *safe_stop_distance_);
604 }
605 return latest_path_;
606}
607
609 RCLCPP_DEBUG(this->get_logger(), "Default case: route is up to date, creating path from route.");
610
611 FollowRoutePlan route_plan;
612 route_plan.path.header = target_header;
613 route_planning_msgs::msg::Route tf_route = route_;
614 if (requiresTransform(route_.header, target_header)) {
615 try {
616 tf_route = tf2_buffer_->transform(route_, target_header.frame_id, tf2_ros::fromMsg(target_header.stamp),
617 fixed_over_time_frame_id_, tf2::durationFromSec(1.0));
618 } catch (tf2::TransformException& ex) {
619 std::string msg = "Route transformation is not available: " + std::string(ex.what()) + ".";
620 setHealth(diagnostic_msgs::msg::DiagnosticStatus::WARN, msg, health_.key_value_pairs);
621 RCLCPP_WARN(this->get_logger(), "%s", msg.c_str());
622 return route_plan;
623 }
624 }
625 route_plan.path.header = tf_route.header;
626
627 std::map<uint64_t, uint64_t> lane_change_indices_map;
628 appendRoutePoints(tf_route, route_plan, lane_change_indices_map);
629
630 std::vector<SimplePathPoint> merged_points = mergeLaneChangeSegments(tf_route, route_plan.path.points, lane_change_indices_map);
631 recalculateS(merged_points);
632 applyGridMapConstraints(target_header, merged_points, route_plan);
633 route_plan.path.points = resamplePath(merged_points, route_plan.stop_at_end, route_plan.offset_to_stop_line);
634 applyObjectConstraints(target_header, merged_points, route_plan);
637 health_.key_value_pairs.insert_or_assign("SuggestedTurnSignal", turnSignalToString(route_plan.suggested_turn_signal));
638 }
639 if (route_plan.stop_at_end) health_.key_value_pairs.insert({"ReasonToStop", route_plan.reason_to_stop});
640 return route_plan;
641}
642
643void SimplePlannerNode::appendRoutePoints(const route_planning_msgs::msg::Route& tf_route,
644 FollowRoutePlan& route_plan,
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);
655 continue;
656 }
657
658 const auto& suggested_lane = route_planning_msgs::route_access::getSuggestedLaneElement(route_element);
659 SimplePathPoint simple_path_point;
660 simple_path_point.position =
661 Eigen::Vector2d(suggested_lane.reference_pose.position.x, suggested_lane.reference_pose.position.y);
662 simple_path_point.s = route_element.s;
663 simple_path_point.v = v_ref_;
664 if (v_ref_ < 0.0) {
665 simple_path_point.v = suggested_lane.speed_limit / 3.6;
666 }
667
668 if (!route_plan.path.points.empty()) {
669 double v_average = (route_plan.path.points.back().v + simple_path_point.v) / 2.0;
670 double dt = 0.0;
671 if (v_average != 0.0) {
672 dt = (simple_path_point.s - route_plan.path.points.back().s) / v_average;
673 }
674 if (dt <= 0.0) {
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.";
678 setHealth(diagnostic_msgs::msg::DiagnosticStatus::WARN, msg, health_.key_value_pairs);
679 RCLCPP_WARN(this->get_logger(), "%s", msg.c_str());
680 }
681 t_total += dt;
682 }
683
684 if (j == tf_route.current_route_element_idx) {
685 route_plan.suggested_turn_signal = suggested_lane.suggested_turn_signal;
686 }
687
688 if (route_element.will_change_suggested_lane &&
689 !tryRegisterLaneChange(tf_route, j, lane_change_indices_map, route_plan.suggested_turn_signal)) {
690 break;
691 }
692
694 updateForTrafficLights(tf_route, j, suggested_lane, simple_path_point, t_total, route_plan.stop_at_end,
695 route_plan.offset_to_stop_line);
696 if (route_plan.stop_at_end) route_plan.reason_to_stop = "Traffic light indicates stop";
697 }
698
699 route_plan.path.points.push_back(simple_path_point);
700
701 if (j == tf_route.destination_route_element_idx - 1) {
702 SimplePathPoint destination_point;
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);
707 route_plan.reason_to_stop = "Reaching end of route";
708 route_plan.stop_at_end = true;
709 }
710 if (route_plan.stop_at_end || t_total >= 2.0 * trajectory_horizon_) {
711 break;
712 }
713 }
714}
715
716bool SimplePlannerNode::tryRegisterLaneChange(const route_planning_msgs::msg::Route& tf_route,
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.";
722 setHealth(diagnostic_msgs::msg::DiagnosticStatus::WARN, msg, health_.key_value_pairs);
723 RCLCPP_WARN(this->get_logger(), "%s", msg.c_str());
724 return false;
725 }
726
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;
731 try {
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,
736 ex.what());
737 return false;
738 }
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;
743 }
744
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.",
753 route_element_idx);
754 return true;
755 }
756
757 double ds = 0.0;
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;
763 } else {
764 std::string msg =
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());
767 setHealth(diagnostic_msgs::msg::DiagnosticStatus::WARN, msg, health_.key_value_pairs);
768 break;
769 }
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());
774 setHealth(diagnostic_msgs::msg::DiagnosticStatus::WARN, msg, health_.key_value_pairs);
775 break;
776 }
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());
781 setHealth(diagnostic_msgs::msg::DiagnosticStatus::WARN, msg, health_.key_value_pairs);
782 break;
783 }
784 ds += std::abs(tf_route.route_elements[i_start].s - tf_route.route_elements[i_start - 1].s);
785 i_start--;
786 }
787
788 if (!tf_route.route_elements[i_start].is_enriched || !tf_route.route_elements[i_end].is_enriched) {
789 std::string msg =
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());
792 setHealth(diagnostic_msgs::msg::DiagnosticStatus::WARN, msg, health_.key_value_pairs);
793 return false;
794 }
795
796 lane_change_indices_map[route_element_idx] = i_start;
797 return true;
798}
799
800void SimplePlannerNode::updateForTrafficLights(const route_planning_msgs::msg::Route& tf_route,
801 size_t route_element_idx,
802 const route_planning_msgs::msg::LaneElement& suggested_lane,
803 const SimplePathPoint& simple_path_point,
804 double t_total,
805 bool& stop_at_end,
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) {
812 continue;
813 }
814 if (reg_elems[k].meta_value == route_planning_msgs::msg::RegulatoryElement::META_VALUE_MOVEMENT_ALLOWED &&
816 continue;
817 }
818
819 offset_to_stop_line =
820 offset_to_stop_line_ + ego_data_.length / 2.0 + ego_data_.state.reference_point.translation_to_geometric_center.x;
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;
824 }
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.";
829 setHealth(diagnostic_msgs::msg::DiagnosticStatus::WARN, msg, health_.key_value_pairs);
830 RCLCPP_WARN(this->get_logger(), "%s", msg.c_str());
831 }
832
833 if (reg_elems[k].has_validity_stamp && consider_future_states_) {
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) {
838 stop_at_end = true;
839 } else {
840 continue;
841 }
842 } else {
843 if (reg_elems[k].meta_value == route_planning_msgs::msg::RegulatoryElement::META_VALUE_MOVEMENT_ALLOWED) {
844 continue;
845 } else {
846 stop_at_end = true;
847 }
848 }
849 } else {
850 stop_at_end = true;
851 }
852
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_;
857 if (distance_to_stop_point < 0.0 && std::abs(distance_to_stop_point) > ignore_stop_line_threshold_) {
858 std::string msg =
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.";
861 setHealth(diagnostic_msgs::msg::DiagnosticStatus::WARN, msg, health_.key_value_pairs);
862 RCLCPP_WARN(this->get_logger(), "%s", msg.c_str());
863 stop_at_end = false;
864 } else if ((distance_to_stop_point < min_distance_to_stop) && stop_at_end) {
865 std::string msg =
866 "Traffic light requires stop, but distance to stop point is smaller than minimum distance to stop. Ignoring traffic "
867 "light.";
868 setHealth(diagnostic_msgs::msg::DiagnosticStatus::WARN, msg, health_.key_value_pairs);
869 RCLCPP_WARN(this->get_logger(), "%s", msg.c_str());
870 stop_at_end = false;
871 }
872 }
873}
874
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) {
879 size_t current = 0;
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;
884 size_t start_idx =
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
888 : 0;
889
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,
892 end_idx, current);
893 continue;
894 }
895
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());
903 }
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());
907 }
908 return merged_points;
909}
910
911void SimplePlannerNode::applyIndicatorRequest(uint8_t suggested_turn_signal) {
912 if (suggested_turn_signal == route_planning_msgs::msg::LaneElement::SUGGESTED_TURN_SIGNAL_NONE &&
913 left_turn_indicator_service_client_->service_is_ready() && right_turn_indicator_service_client_->service_is_ready() &&
914 hazard_lights_service_client_->service_is_ready()) {
915 auto request = std::make_shared<std_srvs::srv::SetBool::Request>();
916 request->data = false;
917 left_turn_indicator_service_client_->async_send_request(request);
918 right_turn_indicator_service_client_->async_send_request(request);
919 hazard_lights_service_client_->async_send_request(request);
920 } else if (suggested_turn_signal == route_planning_msgs::msg::LaneElement::SUGGESTED_TURN_SIGNAL_LEFT &&
921 left_turn_indicator_service_client_->service_is_ready()) {
922 auto request = std::make_shared<std_srvs::srv::SetBool::Request>();
923 request->data = true;
924 left_turn_indicator_service_client_->async_send_request(request);
925 } else if (suggested_turn_signal == route_planning_msgs::msg::LaneElement::SUGGESTED_TURN_SIGNAL_RIGHT &&
926 right_turn_indicator_service_client_->service_is_ready()) {
927 auto request = std::make_shared<std_srvs::srv::SetBool::Request>();
928 request->data = true;
929 right_turn_indicator_service_client_->async_send_request(request);
930 } else if (suggested_turn_signal == route_planning_msgs::msg::LaneElement::SUGGESTED_TURN_SIGNAL_HAZARD &&
931 hazard_lights_service_client_->service_is_ready()) {
932 auto request = std::make_shared<std_srvs::srv::SetBool::Request>();
933 request->data = true;
934 hazard_lights_service_client_->async_send_request(request);
935 } else {
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());
939 setHealth(diagnostic_msgs::msg::DiagnosticStatus::WARN, msg, health_.key_value_pairs);
940 }
941}
942
944 while (!path.points.empty() && path.points[0].position.x() < 0.0) {
945 path.points.erase(path.points.begin());
946 }
947}
948
949SimplePath SimplePlannerNode::calculateSafeStopAlongRoute(const SimplePath& path, const double safe_stop_distance) {
950 SimplePath safe_stop_path = path;
951 double start_s = safe_stop_path.points[0].s; // start s value of path
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) {
954 // remove all points after the point where the safe stop distance is reached
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());
957 break;
958 }
959 }
960 safe_stop_path.points = resamplePath(safe_stop_path.points, true, 0.0);
961 return safe_stop_path;
962}
963
964SimplePath SimplePlannerNode::calculateSafeStopAlongEgoHeading(const perception_msgs::msg::EgoData& ego_data,
965 const double safe_stop_distance,
966 const std_msgs::msg::Header& target_header) {
967 SimplePath safe_stop_path;
968 safe_stop_path.header = ego_data.header;
969
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));
975
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);
978 (void)target_header;
979
980 SimplePathPoint start_point(start_position, 0.0, v_ego);
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);
986
987 safe_stop_path.points = resamplePath(safe_stop_path.points, true, 0.0);
988 return safe_stop_path;
989}
990
991std::vector<SimplePathPoint> SimplePlannerNode::generateLaneChangePath(size_t start_idx,
992 size_t turn_idx,
993 const route_planning_msgs::msg::Route& route) {
994 std::vector<SimplePathPoint> lane_change_path;
995 const size_t end_idx = turn_idx + 1; // lane change should end at the next route element
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());
1000 setHealth(diagnostic_msgs::msg::DiagnosticStatus::WARN, msg, health_.key_value_pairs);
1001 return lane_change_path;
1002 }
1003
1004 int lane_change_direction = route_planning_msgs::route_access::getLaneChangeDirection(route.route_elements[turn_idx],
1005 route.route_elements[turn_idx + 1]);
1006
1007 // Interpolate between the two elements
1008 for (size_t i = start_idx; i <= end_idx; ++i) {
1009 if (i < route.current_route_element_idx) continue;
1010
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; // -> i == turn_idx + 1 == end_idx
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)); // convert km/h to m/s
1023 }
1024
1025 return lane_change_path;
1026}
1027
1028std::vector<SimplePathPoint> SimplePlannerNode::truncatePathAtS(const std::vector<SimplePathPoint>& path, double stop_s) {
1029 if (path.empty()) {
1030 return path;
1031 }
1032
1033 if (stop_s <= path.front().s) {
1034 SimplePathPoint stop_point = path.front();
1035 stop_point.s = stop_s;
1036 return {stop_point};
1037 }
1038
1039 if (stop_s >= path.back().s) {
1040 return path;
1041 }
1042
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()) {
1046 return {*stop_it};
1047 }
1048 if (stop_it == path.end()) {
1049 return path;
1050 }
1051
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;
1056 }
1057
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;
1061 SimplePathPoint stop_point;
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;
1067}
1068
1069void SimplePlannerNode::recalculateS(std::vector<SimplePathPoint>& path) {
1070 if (path.empty()) return;
1071
1072 path[0].s = 0.0;
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;
1076 }
1077}
1078
1079std::vector<SimplePathPoint> SimplePlannerNode::resamplePath(const std::vector<SimplePathPoint>& path,
1080 bool stop_at_end,
1081 double offset_to_stop_line,
1082 const double* speed_cap) {
1083 rclcpp::Time begin = rclcpp::Clock(RCL_SYSTEM_TIME).now();
1084 if (path.empty()) {
1085 std::string msg = "Route is empty. No resampling possible.";
1086 RCLCPP_WARN(this->get_logger(), "%s", msg.c_str());
1087 setHealth(diagnostic_msgs::msg::DiagnosticStatus::WARN, msg, health_.key_value_pairs);
1088 return path;
1089 }
1090
1091 std::vector<SimplePathPoint> resampled_path;
1092 double s = path[0].s;
1093
1094 tk::spline x_spline, y_spline;
1095 if (interpolation_type_ == InterpolationType::SPLINE && path.size() > 2) {
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());
1101 }
1102 x_spline.set_points(s_vector, x_vector);
1103 y_spline.set_points(s_vector, y_vector);
1104 }
1105
1106 while (s < path.back().s) {
1107 // find index of segment in route (not required if using linearInterpolation from utils)
1108 int idx = -1;
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);
1112 break;
1113 }
1114 }
1115
1116 double v = v_ref_; // option 1: use predefined constant velocity
1117 if (v_ref_ < 0.0 && idx >= 0) { // option 2: use velocity from route if predefined velocity is negative
1118 v = path[idx].v + (path[idx + 1].v - path[idx].v) / (path[idx + 1].s - path[idx].s) * (s - path[idx].s);
1119 }
1120 if (speed_cap != nullptr) {
1121 v = std::min(v, std::max(*speed_cap, 0.0));
1122 }
1123
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_; // case 1: constant velocity
1128 if (s + ds > brake_point && stop_at_end) {
1129 if (s < brake_point) { // special case: braking point is between two states
1130 double ds_1 = brake_point - s; // distance with constant velocity to braking point
1131 double dt_1 = ds_1 / v; // time with constant velocity to braking point
1132 double dt_2 = dt_ - dt_1; // remaining time with deceleration
1133 double ds_2 = std::max(0.5 * a_decel_ * std::pow(dt_2, 2) + v * dt_2, 0.0);
1134 ds = ds_1 + ds_2;
1135 } else {
1136 v = std::sqrt(std::max(std::pow(v, 2) + 2 * a_decel_ * (s - brake_point), 0.0)); // case 2: deceleration (v(s))
1137 ds = 0.5 * a_decel_ * std::pow(dt_, 2) + v * dt_; // case 2: deceleration
1138 if (ds < 0.0) ds = path.back().s - s; // only add rest of route instead of driving backwards
1139 }
1140 }
1141
1142 // interpolate point at s
1143 SimplePathPoint simple_path_point;
1144 if (path.size() == 1) {
1145 simple_path_point.position = path[0].position;
1146 } else if (interpolation_type_ == InterpolationType::SPLINE && path.size() > 2) { // spline interpolation
1147 simple_path_point.position.x() = x_spline(s);
1148 simple_path_point.position.y() = y_spline(s);
1149 } else if (
1150 (interpolation_type_ == InterpolationType::SPLINE && path.size() <= 2) ||
1153 LINEAR) { // linear interpolation // TODO: could be improved by using our linearInterpolation function -> no need for idx anymore
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);
1158 } else { // unsupported interpolation type
1159 RCLCPP_ERROR(this->get_logger(), "Unsupported interpolation type value %d", interpolation_type_);
1160 throw std::runtime_error("Unsupported interpolation type value");
1161 }
1162 simple_path_point.s = s;
1163 simple_path_point.v = v;
1164 resampled_path.push_back(simple_path_point);
1165
1166 // increment s and v for next iteration
1167 if (v <= 1e-6 && ds <= 1e-6) break;
1168 s = s + ds;
1169 if (s == path.back().s && v == 0.0) break; // stop at end of route
1170 }
1171
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);
1174
1175 if (stop_at_end) {
1176 SimplePathPoint stop_point = path.back();
1177 if (!resampled_path.empty() && (offset_to_stop_line > 0.0 || (speed_cap != nullptr && *speed_cap <= 1e-6))) {
1178 stop_point = resampled_path.back();
1179 }
1180 stop_point.v = 0.0;
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);
1183 }
1184 }
1185
1186 return resampled_path;
1187}
1188
1194 const rclcpp::Time stamp = now();
1195 PlannerState planner_state = determinePlannerState(stamp);
1196 if (planner_state == PlannerState::NoPublish) {
1197 std_msgs::msg::Header marker_header;
1198 marker_header.stamp = stamp;
1199 marker_header.frame_id = vehicle_frame_id_;
1200 clearObjectInteractionMarkers(marker_header);
1201 return;
1202 }
1203
1204 try {
1205 trajectory_planning_msgs::msg::Trajectory msg = createTrajectory(planner_state, stamp);
1206 diagnosed_publisher_->publish(msg);
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());
1211 setHealth(diagnostic_msgs::msg::DiagnosticStatus::ERROR, msg, {{"PlannerState", plannerStateToString(planner_state)}});
1212 }
1213}
1214
1215} // namespace simple_planner
1216
1224int main(int argc, char* argv[]) {
1225 rclcpp::init(argc, argv);
1226 rclcpp::spin(std::make_shared<simple_planner::SimplePlannerNode>());
1227 rclcpp::shutdown();
1228
1229 return 0;
1230}
void setup()
Sets up subscribers, publishers, etc. to configure the node.
SimplePath buildSafeStopPath(const std_msgs::msg::Header &target_header)
Builds the initial safe-stop path for the current cycle.
std::unique_ptr< diagnostic_updater::TopicDiagnostic > ego_data_topic_diagnostic_
void declareAndLoadParameter(const std::string &name, T &param, 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.
Definition utils.hpp:7
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.
std::optional< double > safe_stop_distance_
bool hasValidGridMap(const rclcpp::Time &stamp) const
Checks whether a fresh, structurally valid grid map is currently available.
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_
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_
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.
std::unique_ptr< diagnostic_updater::TopicDiagnostic > grid_map_topic_diagnostic_
void publishTimerCallback()
This callback is invoked every period seconds by the timer.
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.
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.
rcl_interfaces::msg::SetParametersResult parametersCallback(const std::vector< rclcpp::Parameter > &parameters)
Handles reconfiguration when a parameter value is changed.
Definition utils.hpp:79
TopicDiagnosticConfig object_list_topic_diagnostic_config_
void health(diagnostic_updater::DiagnosticStatusWrapper &stat)
Function called by diagnostic updater to populate diagnostics status.
Definition utils.hpp:173
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.
Definition utils.hpp:188
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.
Definition utils.hpp:203
std::unique_ptr< diagnostic_updater::TopicDiagnostic > route_topic_diagnostic_
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_
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.
Definition utils.hpp:180
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.
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.
SimplePath transformPath(const SimplePath &path, const std_msgs::msg::Header &target_header)
Transforms a simple path into the requested target frame and timestamp.
Definition utils.hpp:140
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.
Definition utils.hpp:127
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.
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
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)