Carma-platform v4.2.0
CARMA Platform is built on robot operating system (ROS) and utilizes open source software (OSS) that enables Cooperative Driving Automation (CDA) features to allow Automated Driving Systems to interact and cooperate with infrastructure and other vehicles through communication.
approaching_emergency_vehicle_plugin_node.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2022-2023 LEIDOS.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
17
19{
20 namespace std_ph = std::placeholders;
21
22 namespace {
27 double getManeuverEndSpeed(const carma_planning_msgs::msg::Maneuver& mvr) {
28 switch(mvr.type) {
29 case carma_planning_msgs::msg::Maneuver::LANE_FOLLOWING:
30 return mvr.lane_following_maneuver.end_speed;
31 case carma_planning_msgs::msg::Maneuver::LANE_CHANGE:
32 return mvr.lane_change_maneuver.end_speed;
33 case carma_planning_msgs::msg::Maneuver::STOP_AND_WAIT:
34 return 0;
35 default:
36 return 0;
37 }
38 }
39 }
40
42 : carma_guidance_plugins::StrategicPlugin(options)
43 {
44 // Create initial config
45 config_ = Config();
46
47 // Declare parameters
48 config_.passing_threshold = declare_parameter<double>("passing_threshold", config_.passing_threshold);
49 config_.approaching_threshold = declare_parameter<double>("approaching_threshold", config_.approaching_threshold);
50 config_.finished_passing_threshold = declare_parameter<double>("finished_passing_threshold", config_.finished_passing_threshold);
51 config_.min_lane_following_duration_before_lane_change = declare_parameter<double>("min_lane_following_duration_before_lane_change", config_.min_lane_following_duration_before_lane_change);
52 config_.bsm_processing_frequency = declare_parameter<double>("bsm_processing_frequency", config_.bsm_processing_frequency);
53 config_.speed_limit_reduction_during_passing = declare_parameter<double>("speed_limit_reduction_during_passing", config_.speed_limit_reduction_during_passing);
54 config_.minimum_reduced_speed_limit = declare_parameter<double>("minimum_reduced_speed_limit", config_.minimum_reduced_speed_limit);
55 config_.default_speed_limit = declare_parameter<double>("default_speed_limit", config_.default_speed_limit);
56 config_.reduced_speed_buffer = declare_parameter<double>("reduced_speed_buffer", config_.reduced_speed_buffer);
57 config_.timeout_check_frequency = declare_parameter<double>("timeout_check_frequency", config_.timeout_check_frequency);
58 config_.timeout_duration = declare_parameter<double>("timeout_duration", config_.timeout_duration);
59 config_.minimal_plan_duration = declare_parameter<double>("minimal_plan_duration", config_.minimal_plan_duration);
60 config_.buffer_distance_before_stopping = declare_parameter<double>("buffer_distance_before_stopping", config_.buffer_distance_before_stopping);
61 config_.stopping_accel_limit_multiplier = declare_parameter<double>("stopping_accel_limit_multiplier", config_.stopping_accel_limit_multiplier);
62 config_.vehicle_acceleration_limit = declare_parameter<double>("vehicle_acceleration_limit", config_.vehicle_acceleration_limit);
63 config_.route_end_point_buffer = declare_parameter<double>("route_end_point_buffer", config_.route_end_point_buffer);
64 config_.approaching_erv_status_publication_frequency = declare_parameter<double>("approaching_erv_status_publication_frequency", config_.approaching_erv_status_publication_frequency);
65 config_.warning_broadcast_frequency = declare_parameter<double>("warning_broadcast_frequency", config_.warning_broadcast_frequency);
66 config_.max_warning_broadcasts = declare_parameter<int>("max_warning_broadcasts", config_.max_warning_broadcasts);
67 config_.vehicle_length = declare_parameter<double>("vehicle_length", config_.vehicle_length);
68 config_.lane_following_plugin = declare_parameter<std::string>("lane_following_plugin", config_.lane_following_plugin);
69 config_.lane_change_plugin = declare_parameter<std::string>("lane_change_plugin", config_.lane_change_plugin);
70 config_.vehicle_id = declare_parameter<std::string>("vehicle_id", config_.vehicle_id);
71
73 }
74
75 rcl_interfaces::msg::SetParametersResult ApproachingEmergencyVehiclePlugin::parameter_update_callback(const std::vector<rclcpp::Parameter> &parameters)
76 {
77 auto error_1 = update_params<double>({
78 {"passing_threshold", config_.passing_threshold},
79 {"approaching_threshold", config_.approaching_threshold},
80 {"finished_passing_threshold", config_.finished_passing_threshold},
81 {"min_lane_following_duration_before_lane_change", config_.min_lane_following_duration_before_lane_change},
82 {"bsm_processing_frequency", config_.bsm_processing_frequency},
83 {"speed_limit_reduction_during_passing", config_.speed_limit_reduction_during_passing},
84 {"minimum_reduced_speed_limit", config_.minimum_reduced_speed_limit},
85 {"default_speed_limit", config_.default_speed_limit},
86 {"reduced_speed_buffer", config_.reduced_speed_buffer},
87 {"timeout_check_frequency", config_.timeout_check_frequency},
88 {"timeout_duration", config_.timeout_duration},
89 {"minimal_plan_duration", config_.minimal_plan_duration},
90 {"buffer_distance_before_stopping", config_.buffer_distance_before_stopping},
91 {"stopping_accel_limit_multiplier", config_.stopping_accel_limit_multiplier},
92 {"vehicle_acceleration_limit", config_.vehicle_acceleration_limit},
93 {"route_end_point_buffer", config_.route_end_point_buffer},
94 {"approaching_erv_status_publication_frequency", config_.approaching_erv_status_publication_frequency},
95 {"warning_broadcast_frequency", config_.warning_broadcast_frequency},
96 {"vehicle_length", config_.vehicle_length}
97 }, parameters);
98
99 auto error_2 = update_params<int>({
100 {"max_warning_broadcasts", config_.max_warning_broadcasts}
101 }, parameters);
102
103 auto error_3 = update_params<std::string>({
104 {"lane_following_plugin", config_.lane_following_plugin},
105 {"lane_change_plugin", config_.lane_change_plugin},
106 {"stop_and_wait_plugin", config_.stop_and_wait_plugin},
107 {"vehicle_id", config_.vehicle_id}
108 }, parameters);
109
110 rcl_interfaces::msg::SetParametersResult result;
111
112 result.successful = !error_1 && !error_2 && !error_3;
113
114 return result;
115 }
116
118 {
119 RCLCPP_INFO_STREAM(rclcpp::get_logger(logger_name), "ApproachingEmergencyVehiclePlugin trying to configure");
120
121 // Reset config
122 config_ = Config();
123
124 // Load parameters
125 get_parameter<double>("passing_threshold", config_.passing_threshold);
126 get_parameter<double>("approaching_threshold", config_.approaching_threshold);
127 get_parameter<double>("finished_passing_threshold", config_.finished_passing_threshold);
128 get_parameter<double>("min_lane_following_duration_before_lane_change", config_.min_lane_following_duration_before_lane_change);
129 get_parameter<double>("bsm_processing_frequency", config_.bsm_processing_frequency);
130 get_parameter<double>("speed_limit_reduction_during_passing", config_.speed_limit_reduction_during_passing);
131 get_parameter<double>("minimum_reduced_speed_limit", config_.minimum_reduced_speed_limit);
132 get_parameter<double>("default_speed_limit", config_.default_speed_limit);
133 get_parameter<double>("reduced_speed_buffer", config_.reduced_speed_buffer);
134 get_parameter<double>("timeout_check_frequency", config_.timeout_check_frequency);
135 get_parameter<double>("timeout_duration", config_.timeout_duration);
136 get_parameter<double>("minimal_plan_duration", config_.minimal_plan_duration);
137 get_parameter<double>("buffer_distance_before_stopping", config_.buffer_distance_before_stopping);
138 get_parameter<double>("stopping_accel_limit_multiplier", config_.stopping_accel_limit_multiplier);
139 get_parameter<double>("vehicle_acceleration_limit", config_.vehicle_acceleration_limit);
140 get_parameter<double>("route_end_point_buffer", config_.route_end_point_buffer);
141 get_parameter<double>("approaching_erv_status_publication_frequency", config_.approaching_erv_status_publication_frequency);
142 get_parameter<double>("warning_broadcast_frequency", config_.warning_broadcast_frequency);
143 get_parameter<double>("vehicle_length", config_.vehicle_length);
144 get_parameter<int>("max_warning_broadcasts", config_.max_warning_broadcasts);
145 get_parameter<std::string>("lane_following_plugin", config_.lane_following_plugin);
146 get_parameter<std::string>("lane_change_plugin", config_.lane_change_plugin);
147 get_parameter<std::string>("vehicle_id", config_.vehicle_id);
148
149 RCLCPP_INFO_STREAM(rclcpp::get_logger(logger_name), "ApproachingEmergencyVehiclePlugin Config: " << config_);
150
151 // Register runtime parameter update callback
152 add_on_set_parameters_callback(std::bind(&ApproachingEmergencyVehiclePlugin::parameter_update_callback, this, std_ph::_1));
153
154 // Setup subscribers
155 incoming_bsm_sub_ = create_subscription<carma_v2x_msgs::msg::BSM>("incoming_bsm", 1,
156 std::bind(&ApproachingEmergencyVehiclePlugin::incomingBsmCallback, this, std_ph::_1));
157
158 georeference_sub_ = create_subscription<std_msgs::msg::String>("georeference", 10,
159 std::bind(&ApproachingEmergencyVehiclePlugin::georeferenceCallback, this, std_ph::_1));
160
161 route_state_sub_ = create_subscription<carma_planning_msgs::msg::RouteState>("route_state", 10,
162 std::bind(&ApproachingEmergencyVehiclePlugin::routeStateCallback, this, std_ph::_1));
163
164 twist_sub_ = create_subscription<geometry_msgs::msg::TwistStamped>("current_velocity", 10,
165 std::bind(&ApproachingEmergencyVehiclePlugin::twistCallback, this, std_ph::_1));
166
167 incoming_emergency_vehicle_ack_sub_ = create_subscription<carma_v2x_msgs::msg::EmergencyVehicleAck>("incoming_emergency_vehicle_ack", 10,
169
170 guidance_state_sub_ = create_subscription<carma_planning_msgs::msg::GuidanceState>("state", 1,
172
173 route_sub_ = create_subscription<carma_planning_msgs::msg::Route>("route", 1,
174 std::bind(&ApproachingEmergencyVehiclePlugin::routeCallback, this, std_ph::_1));
175
176 // Setup publishers
177 outgoing_emergency_vehicle_response_pub_ = create_publisher<carma_v2x_msgs::msg::EmergencyVehicleResponse>("outgoing_emergency_vehicle_response", 10);
178
179 approaching_erv_status_pub_ = create_publisher<carma_msgs::msg::UIInstructions>("approaching_erv_status", 10);
180
181 hazard_light_cmd_pub_ = create_publisher<std_msgs::msg::Bool>("hazard_light_status", 10);
182
184
185 // Return success if everything initialized successfully
186 return CallbackReturn::SUCCESS;
187 }
188
190 {
191 // Timer setup for generating a BSM
192 int erv_timeout_check_period_ms = (1 / config_.timeout_check_frequency) * 1000; // Conversion from frequency (Hz) to milliseconds time period
193 erv_timeout_timer_ = create_timer(get_clock(),
194 std::chrono::milliseconds(erv_timeout_check_period_ms),
196
197 // Timer setup for broadcasting EmergencyVehicleResponse warning message to an approaching ERV when the ego vehicle cannot change lanes out of the ERV's path
198 int emergency_vehicle_response_period_ms = (1 / config_.warning_broadcast_frequency) * 1000; // Conversion from frequency (Hz) to milliseconds time period
199 warning_broadcast_timer_ = create_timer(get_clock(),
200 std::chrono::milliseconds(emergency_vehicle_response_period_ms),
202
203 // Timer setup for publishing approaching ERV status update to the Web UI
204 int approaching_erv_status_period_ms = (1 / config_.approaching_erv_status_publication_frequency) * 1000; // Conversion from frequency (Hz) to milliseconds time period
205 approaching_emergency_vehicle_status_timer_ = create_timer(get_clock(),
206 std::chrono::milliseconds(approaching_erv_status_period_ms),
208
209 // Timer setup for publishing hazard light ON/OFF status boolean
210 int hazard_light_status_ms = (1 / 30) * 1000; // Conversion from frequency 30(Hz) to milliseconds time period
211 hazard_light_timer_ = create_timer(get_clock(),
212 std::chrono::milliseconds(hazard_light_status_ms),
214
215 return CallbackReturn::SUCCESS;
216 }
217
219 {
223 {
224 hazard_light_cmd_ = true;
225 }
226 else
227 {
228 hazard_light_cmd_ = false;
229 }
230 std_msgs::msg::Bool msg;
231 msg.data = hazard_light_cmd_;
232 hazard_light_cmd_pub_->publish(msg);
233 }
234
236 // Trigger timeout event if a timeout has occurred for the currently tracked ERV
238 double seconds_since_prev_update = (this->get_clock()->now() - tracked_erv_.latest_update_time).seconds();
239
240 if(seconds_since_prev_update >= config_.timeout_duration){
241 RCLCPP_ERROR_STREAM(rclcpp::get_logger(logger_name), "Timeout occurred for ERV " << tracked_erv_.vehicle_id);
242 has_tracked_erv_ = false;
245 }
246 }
247 }
248
251 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Sending a warning message to " << tracked_erv_.vehicle_id);
252
253 carma_v2x_msgs::msg::EmergencyVehicleResponse msg;
254 msg.m_header.sender_id = config_.vehicle_id;
255 msg.m_header.recipient_id = tracked_erv_.vehicle_id;
256
257 msg.can_change_lanes = false;
258 msg.reason_exists = true;
259 msg.reason = "Vehicle " + config_.vehicle_id + " is unable to change lanes.";
260
262
264
265 // Reset counter and boolean flag if the maximum number of warnings have been broadcasted
267 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Maximum number of warning messages sent to " << tracked_erv_.vehicle_id);
270 }
271 }
272 }
273
275 // Generate the status message and publish it to the applicable ROS topic
276 carma_msgs::msg::UIInstructions status_msg = generateApproachingErvStatusMessage();
277 approaching_erv_status_pub_->publish(status_msg);
278 }
279
281 // Initialize the message that will be returned by this function
282 carma_msgs::msg::UIInstructions status_msg;
283 status_msg.type = carma_msgs::msg::UIInstructions::INFO;
284
294 boost::format fmter(APPROACHING_ERV_STATUS_PARAMS);
295
296 // Index 0 of formatted string; indicates whether an ERV that is approaching the ego vehicle is being tracked
298 fmter %true;
299 }
300 else{
301 fmter %false;
302 }
303
304 // Index 1 of formatted string; indicates estimated time until tracked ERV passes the ego vehicle
306
307 // Add Index 2 of formatted string based on this plugin's current ApproachingEmergencyVehicleState
308 switch (transition_table_.getState())
309 {
311 fmter %"No action.";
312 break;
313
315 // Add ego vehicle action based on the direction of the upcoming lane change
317 fmter %"Approaching ERV is in our lane. Attempting to change lanes to the right.";
318 }
319 else{
320 fmter %"Approaching ERV is in our lane. Attempting to change lanes to the left.";
321 }
322
323 break;
324
326 // Add ego vehicle action based on whether the current speed is near the reduced target speed of the latest maneuver plan's first maneuver
327 if(!latest_maneuver_plan_.maneuvers.empty()){
328 // Extract the target speed in m/s from the latest maneuver plan's first maneuver and convert it to mph
329 double target_speed_ms = getManeuverEndSpeed(latest_maneuver_plan_.maneuvers[0]);
330 int target_speed_mph = std::round(target_speed_ms * METERS_PER_SEC_TO_MILES_PER_HOUR);
331
334 if(abs(current_speed_ - target_speed_ms) <= config_.reduced_speed_buffer){
335 fmter %("Approaching ERV is in our lane and a lane change is not possible. Remaining in the current lane at a reduced speed of " + std::to_string(target_speed_mph) + " mph.");
336 }
337 else{
338 fmter %("Approaching ERV is in our lane and a lane change is not possible. Remaining in the current lane and slowing down to a reduced speed of " + std::to_string(target_speed_mph) + " mph.");
339 }
340 }
341 else{
342 int non_reduced_speed_to_maintain_mph = std::round(non_reduced_speed_to_maintain_ * METERS_PER_SEC_TO_MILES_PER_HOUR);
343 fmter %("Approaching ERV is in our lane and a lane change is not possible. Remaining in the current lane and maintaining a speed of " + std::to_string(non_reduced_speed_to_maintain_mph) + " mph.");
344 }
345 }
346 else{
347 if(abs(current_speed_ - target_speed_ms) <= config_.reduced_speed_buffer){
348 fmter %("Approaching ERV is in adjacent lane. Remaining in the current lane at a reduced speed of " + std::to_string(target_speed_mph) + " mph.");
349 }
350 else{
351 fmter %("Approaching ERV is in adjacent lane. Remaining in the current lane and slowing down to a reduced speed of " + std::to_string(target_speed_mph) + " mph.");
352 }
353 }
354 }
355 else{
356 throw std::invalid_argument("State is SLOWING_DOWN_FOR_ERV but latest maneuver plan is empty!");
357 }
358 break;
359
360 default:
361 throw std::invalid_argument("Transition table in unsupported state");
362 }
363
364 // Add formatted string to status message
365 std::string str = fmter.str();
366 status_msg.msg = str;
367
368 return status_msg;
369 }
370
371 boost::optional<lanelet::BasicPoint2d> ApproachingEmergencyVehiclePlugin::getErvPositionInMap(const double& current_latitude, const double& current_longitude){
372 // Check if the map projection is available
373 if (!map_projector_) {
374 throw std::invalid_argument("Attempting to get ERV's current position in map before map projection was set");
375 }
376
377 // Build map projector
378 lanelet::projection::LocalFrameProjector projector(map_projector_.get().c_str());
379
380 // Create vector to hold ERV's projected current position
381 std::vector<lanelet::BasicPoint3d> erv_current_position_projected_vec;
382
383 // Add ERV's current location to the beginning of erv_current_position_projected_vec
384 lanelet::GPSPoint current_erv_location;
385 current_erv_location.lat = current_latitude;
386 current_erv_location.lon = current_longitude;
387
388 // Convert ERV's projected position to its position in the map frame
389 erv_current_position_projected_vec.emplace_back(projector.forward(current_erv_location));
390 auto erv_current_position_in_map_vec = lanelet::utils::transform(erv_current_position_projected_vec, [](auto a) { return lanelet::traits::to2D(a); });
391
392 // Conduct size check since only the first element is being returned
393 if(!erv_current_position_in_map_vec.empty()){
394 return erv_current_position_in_map_vec[0];
395 }
396 else{
397 return boost::optional<lanelet::BasicPoint2d>();
398 }
399 }
400
401 boost::optional<ErvInformation> ApproachingEmergencyVehiclePlugin::getErvInformationFromBsm(carma_v2x_msgs::msg::BSM::UniquePtr msg){
402 // Initialize ErvInformation object, which will be populated with information from this BSM if it is from an ERV
403 ErvInformation erv_information;
404
405 // Get vehicle_id from the BSM
406 std::stringstream ss;
407 for(size_t i = 0; i < msg->core_data.id.size(); ++i){
408 ss << std::setfill('0') << std::setw(2) << std::hex << (unsigned)msg->core_data.id.at(i);
409 }
410 erv_information.vehicle_id = ss.str();
411
412 // Check whether vehicle's lights and sirens are active
413 bool has_active_lights_and_sirens = false;
414 if(msg->presence_vector & carma_v2x_msgs::msg::BSM::HAS_PART_II){
415
416 // Parse BSM Part II Content to determine whether the vehicle's lights and sirens are active
417 if(msg->part_ii.size() > 0){
418 for(size_t i = 0; i < msg->part_ii.size(); ++i){
419
420 // special_vehicle_extensions contain the status of the vehicle's lights and sirens
421 if(msg->part_ii[i].part_ii_id == carma_v2x_msgs::msg::BSMPartIIExtension::SPECIAL_VEHICLE_EXT){
422 carma_v2x_msgs::msg::SpecialVehicleExtensions special_vehicle_ext = msg->part_ii[i].special_vehicle_extensions;
423
424 if(special_vehicle_ext.presence_vector & carma_v2x_msgs::msg::SpecialVehicleExtensions::HAS_VEHICLE_ALERTS){
425 if(special_vehicle_ext.vehicle_alerts.siren_use.siren_in_use == j2735_v2x_msgs::msg::SirenInUse::IN_USE
426 && special_vehicle_ext.vehicle_alerts.lights_use.lightbar_in_use == j2735_v2x_msgs::msg::LightbarInUse::IN_USE){
427 has_active_lights_and_sirens = true;
428 }
429 }
430 }
431 }
432 }
433 }
434
435 if(!has_active_lights_and_sirens){
436 // BSM is not a valid ERV BSM since the lights and sirens are not both active; return an empty object
437 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "BSM is not a valid ERV BSM since the lights and sirens are not both active; return an empty object");
438
439 return boost::optional<ErvInformation>();
440 }
441
442 // Get vehicle's current speed from the BSM
443 if(msg->core_data.presence_vector & carma_v2x_msgs::msg::BSMCoreData::SPEED_AVAILABLE){
444 erv_information.current_speed = msg->core_data.speed;
445 }
446 else{
447 // BSM is not a valid ERV BSM since current speed is not included; return an empty object
448 RCLCPP_ERROR_STREAM(rclcpp::get_logger(logger_name), "BSM is not a valid ERV BSM since current speed is not included; return an empty object");
449
450 return boost::optional<ErvInformation>();
451 }
452
453 if(erv_information.current_speed <= current_speed_){
454 RCLCPP_ERROR_STREAM(rclcpp::get_logger(logger_name), "Ignoring received BSM since ERV speed of " << erv_information.current_speed << " is less than ego speed of " << current_speed_);
455 return boost::optional<ErvInformation>();
456 }
457
458 // Get vehicle's current latitude from the BSM
459 if(msg->core_data.presence_vector & carma_v2x_msgs::msg::BSMCoreData::LATITUDE_AVAILABLE){
460 erv_information.current_latitude = msg->core_data.latitude;
461 }
462 else{
463 RCLCPP_ERROR_STREAM(rclcpp::get_logger(logger_name), "No latitude available");
464
465 // BSM is not a valid ERV BSM since current latitude is not included; return an empty object
466 return boost::optional<ErvInformation>();
467 }
468
469 // Get vehicle's current longitude from the BSM
470 if(msg->core_data.presence_vector & carma_v2x_msgs::msg::BSMCoreData::LONGITUDE_AVAILABLE){
471
472 erv_information.current_longitude = msg->core_data.longitude;
473 }
474 else{
475 // BSM is not a valid ERV BSM since current longitude is not included; return an empty object
476 RCLCPP_ERROR_STREAM(rclcpp::get_logger(logger_name), "No longitude available");
477
478 return boost::optional<ErvInformation>();
479 }
480
481 // Get vehicle's current position in the map frame from the BSM
482 boost::optional<lanelet::BasicPoint2d> erv_position_in_map = getErvPositionInMap(erv_information.current_latitude, erv_information.current_longitude);
483
484 if(erv_position_in_map){
485 erv_information.current_position_in_map = *erv_position_in_map;
486 }
487
488 // Get vehicle's route destination points from the BSM
489 std::vector<carma_v2x_msgs::msg::Position3D> erv_destination_points;
490 if(msg->presence_vector & carma_v2x_msgs::msg::BSM::HAS_REGIONAL){
491
492 // Parse BSM Regional Extensions to determine whether BSM includes the ERV's route destination points
493 if(msg->regional.size() > 0){
494 for(size_t i = 0; i < msg->regional.size(); ++i){
495 if(msg->regional[i].regional_extension_id == carma_v2x_msgs::msg::BSMRegionalExtension::ROUTE_DESTINATIONS){
496 erv_destination_points = msg->regional[i].route_destination_points;
497 break;
498 }
499 }
500 }
501 }
502
503 if(erv_destination_points.empty()){
504 RCLCPP_ERROR_STREAM(rclcpp::get_logger(logger_name), "ERV's BSM does not contain any future destination points");
505
506 // BSM is not a valid ERV BSM since it does not include destination points; return an empty object
507 return boost::optional<ErvInformation>();
508 }
509
510 // Update the latest processing time of this ERV
511 latest_erv_update_times_[erv_information.vehicle_id] = this->now();
512
513 // Generate ERV's route based on its current position and its destination points
514 lanelet::Optional<lanelet::routing::Route> erv_future_route = generateErvRoute(erv_information.current_latitude, erv_information.current_longitude, erv_destination_points);
515
516 if(!erv_future_route){
517 // ERV cannot be tracked since its route could not be generated; return an empty object
518 RCLCPP_ERROR_STREAM(rclcpp::get_logger(logger_name), "The ERV's route could not be generated");
519
520 return boost::optional<ErvInformation>();
521 }
522
523 // Determine the ERV's current lane index
524 // Note: For 'lane index', 0 is rightmost lane, 1 is second rightmost, etc.; Only the current travel direction is considered
525 if(!erv_future_route.get().shortestPath().empty()){
526 lanelet::ConstLanelet erv_current_lanelet = erv_future_route.get().shortestPath()[0];
527
528 // NOTE: this logic checks if the ERV and CMV are on a same direction or not.
529 // Currently this check is sufficient to happen only once due to the use case scenarios
530 if (is_same_direction_.find(erv_information.vehicle_id) == is_same_direction_.end())
531 {
532 is_same_direction_[erv_information.vehicle_id] = false;
533 for (auto llt: erv_future_route.get().shortestPath()) // checks if ERV is on the same path assuming CMV got all of its planned route when detected
534 {
535 if (wm_->getRoute()->contains(llt))
536 {
537 is_same_direction_[erv_information.vehicle_id] = true;
538
539 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Detected that ERV: " << erv_information.vehicle_id << " and CMV are travelling in the SAME direction");
540 break;
541 }
542 }
543 }
544
545 if (!is_same_direction_[erv_information.vehicle_id]) // opposite direction
546 {
547 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Detected that ERV and CMV are travelling in DIFFERENT directions");
548 return boost::optional<ErvInformation>(); // if opposite direction, do not track
549 }
550
551 // Get ERV's lane index
552 int lane_index = wm_->getMapRoutingGraph()->rights(erv_current_lanelet).size();
553
554 // A currently-tracked ERV must report the same lane index twice in a row before it is assigned the new lane index
556 if(lane_index == tracked_erv_.previous_lane_index){
557 erv_information.lane_index = lane_index;
558 }
559 else{
561 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Detected first new lane index of " << lane_index << ", ERV's lane index will remain " << tracked_erv_.previous_lane_index);
562 }
563
564 erv_information.previous_lane_index = lane_index;
565 }
566 else{
567 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "First time detecting this ERV, it's lane index will be " << lane_index);
568 erv_information.lane_index = lane_index;
569 erv_information.previous_lane_index = lane_index;
570 }
571
572
573 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "ERV's lane index is " << erv_information.lane_index);
574
575 }
576 else{
577 RCLCPP_ERROR_STREAM(rclcpp::get_logger(logger_name), "ERV's shortest path is empty!");
578 }
579
580 // Get intersecting lanelet between ERV's future route and ego vehicle's future shortest path
581 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Calling getRouteIntersectingLanelet");
582 boost::optional<lanelet::ConstLanelet> intersecting_lanelet = getRouteIntersectingLanelet(erv_future_route.get());
583 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Done calling getRouteIntersectingLanelet");
584
585 if(intersecting_lanelet){
586 erv_information.intersecting_lanelet = *intersecting_lanelet;
587 }
588 else{
589 // No intersecting lanelet between ERV and ego vehicle was found; return an empty object
590 RCLCPP_ERROR_STREAM(rclcpp::get_logger(logger_name), "No intersecting lanelet between the ERV's future route and the ego vehicle's future route was found.");
591
592 return boost::optional<ErvInformation>();
593 }
594
595 // Get the time (seconds) until the ERV passes the ego vehicle
596 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Calling getSecondsUntilPassing()");
597 boost::optional<double> seconds_until_passing = getSecondsUntilPassing(erv_future_route, erv_information.current_position_in_map, erv_information.current_speed, erv_information.intersecting_lanelet);
598 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Done calling getSecondsUntilPassing()");
599
600 if(seconds_until_passing){
601 erv_information.seconds_until_passing = seconds_until_passing.get();
602 RCLCPP_INFO_STREAM(rclcpp::get_logger(logger_name), "Detected approaching ERV that is passing ego vehicle in " << seconds_until_passing.get() << " seconds");
603 }
604 else{
605 RCLCPP_ERROR_STREAM(rclcpp::get_logger(logger_name), "Detected ERV is not approaching the ego vehicle");
606
607 // ERV will not be tracked since it is not considered to be approaching the ego vehicle; return an empty object
608 return boost::optional<ErvInformation>();
609 }
610
611 return erv_information;
612 }
613
614 void ApproachingEmergencyVehiclePlugin::georeferenceCallback(const std_msgs::msg::String::UniquePtr msg)
615 {
616 // Build projector from proj string
617 map_projector_ = msg->data;
618 }
619
620 void ApproachingEmergencyVehiclePlugin::incomingEmergencyVehicleAckCallback(const carma_v2x_msgs::msg::EmergencyVehicleAck::UniquePtr msg)
621 {
622 // Only process message if it is from the currently-tracked ERV and it is intended for the ego vehicle
623 if(has_tracked_erv_ && (msg->m_header.sender_id == tracked_erv_.vehicle_id) && (msg->m_header.recipient_id == config_.vehicle_id)){
624 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "EmergencyVehicleAck received from ERV " << tracked_erv_.vehicle_id);
625
626 // Reset counter and boolean flag to stop warning messages from being broadcasted
629 }
630 }
631
632
633 lanelet::Optional<lanelet::routing::Route> ApproachingEmergencyVehiclePlugin::generateErvRoute(double current_latitude, double current_longitude,
634 std::vector<carma_v2x_msgs::msg::Position3D> erv_destination_points){
635 // Check if the map projection is available
636 if (!map_projector_) {
637 throw std::invalid_argument("Attempting to generate an ERV's route before map projection was set");
638 }
639
640 // Build map projector
641 lanelet::projection::LocalFrameProjector projector(map_projector_.get().c_str());
642
643 // Create vector to hold ERV's destination points
644 std::vector<lanelet::BasicPoint3d> erv_destination_points_projected;
645
646 // Add ERV's current location to the beginning of erv_destination_points
647 lanelet::GPSPoint current_erv_location;
648
649 current_erv_location.lat = current_latitude;
650 current_erv_location.lon = current_longitude;
651
652
653
654 // Add ERV's future destination points to erv_destination_points
655 if(erv_destination_points.size() > 0){
656 for(size_t i = 0; i < erv_destination_points.size(); ++i){
657 carma_v2x_msgs::msg::Position3D position_3d_point = erv_destination_points[i];
658
659 lanelet::GPSPoint erv_destination_point;
660 erv_destination_point.lon = position_3d_point.longitude;
661 erv_destination_point.lat = position_3d_point.latitude;
662
663 if(position_3d_point.elevation_exists){
664 erv_destination_point.ele = position_3d_point.elevation;
665 }
666
667 erv_destination_points_projected.emplace_back(projector.forward(erv_destination_point));
668 }
669 }
670
671 // Convert ERV destination points to map frame
672 auto erv_destination_points_in_map = lanelet::utils::transform(erv_destination_points_projected, [](auto a) { return lanelet::traits::to2D(a); });
673
674 auto cmv_location = lanelet::traits::to2D(projector.forward(current_erv_location));
675 auto shortened_erv_destination_points_in_map = filter_points_ahead(cmv_location, erv_destination_points_in_map);
676
677 if(shortened_erv_destination_points_in_map.empty())
678 {
679 RCLCPP_ERROR_STREAM(rclcpp::get_logger(logger_name), "ERV has passed all the destination points!");
680
681 // Return empty route
682 return lanelet::Optional<lanelet::routing::Route>();
683 }
684
685 // Verify that ERV destination points are geometrically in the map
686 for(size_t i = 0; i < shortened_erv_destination_points_in_map.size(); ++i){
687 auto pt = shortened_erv_destination_points_in_map[i];
688
689 // Return empty route if a destination point is not contained within the map
690 if((wm_->getLaneletsFromPoint(shortened_erv_destination_points_in_map[i])).empty()){
691 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "ERV destination point " << i
692 << " is not contained in a lanelet map; x: " << pt.x() << " y: " << pt.y());
693
694 return lanelet::Optional<lanelet::routing::Route>();
695 }
696 }
697
698 // Obtain ERV's starting lanelet
699 auto starting_lanelet_vector = lanelet::geometry::findNearest(wm_->getMap()->laneletLayer, cmv_location, 1);
700 if(starting_lanelet_vector.empty())
701 {
702 RCLCPP_ERROR_STREAM(rclcpp::get_logger(logger_name), "Found no lanelets in the map. ERV routing cannot be completed.");
703
704 // Return empty route
705 return lanelet::Optional<lanelet::routing::Route>();
706 }
707 auto starting_lanelet = lanelet::ConstLanelet(starting_lanelet_vector[0].second.constData());
708
709 // Obtain ERV's ending lanelet
710 auto ending_lanelet_vector = lanelet::geometry::findNearest(wm_->getMap()->laneletLayer, shortened_erv_destination_points_in_map.back(), 1);
711 auto ending_lanelet = lanelet::ConstLanelet(ending_lanelet_vector[0].second.constData());
712 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "ending_lanelet: " << ending_lanelet.id());
713 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "starting_lanelet: " << starting_lanelet.id());
714
715 // Obtain ERV's via lanelets
716 std::vector<lanelet::BasicPoint2d> via = std::vector<lanelet::BasicPoint2d>(shortened_erv_destination_points_in_map.begin(), shortened_erv_destination_points_in_map.end() - 1);
717 lanelet::ConstLanelets via_lanelets_vector;
718 for(const auto& point : via){
719 auto lanelet_vector = lanelet::geometry::findNearest(wm_->getMap()->laneletLayer, point, 1);
720 auto chosen_lanelet_to_emplace = lanelet::ConstLanelet(lanelet_vector[0].second.constData());
721 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "chosen_lanelet_to_emplace: " << chosen_lanelet_to_emplace.id());
722
723 if (chosen_lanelet_to_emplace.id() != starting_lanelet.id()) // if id is same, it fails to route
724 via_lanelets_vector.emplace_back(chosen_lanelet_to_emplace);
725 else
726 {
727 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "======> id was found same: " << chosen_lanelet_to_emplace.id());
728 }
729 }
730
731 // Generate the ERV's route
732 auto erv_route = wm_->getMapRoutingGraph()->getRouteVia(starting_lanelet, via_lanelets_vector, ending_lanelet);
733
734 return erv_route;
735 }
736
737 std::vector<lanelet::BasicPoint2d> ApproachingEmergencyVehiclePlugin::filter_points_ahead(const lanelet::BasicPoint2d& reference_point, const std::vector<lanelet::BasicPoint2d>& original_points) const
738 {
739 if (original_points.size() <= 1)
740 {
741 return original_points;
742 }
743
744 // extend the list by extrapolating last two points
745 auto extended_points = original_points;
746 double last_dx = (original_points.end() - 1 ) ->x() - (original_points.end() - 2 ) ->x();
747 double last_dy = (original_points.end() - 1 ) ->y() - (original_points.end() - 2 ) ->y();
748 lanelet::BasicPoint2d extended_point = {(original_points.end() - 1 ) ->x() + last_dx, (original_points.end() - 1 ) ->y() + last_dy};
749 extended_points.push_back(extended_point);
750
751 size_t i = 0;
752 size_t closest_idx;
753 double closest_dist = DBL_MAX;
754 while (i < extended_points.size() - 1)
755 {
756 // Calculate vectors
757 double v1x = reference_point.x() - extended_points[i].x();
758
759 double v1y = reference_point.y() - extended_points[i].y();
760 double v2x = extended_points[i+1].x() - extended_points[i].x();
761 double v2y = extended_points[i+1].y() - extended_points[i].y();
762
763 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "reference_point x: " << reference_point.x() << ", y: " << reference_point.y());
764 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "extended_points x: " << extended_points[i].x() << ", y: " << extended_points[i].y());
765
766
767 // Calculate dot product
768 double dotProduct = v1x * v2x + v1y * v2y;
769
770 // Calculate magnitudes
771 double v1Mag = sqrt(v1x * v1x + v1y * v1y);
772 double v2Mag = sqrt(v2x * v2x + v2y * v2y);
773
774 // Calculate angle in radians
775 double angleRad = acos(dotProduct / (v1Mag * v2Mag));
776
777 // Angle between the vectors above 90 degrees means the point i is ahead
778 if (angleRad >= M_PI / 2)
779 {
780 double dx = reference_point.x() - extended_points[i].x();
781 double dy = reference_point.y() - extended_points[i].y();
782 double distance = sqrt (dx * dx + dy * dy);
783 if (distance < closest_dist) // get closest point to the reference point from the multiple possible points
784 {
785 closest_dist = distance;
786 closest_idx = i;
787 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "closest_dist: " << closest_dist << ", closest_idx" << closest_idx);
788
789 }
790 }
791 i ++;
792 }
793
794 double dx = reference_point.x() - original_points.back().x();
795 double dy = reference_point.y() - original_points.back().y();
796 double distance = sqrt (dx * dx + dy * dy);
797
798 // last point is still closer to the reference point, all points have passed
799 // note that if the optimal point is the last one, this check fails as intended
800 if (distance < closest_dist)
801 {
802 RCLCPP_ERROR_STREAM(rclcpp::get_logger(logger_name), "Returning empty here");
803
804 return {};
805 }
806
807 return std::vector<lanelet::BasicPoint2d>(original_points.begin() + closest_idx, original_points.end());
808 }
809 void ApproachingEmergencyVehiclePlugin::incomingBsmCallback(carma_v2x_msgs::msg::BSM::UniquePtr msg)
810 {
811 // Only process incoming BSMs if guidance is currently engaged
813 return;
814 }
815
816 // Get the vehicle ID associated with the received BSM
817 std::stringstream ss;
818 for(size_t i = 0; i < msg->core_data.id.size(); ++i){
819 ss << std::setfill('0') << std::setw(2) << std::hex << (unsigned)msg->core_data.id.at(i);
820 }
821 std::string erv_vehicle_id = ss.str();
822 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Received a BSM from " << erv_vehicle_id);
823
825 // If there is already an ERV approaching the ego vehicle, only process this BSM further if it is from that ERV and enough time has passed since the previously processed BSM
826
827 if(erv_vehicle_id == tracked_erv_.vehicle_id){
828 double seconds_since_prev_processed_bsm = (this->now() - tracked_erv_.latest_update_time).seconds();
829
830 if(seconds_since_prev_processed_bsm < (1.0 / config_.bsm_processing_frequency)){
831 // Do not process ERV's BSM further since not enough time has passed since its previously processed BSM
832 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Ignoring BSM from tracked ERV " << erv_vehicle_id << " since a BSM from it was processed " << seconds_since_prev_processed_bsm << " seconds ago");
833 return;
834 }
835 }
836 else{
837 // Do not process BSM further since it is not for the currently tracked ERV
838 return;
839 }
840 }
841 else{
842
843 // If BSM is from a detected active ERV, only process it further if enough time has passed since the previously processed BSM from this ERV
844 if (latest_erv_update_times_.find(erv_vehicle_id) != latest_erv_update_times_.end()){
845 double seconds_since_prev_processed_bsm = (this->now() - latest_erv_update_times_[erv_vehicle_id]).seconds();
846
847 if(seconds_since_prev_processed_bsm < (1.0 / config_.bsm_processing_frequency)){
848 // Do not process ERV's BSM further since not enough time has passed since its previously processed BSM
849 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Ignoring BSM from non tracked ERV " << erv_vehicle_id << " since a BSM from it was processed " << seconds_since_prev_processed_bsm << " seconds ago");
850 return;
851 }
852 }
853 }
854
855 // Get ErvInformation object with information from the BSM if it is from an active ERV that is approaching the ego vehicle
856 boost::optional<ErvInformation> erv_information = getErvInformationFromBsm(std::move(msg));
857 if(!erv_information){
858 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "BSM is not from an active ERV that is approaching the ego vehicle.");
859
861 RCLCPP_INFO_STREAM(rclcpp::get_logger(logger_name), "BSM was from the currently tracked approaching ERV! ERV is no longer approaching.");
862 has_tracked_erv_ = false;
865 }
866
867 // BSM is not from an active ERV that is approaching the ego vehicle
868 return;
869 }
870 else{
871 // Update tracked ERV information since this is an active ERV that is approaching the ego vehicle
872 tracked_erv_ = *erv_information;
873 tracked_erv_.latest_update_time = this->now();
874
875 if(!has_tracked_erv_){
876 has_tracked_erv_ = true;
877 }
878 }
879
880 return;
881 }
882
883 boost::optional<double> ApproachingEmergencyVehiclePlugin::getSecondsUntilPassing(lanelet::Optional<lanelet::routing::Route>& erv_future_route, const lanelet::BasicPoint2d& erv_position_in_map,
884 const double& erv_current_speed, lanelet::ConstLanelet& intersecting_lanelet){
885
886 // Obtain ego vehicle and ERV distances to the end of the intersecting lanelet so neither vehicle will currently be past that point
887 lanelet::ConstLineString2d intersecting_centerline = lanelet::utils::to2D(intersecting_lanelet.centerline());
888 lanelet::BasicPoint2d intersecting_end_point = intersecting_centerline.back();
889
890 // Get ego vehicle's (its rear bumper) distance to the intersecting lanelet's centerline endpoint
891 double ego_dist_to_lanelet = wm_->routeTrackPos(intersecting_end_point).downtrack - (latest_route_state_.down_track - config_.vehicle_length);
892
893 // Set erv_world_model_ route to the erv_future_route
894 lanelet::routing::Route route = std::move(*erv_future_route);
895 carma_wm::LaneletRoutePtr erv_future_route_ptr = std::make_shared<lanelet::routing::Route>(std::move(route));
896 erv_world_model_->setRoute(erv_future_route_ptr);
897
898 // Get ERV's distance to the intersecting lanelet's centerline endpoint
899 double erv_dist_to_lanelet = erv_world_model_->routeTrackPos(intersecting_end_point).downtrack - erv_world_model_->routeTrackPos(erv_position_in_map).downtrack;
900
901 if(erv_dist_to_lanelet < ego_dist_to_lanelet){
902 // When ERV is in front of the ego vehicle, only process further if the ERV is actively passing the ego vehicle
903
905 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Detected ERV is in front of the ego vehicle");
906 return boost::optional<double>();
907 }
908 else{
909 // This ERV is actively passing the ego vehicle until its distance in front of the ego vehicle is at least config_.finished_passing_threshold
910 if((ego_dist_to_lanelet - erv_dist_to_lanelet) < config_.finished_passing_threshold){
911 // Return zero to indicate that the ERV is currently passing the ego vehicle
912 return 0.0;
913 }
914 else{
915 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "ERV has passed the ego vehicle");
916 has_tracked_erv_ = false;
919 return boost::optional<double>();
920 }
921 }
922 }
923 else{
924 // When ERV is behind the ego vehicle, only process further if the ERV is travelling faster than the ego vehicle
925
926 if(erv_current_speed < current_speed_){
927 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Detected ERV is travelling slower than the ego vehicle, and will not pass the ego vehicle");
928 return boost::optional<double>();
929 }
930 else{
931 // Calculate seconds_until_passing and protect against division by zero
932 double delta_speed = erv_current_speed - current_speed_;
933
934 if(delta_speed == 0.0){
935 delta_speed = epsilon_;
936 }
937 double seconds_until_passing = (erv_dist_to_lanelet - ego_dist_to_lanelet) / delta_speed;
938
939 return seconds_until_passing;
940 }
941 }
942 }
943
944 boost::optional<lanelet::ConstLanelet> ApproachingEmergencyVehiclePlugin::getRouteIntersectingLanelet(const lanelet::routing::Route& erv_future_route){
945
946 if(future_route_lanelet_ids_.empty()){
947 RCLCPP_WARN_STREAM(rclcpp::get_logger(logger_name), "Remaining route lanelets for the ego vehicle not found; plugin cannot compute the intersecting lanelet.");
948 return boost::optional<lanelet::ConstLanelet>();
949 }
950
951 // Get current downtrack from latest_route_state_
952 double current_downtrack = latest_route_state_.down_track;
953
954 // Find first successful intersecting lanelet between ERV route and ego route with a lanelet starting downtrack greater than current downtrack.
955 // Additionally, remove lanelets from future_route_lanelet_ids_ that the ego vehicle has passed.
956 for(auto it = future_route_lanelet_ids_.begin(); it != future_route_lanelet_ids_.end();){
957 // Get lanelet
958 lanelet::Id ego_route_lanelet_id = *it;
959 lanelet::ConstLanelet ego_route_lanelet = wm_->getMap()->laneletLayer.get(ego_route_lanelet_id);
960
961 // Get lanelet's centerline end point downtrack
962 lanelet::BasicPoint2d ego_route_lanelet_centerline_end = lanelet::utils::to2D(ego_route_lanelet.centerline()).back();
963 double ego_route_lanelet_centerline_end_downtrack = wm_->routeTrackPos(ego_route_lanelet_centerline_end).downtrack;
964
965 if(current_downtrack > ego_route_lanelet_centerline_end_downtrack){
966 // If current downtrack is greater than lanelet ending downtrack, remove lanelet from future route lanelet and continue
967 // NOTE: Iterator is not updated since an element is being erased
968 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Removing passed lanelet " << ego_route_lanelet_id);
970 }
971 else{
972 // If lanelet exists in both, return it as the intersecting lanelet
973 if(erv_future_route.contains(ego_route_lanelet)){
974 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Found intersecting lanelet " << ego_route_lanelet_id);
975 return boost::optional<lanelet::ConstLanelet>(ego_route_lanelet);
976 }
977 else{
978 // Increase iterator
979 ++it;
980 }
981 }
982 }
983 }
984
985 void ApproachingEmergencyVehiclePlugin::routeStateCallback(carma_planning_msgs::msg::RouteState::UniquePtr msg)
986 {
987 latest_route_state_ = *msg;
988 }
989
990 void ApproachingEmergencyVehiclePlugin::twistCallback(geometry_msgs::msg::TwistStamped::UniquePtr msg){
991 current_speed_ = msg->twist.linear.x;
992 }
993
994 void ApproachingEmergencyVehiclePlugin::guidanceStateCallback(carma_planning_msgs::msg::GuidanceState::UniquePtr msg){
997 }
998 else{
999 is_guidance_engaged_ = false;
1000 }
1001 }
1002
1003 void ApproachingEmergencyVehiclePlugin::routeCallback(carma_planning_msgs::msg::Route::UniquePtr msg){
1004 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Received route callback");
1005 std::vector<int> new_future_route_lanelet_ids;
1006
1007 for(size_t i = 0; i < msg->route_path_lanelet_ids.size(); ++i){
1008 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "New route lanelet: " << msg->route_path_lanelet_ids[i]);
1009 new_future_route_lanelet_ids.push_back(msg->route_path_lanelet_ids[i]);
1010 }
1011
1012 future_route_lanelet_ids_ = new_future_route_lanelet_ids;
1013 }
1014
1016 {
1017 double speed_limit = config_.default_speed_limit;
1018
1019 lanelet::Optional<carma_wm::TrafficRulesConstPtr> traffic_rules = wm_->getTrafficRules();
1020 if (traffic_rules)
1021 {
1022 speed_limit =(*traffic_rules)->speedLimit(lanelet).speedLimit.value();
1023 }
1024 else
1025 {
1026 throw std::invalid_argument("No speed limit could be found since valid traffic rules object could not be built");
1027 }
1028
1029 return speed_limit;
1030 }
1031
1032 rclcpp::Duration ApproachingEmergencyVehiclePlugin::getManeuverDuration(const carma_planning_msgs::msg::Maneuver &maneuver, double epsilon) const
1033 {
1034 double maneuver_start_speed = GET_MANEUVER_PROPERTY(maneuver, start_speed);
1035 double maneuver_end_speed = getManeuverEndSpeed(maneuver);
1036 double sum_start_and_end_speed = maneuver_start_speed + maneuver_end_speed;
1037
1038 if(sum_start_and_end_speed < epsilon){
1039 throw std::invalid_argument("Maneuver start and ending speed is zero");
1040 }
1041
1042 rclcpp::Duration maneuver_duration{0,0};
1043 double maneuver_start_dist = GET_MANEUVER_PROPERTY(maneuver, start_dist);
1044 double maneuver_end_dist = GET_MANEUVER_PROPERTY(maneuver, end_dist);
1045
1046 RCLCPP_ERROR_STREAM(rclcpp::get_logger(logger_name),"maneuver_end_dist: " << maneuver_end_dist << ", maneuver_start_dist: " << maneuver_start_dist << ", sum_start_and_end_speed: " << sum_start_and_end_speed);
1047
1048 maneuver_duration = rclcpp::Duration((maneuver_end_dist - maneuver_start_dist) / (0.5 * sum_start_and_end_speed) * 1e9);
1049
1050 return maneuver_duration;
1051 }
1052
1053 carma_planning_msgs::msg::Maneuver ApproachingEmergencyVehiclePlugin::composeLaneFollowingManeuverMessage(double start_dist, double end_dist,
1054 double start_speed, double target_speed, int lanelet_id, rclcpp::Time& start_time) const
1055 {
1056 carma_planning_msgs::msg::Maneuver maneuver_msg;
1057
1058 maneuver_msg.type = carma_planning_msgs::msg::Maneuver::LANE_FOLLOWING;
1059 maneuver_msg.lane_following_maneuver.parameters.negotiation_type = carma_planning_msgs::msg::ManeuverParameters::NO_NEGOTIATION;
1060 maneuver_msg.lane_following_maneuver.parameters.presence_vector = carma_planning_msgs::msg::ManeuverParameters::HAS_TACTICAL_PLUGIN;
1061 maneuver_msg.lane_following_maneuver.parameters.planning_tactical_plugin = config_.lane_following_plugin;
1062 maneuver_msg.lane_following_maneuver.parameters.planning_strategic_plugin = strategic_plugin_name_;
1063 maneuver_msg.lane_following_maneuver.start_dist = start_dist;
1064 maneuver_msg.lane_following_maneuver.start_speed = start_speed;
1065 maneuver_msg.lane_following_maneuver.start_time = start_time;
1066 maneuver_msg.lane_following_maneuver.end_dist = end_dist;
1067 maneuver_msg.lane_following_maneuver.end_speed = target_speed;
1068 maneuver_msg.lane_following_maneuver.lane_ids = { std::to_string(lanelet_id) };
1069
1070 rclcpp::Duration maneuver_duration = getManeuverDuration(maneuver_msg, epsilon_);
1071 maneuver_msg.lane_following_maneuver.end_time = start_time + maneuver_duration;
1072
1073 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Composed lane follow maneuver for lanelet ID:" << lanelet_id << " with duration " << maneuver_duration.seconds());
1074 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "start speed: " << start_speed << ", end speed: " << target_speed);
1075 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "start dist: " << start_dist << ", end dist: " << end_dist);
1076
1077 return maneuver_msg;
1078 }
1079
1080 carma_planning_msgs::msg::Maneuver ApproachingEmergencyVehiclePlugin::composeLaneChangeManeuverMessage(double start_dist, double end_dist,
1081 double start_speed, double target_speed, lanelet::Id starting_lane_id, lanelet::Id ending_lane_id, rclcpp::Time& start_time) const
1082 {
1083 carma_planning_msgs::msg::Maneuver maneuver_msg;
1084 maneuver_msg.type = carma_planning_msgs::msg::Maneuver::LANE_CHANGE;
1085 maneuver_msg.lane_change_maneuver.parameters.negotiation_type = carma_planning_msgs::msg::ManeuverParameters::NO_NEGOTIATION;
1086 maneuver_msg.lane_change_maneuver.parameters.presence_vector = carma_planning_msgs::msg::ManeuverParameters::HAS_TACTICAL_PLUGIN;
1087 maneuver_msg.lane_change_maneuver.parameters.planning_tactical_plugin = config_.lane_change_plugin;
1088 maneuver_msg.lane_change_maneuver.parameters.planning_strategic_plugin = strategic_plugin_name_;
1089 maneuver_msg.lane_change_maneuver.start_dist = start_dist;
1090 maneuver_msg.lane_change_maneuver.start_speed = start_speed;
1091 maneuver_msg.lane_change_maneuver.start_time = start_time;
1092 maneuver_msg.lane_change_maneuver.end_dist = end_dist;
1093 maneuver_msg.lane_change_maneuver.end_speed = target_speed;
1094 maneuver_msg.lane_change_maneuver.starting_lane_id = std::to_string(starting_lane_id);
1095 maneuver_msg.lane_change_maneuver.ending_lane_id = std::to_string(ending_lane_id);
1096
1097 rclcpp::Duration maneuver_duration = getManeuverDuration(maneuver_msg, epsilon_);
1098 maneuver_msg.lane_change_maneuver.end_time = start_time + maneuver_duration;
1099
1100 // Preserve lane change maneuver ID from previous plan to maintain identical trajectory generation across separate maneuver plans
1102 maneuver_msg.lane_change_maneuver.parameters.maneuver_id = upcoming_lc_params_.maneuver_id;
1103 }
1104 else{
1105 static auto gen = boost::uuids::random_generator(); // Initialize uuid generator
1106 maneuver_msg.lane_change_maneuver.parameters.maneuver_id = boost::lexical_cast<std::string>(gen()); // generate uuid and convert to string
1107 }
1108
1109 RCLCPP_DEBUG_STREAM(get_logger(),"Creating lane change id: " << maneuver_msg.lane_change_maneuver.parameters.maneuver_id << "start dist: " << start_dist << " end dist: " << end_dist << " Starting llt: " << starting_lane_id << " Ending llt: " << ending_lane_id);
1110 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "start speed: " << start_speed << ", end speed: " << target_speed << ", duration: " << maneuver_duration.seconds());
1111 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "start dist: " << start_dist << ", end dist: " << end_dist);
1112
1113 return maneuver_msg;
1114 }
1115
1116 carma_planning_msgs::msg::Maneuver ApproachingEmergencyVehiclePlugin::composeStopAndWaitManeuverMessage(double start_dist, double end_dist, double start_speed,
1117 lanelet::Id starting_lane_id, lanelet::Id ending_lane_id, double stopping_deceleration, rclcpp::Time& start_time) const
1118 {
1119 carma_planning_msgs::msg::Maneuver maneuver_msg;
1120 maneuver_msg.type = carma_planning_msgs::msg::Maneuver::STOP_AND_WAIT;
1121 maneuver_msg.stop_and_wait_maneuver.parameters.negotiation_type = carma_planning_msgs::msg::ManeuverParameters::NO_NEGOTIATION;
1122 maneuver_msg.stop_and_wait_maneuver.parameters.presence_vector = carma_planning_msgs::msg::ManeuverParameters::HAS_TACTICAL_PLUGIN
1123 | carma_planning_msgs::msg::ManeuverParameters::HAS_FLOAT_META_DATA;
1124 maneuver_msg.stop_and_wait_maneuver.parameters.planning_tactical_plugin = config_.stop_and_wait_plugin;
1125 maneuver_msg.stop_and_wait_maneuver.parameters.planning_strategic_plugin = strategic_plugin_name_;
1126 maneuver_msg.stop_and_wait_maneuver.start_time = start_time;
1127 maneuver_msg.stop_and_wait_maneuver.start_dist = start_dist;
1128 maneuver_msg.stop_and_wait_maneuver.start_speed = start_speed;
1129 maneuver_msg.stop_and_wait_maneuver.end_dist = end_dist;
1130 maneuver_msg.stop_and_wait_maneuver.starting_lane_id = std::to_string(starting_lane_id);
1131 maneuver_msg.stop_and_wait_maneuver.ending_lane_id = std::to_string(ending_lane_id);
1132
1133 rclcpp::Duration maneuver_duration = getManeuverDuration(maneuver_msg, epsilon_);
1134 maneuver_msg.stop_and_wait_maneuver.end_time = start_time + maneuver_duration;
1135
1136 // Set the meta-data for the StopAndWait Maneuver to define the buffer in the route end point stopping location
1137 maneuver_msg.stop_and_wait_maneuver.parameters.float_valued_meta_data.push_back(config_.route_end_point_buffer);
1138 maneuver_msg.stop_and_wait_maneuver.parameters.float_valued_meta_data.push_back(stopping_deceleration);
1139
1140 static auto gen = boost::uuids::random_generator(); // Initialize uuid generator
1141 maneuver_msg.stop_and_wait_maneuver.parameters.maneuver_id = boost::lexical_cast<std::string>(gen()); // generate uuid and convert to string
1142
1143 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Composed stop and wait maneuver for with start lanelet:" << starting_lane_id << ", end lanelet: " << ending_lane_id << " with duration " << maneuver_duration.seconds());
1144 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "start speed: " << start_speed);
1145 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "start dist: " << start_dist << ", end dist: " << end_dist);
1146
1147 return maneuver_msg;
1148 }
1149
1150 void ApproachingEmergencyVehiclePlugin::addStopAndWaitToEndOfPlan(carma_planning_msgs::srv::PlanManeuvers::Response::SharedPtr resp,
1151 double downtrack_progress, double stop_maneuver_beginning_downtrack, double end_of_route_downtrack,
1152 double stopping_entry_speed, double stopping_deceleration, double current_lanelet_ending_downtrack,
1153 lanelet::ConstLanelet current_lanelet, rclcpp::Time time_progress)
1154 {
1155 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Maneuver plan is reaching the end of the route");
1156
1157 // Add a lane follow maneuver before the stop and wait maneuver if the distance before stop_maneuver_beginning_downtrack is too large
1158 if((stop_maneuver_beginning_downtrack - downtrack_progress) >= config_.buffer_distance_before_stopping){
1159 // Compose lane follow maneuver and update downtrack_progress
1160 resp->new_plan.maneuvers.push_back(composeLaneFollowingManeuverMessage(downtrack_progress, stop_maneuver_beginning_downtrack,
1161 stopping_entry_speed, stopping_entry_speed, current_lanelet.id(), time_progress));
1162
1163 downtrack_progress = stop_maneuver_beginning_downtrack;
1164 time_progress += getManeuverDuration(resp->new_plan.maneuvers.back(), epsilon_);
1165 }
1166
1167 // Get the starting lanelet ID for the stop and wait maneuver
1168 lanelet::Id start_lane_id = current_lanelet.id();
1169
1170 // Identify the ending lanelet for the stop and wait maneuver
1171 while(current_lanelet_ending_downtrack < end_of_route_downtrack){
1172 // Get the next lanelet in the current lane if it exists
1173 if(!wm_->getMapRoutingGraph()->following(current_lanelet, false).empty()){
1174 current_lanelet = wm_->getMapRoutingGraph()->following(current_lanelet, false).front();
1175 }
1176 else{
1177 RCLCPP_WARN_STREAM(rclcpp::get_logger(logger_name), "A following lanelet in the current lane could not be found; returning empty plan");
1178 resp->new_plan.maneuvers = {};
1179 return;
1180 }
1181
1182 // Check whether the next lanelet is on the route
1183 if(!wm_->getRoute()->contains(current_lanelet)){
1184 RCLCPP_WARN_STREAM(rclcpp::get_logger(logger_name), "The next lanelet in the current lane is not on the route; returning empty plan");
1185 resp->new_plan.maneuvers = {};
1186 return;
1187 }
1188
1189 // Break from loop if lanelet's ending downtrack is after the route end downtrack; this is the maneuver's ending lanelet
1190 current_lanelet_ending_downtrack = wm_->routeTrackPos(current_lanelet.centerline2d().back()).downtrack;
1191 if(current_lanelet_ending_downtrack >= end_of_route_downtrack){
1192 break;
1193 }
1194 }
1195
1196 // Add stop and wait maneuver to maneuver plan
1197 resp->new_plan.maneuvers.push_back(composeStopAndWaitManeuverMessage(downtrack_progress, end_of_route_downtrack,
1198 stopping_entry_speed, start_lane_id, current_lanelet.id(), stopping_deceleration, time_progress));
1199 }
1200
1202 carma_planning_msgs::srv::PlanManeuvers::Response::SharedPtr resp,
1203 lanelet::ConstLanelet current_lanelet, double downtrack_progress, double current_lanelet_ending_downtrack,
1204 double speed_progress, double target_speed, rclcpp::Time time_progress, bool is_maintaining_non_reduced_speed)
1205 {
1206 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Generating remain-in-lane maneuver plan");
1207
1208 // Reset planned lane change flag since plugin will not be planning for an upcoming lane change
1210
1211 // Update maneuver target speed to be a speed being maintained or a reduced speed below the speed limit
1212 if(is_maintaining_non_reduced_speed){
1213 // ERV is close enough that if the ego vehicle slows down it could cause a safety hazard; set target speed to maintain a non-reduced speed
1214 target_speed = non_reduced_speed_to_maintain_;
1215 }
1216 else{
1217 // Reduce target_speed since this method is triggered when an ERV is approaching the ego vehicle
1218 target_speed = std::max((target_speed - config_.speed_limit_reduction_during_passing), config_.minimum_reduced_speed_limit);
1219 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Maneuver target speed reduced to " << target_speed);
1220 }
1221
1222 double maneuver_plan_ending_downtrack = downtrack_progress + config_.minimal_plan_duration * target_speed;
1223 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Ending downtrack based on plan duration: " << maneuver_plan_ending_downtrack);
1224
1225 maneuver_plan_ending_downtrack = std::min(maneuver_plan_ending_downtrack, wm_->getRouteEndTrackPos().downtrack);
1226 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Ending downtrack based on end of route: " << wm_->getRouteEndTrackPos().downtrack);
1227 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Selected ending downtrack: " << maneuver_plan_ending_downtrack);
1228
1229 // Compute target deceleration for stopping
1231
1232 // Compute stopping distance where v_f = 0
1233 // (v_f^2 - v_i^2) / (2*a) = d
1234 double stopping_distance = (target_speed * target_speed) / (2.0 * stopping_deceleration);
1235 double begin_stopping_downtrack = wm_->getRouteEndTrackPos().downtrack - stopping_distance;
1236 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Stop and wait maneuver must begin by downtrack " << begin_stopping_downtrack);
1237
1238 // Generate maneuver plan
1239 while(downtrack_progress < maneuver_plan_ending_downtrack){
1240
1241 if(begin_stopping_downtrack <= current_lanelet_ending_downtrack){
1242 // Complete maneuver plan with a stop and wait maneuver if the current lanelet intercepts the stopping downtrack
1243 addStopAndWaitToEndOfPlan(resp, downtrack_progress, begin_stopping_downtrack, wm_->getRouteEndTrackPos().downtrack,
1244 speed_progress, stopping_deceleration, current_lanelet_ending_downtrack, current_lanelet, time_progress);
1245 return;
1246 }
1247 else{
1248 // Compose lane following maneuver and add it to the response's maneuver plan
1249 resp->new_plan.maneuvers.push_back(composeLaneFollowingManeuverMessage(downtrack_progress, current_lanelet_ending_downtrack,
1250 speed_progress, target_speed, current_lanelet.id(), time_progress));
1251
1252 // Get the next lanelet in the current lane if it exists
1253 if(!wm_->getMapRoutingGraph()->following(current_lanelet, false).empty())
1254 {
1255 current_lanelet = wm_->getMapRoutingGraph()->following(current_lanelet, false).front();
1256 }
1257 else
1258 {
1259 RCLCPP_WARN_STREAM(rclcpp::get_logger(logger_name), "A following lanelet in the current lane could not be found; returning empty plan");
1260 resp->new_plan.maneuvers = {};
1261 return;
1262 }
1263
1264 // Check whether the next lanelet is on the route
1265 if(!wm_->getRoute()->contains(current_lanelet)){
1266 RCLCPP_WARN_STREAM(rclcpp::get_logger(logger_name), "The next lanelet in the current lane is not on the route; returning empty plan");
1267 resp->new_plan.maneuvers = {};
1268 return;
1269 }
1270
1271 // Update the lane follow maneuver parameters for the next maneuver in the plan
1272 downtrack_progress = wm_->routeTrackPos(current_lanelet.centerline2d().front()).downtrack;
1273 current_lanelet_ending_downtrack = wm_->routeTrackPos(current_lanelet.centerline2d().back()).downtrack;
1274 speed_progress = getManeuverEndSpeed(resp->new_plan.maneuvers.back());
1275 target_speed = getLaneletSpeedLimit(current_lanelet);
1276
1277 // Update maneuver target speed to be a speed being maintained or a reduced speed below the speed limit
1278 if(is_maintaining_non_reduced_speed){
1279 // ERV is close enough that if the ego vehicle slows down it could cause a safety hazard; set target speed to maintain a non-reduced speed
1280 target_speed = non_reduced_speed_to_maintain_;
1281 }
1282 else{
1283 // Reduce target_speed if an ERV is actively passing the ego vehicle
1284 target_speed = std::max((target_speed - config_.speed_limit_reduction_during_passing), config_.minimum_reduced_speed_limit);
1285 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Maneuver target speed reduced to " << target_speed);
1286 }
1287
1288 time_progress += getManeuverDuration(resp->new_plan.maneuvers.back(), epsilon_);
1289
1290 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Next maneuver starting downtrack is " << downtrack_progress << ", end of plan is at " << maneuver_plan_ending_downtrack);
1291 }
1292 }
1293 }
1294
1296 carma_planning_msgs::srv::PlanManeuvers::Response::SharedPtr resp,
1297 lanelet::ConstLanelet current_lanelet, double downtrack_progress, double current_lanelet_ending_downtrack,
1298 double speed_progress, double target_speed, rclcpp::Time time_progress, int ego_lane_index, int erv_lane_index)
1299 {
1300 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Generating move-over maneuver plan");
1301
1302 double maneuver_plan_ending_downtrack = downtrack_progress + config_.minimal_plan_duration * target_speed;
1303 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Ending downtrack based on plan duration: " << maneuver_plan_ending_downtrack);
1304
1305 maneuver_plan_ending_downtrack = std::min(maneuver_plan_ending_downtrack, wm_->getRouteEndTrackPos().downtrack);
1306 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Ending downtrack based on end of route: " << wm_->getRouteEndTrackPos().downtrack);
1307 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Selected ending downtrack: " << maneuver_plan_ending_downtrack);
1308
1309 // Compute target deceleration for stopping
1311
1312 // Compute stopping distance where v_f = 0
1313 // (v_f^2 - v_i^2) / (2*a) = d
1314 double stopping_distance = (target_speed * target_speed) / (2.0 * stopping_deceleration);
1315 double begin_stopping_downtrack = wm_->getRouteEndTrackPos().downtrack - stopping_distance;
1316 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Stop and wait maneuver must begin by downtrack " << begin_stopping_downtrack);
1317
1318 // Generate maneuver plan when there is already a planned upcoming lane change
1320 while(downtrack_progress < maneuver_plan_ending_downtrack){
1321 if(begin_stopping_downtrack <= current_lanelet_ending_downtrack){
1322 // Complete maneuver plan with a stop and wait maneuver if the current lanelet intercepts the stopping downtrack
1323 addStopAndWaitToEndOfPlan(resp, downtrack_progress, begin_stopping_downtrack, wm_->getRouteEndTrackPos().downtrack,
1324 speed_progress, stopping_deceleration, current_lanelet_ending_downtrack, current_lanelet, time_progress);
1325 return;
1326 }
1327 else{
1328 // Plan lane change maneuver if current downtrack progress is between the starting and ending downtracks of the planned upcoming lane change, otherwise plan lane follow maneuver
1329 if(((upcoming_lc_params_.start_dist - epsilon_) <= downtrack_progress) && (downtrack_progress < (upcoming_lc_params_.end_dist - epsilon_))){
1332
1333 current_lanelet = upcoming_lc_params_.ending_lanelet;
1334 }
1335 else{
1336 resp->new_plan.maneuvers.push_back(composeLaneFollowingManeuverMessage(downtrack_progress, current_lanelet_ending_downtrack,
1337 speed_progress, target_speed, current_lanelet.id(), time_progress));
1338 }
1339
1340 // Get the next lanelet in the current lane if it exists
1341 if(!wm_->getMapRoutingGraph()->following(current_lanelet, false).empty())
1342 {
1343 current_lanelet = wm_->getMapRoutingGraph()->following(current_lanelet, false).front();
1344 }
1345 else
1346 {
1347 RCLCPP_WARN_STREAM(rclcpp::get_logger(logger_name), "A following lanelet in the current lane could not be found; returning empty plan");
1348 resp->new_plan.maneuvers = {};
1349 return;
1350 }
1351
1352 // Check whether the next lanelet is on the route
1353 if(!wm_->getRoute()->contains(current_lanelet)){
1354 RCLCPP_WARN_STREAM(rclcpp::get_logger(logger_name), "The next lanelet in the current lane is not on the route; returning empty plan");
1355 resp->new_plan.maneuvers = {};
1356 return;
1357 }
1358
1359 // Update the maneuver parameters for the next maneuver in the plan
1360 downtrack_progress = wm_->routeTrackPos(current_lanelet.centerline2d().front()).downtrack;
1361 current_lanelet_ending_downtrack = wm_->routeTrackPos(current_lanelet.centerline2d().back()).downtrack;
1362 speed_progress = getManeuverEndSpeed(resp->new_plan.maneuvers.back());
1363 target_speed = getLaneletSpeedLimit(current_lanelet);
1364 time_progress += getManeuverDuration(resp->new_plan.maneuvers.back(), epsilon_);
1365
1366 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Next maneuver starting downtrack is " << downtrack_progress << ", end of plan is at " << maneuver_plan_ending_downtrack);
1367 }
1368 }
1369 }
1370 else{
1371 bool completed_initial_lane_following = false; // Flag to indicate whether sufficient lane following has been planned before the lane change
1372 double initial_lane_following_duration = 0.0; // Object to store the duration of lane following planned before the lane change
1373
1374 while(downtrack_progress < maneuver_plan_ending_downtrack){
1375
1376 if(begin_stopping_downtrack <= current_lanelet_ending_downtrack){
1377 // Complete maneuver plan with a stop and wait maneuver if the current lanelet intercepts the stopping downtrack
1378 addStopAndWaitToEndOfPlan(resp, downtrack_progress, begin_stopping_downtrack, wm_->getRouteEndTrackPos().downtrack,
1379 speed_progress, stopping_deceleration, current_lanelet_ending_downtrack, current_lanelet, time_progress);
1380 return;
1381 }
1382 else{
1383 // First maneuver(s) are lane-following to enable sufficient time to activate turn signals before conducting lane change
1384 if(!completed_initial_lane_following){
1385 resp->new_plan.maneuvers.push_back(composeLaneFollowingManeuverMessage(downtrack_progress, current_lanelet_ending_downtrack,
1386 speed_progress, target_speed, current_lanelet.id(), time_progress));
1387
1388 initial_lane_following_duration += getManeuverDuration(resp->new_plan.maneuvers.back(), epsilon_).seconds();
1389 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Seconds of lane following before lane change: " << initial_lane_following_duration);
1390
1391 if(initial_lane_following_duration >= config_.min_lane_following_duration_before_lane_change){
1392 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Minimum lane following duration before lane change satisfied.");
1393 completed_initial_lane_following = true;
1394 }
1395 }
1396 else{
1397
1399 // Initialize UpcomingLaneChangeParameters object to store information regarding the planned lane change maneuver
1400 UpcomingLaneChangeParameters new_lc_params;
1401
1402 // Determine if left lane change or right lane change required, and get the target lanelet
1403 lanelet::Optional<lanelet::ConstLanelet> target_lanelet;
1404 if(ego_lane_index != erv_lane_index){
1405 RCLCPP_WARN_STREAM(rclcpp::get_logger(logger_name), "Planning a move-over maneuver plan when the ego vehicle is not in the ERV's path! Returning an empty maneuver plan");
1406 resp->new_plan.maneuvers = {};
1407 return;
1408 }
1409 else if((ego_lane_index == 0) && (erv_lane_index == 0)){
1410 // Ego vehicle and ERV are both in the rightmost lane so a left lane change is required
1411 new_lc_params.is_right_lane_change = false;
1412 target_lanelet = wm_->getMapRoutingGraph()->left(current_lanelet);
1413 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Composing a left lane change maneuver from lanelet " << current_lanelet.id());
1414 }
1415 else{
1416 // A right lane change is required
1417 new_lc_params.is_right_lane_change = true;
1418 target_lanelet = wm_->getMapRoutingGraph()->right(current_lanelet);
1419 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Composing a right lane change maneuver from lanelet " << current_lanelet.id());
1420 }
1421
1422 // Only compose lane change maneuver if the target lanelet exists, otherwise compose lane follow maneuver
1423 if(target_lanelet){
1424 resp->new_plan.maneuvers.push_back(composeLaneChangeManeuverMessage(downtrack_progress, current_lanelet_ending_downtrack,
1425 speed_progress, target_speed, current_lanelet.id(), target_lanelet.get().id(), time_progress));
1426
1428
1429 new_lc_params.starting_lanelet = current_lanelet;
1430 new_lc_params.ending_lanelet = target_lanelet.get();
1431 new_lc_params.start_dist = downtrack_progress;
1432 new_lc_params.end_dist = current_lanelet_ending_downtrack;
1433 new_lc_params.start_speed = speed_progress;
1434 new_lc_params.end_speed = target_speed;
1435 new_lc_params.maneuver_id = resp->new_plan.maneuvers.back().lane_change_maneuver.parameters.maneuver_id;
1436 upcoming_lc_params_ = new_lc_params;
1437
1438 current_lanelet = *target_lanelet;
1439 }
1440 else{
1441 RCLCPP_WARN_STREAM(rclcpp::get_logger(logger_name), "A lane change maneuver from " << current_lanelet.id() << " could not be composed since no target lanelet was found!");
1442 resp->new_plan.maneuvers.push_back(composeLaneFollowingManeuverMessage(downtrack_progress, current_lanelet_ending_downtrack,
1443 speed_progress, target_speed, current_lanelet.id(), time_progress));
1444 }
1445 }
1446 else{
1447 resp->new_plan.maneuvers.push_back(composeLaneFollowingManeuverMessage(downtrack_progress, current_lanelet_ending_downtrack,
1448 speed_progress, target_speed, current_lanelet.id(), time_progress));
1449 }
1450 }
1451
1452 // Get the next lanelet in the current lane if it exists
1453 if(!wm_->getMapRoutingGraph()->following(current_lanelet, false).empty())
1454 {
1455 current_lanelet = wm_->getMapRoutingGraph()->following(current_lanelet, false).front();
1456 }
1457 else
1458 {
1459 RCLCPP_WARN_STREAM(rclcpp::get_logger(logger_name), "A following lanelet in the current lane could not be found; returning empty plan");
1460 resp->new_plan.maneuvers = {};
1461 return;
1462 }
1463
1464 // Check whether the next lanelet is on the route
1465 if(!wm_->getRoute()->contains(current_lanelet)){
1466 RCLCPP_WARN_STREAM(rclcpp::get_logger(logger_name), "The next lanelet in the current lane is not on the route; returning empty plan");
1467 resp->new_plan.maneuvers = {};
1468 return;
1469 }
1470
1471 // Update the maneuver parameters for the next maneuver in the plan
1472 downtrack_progress = wm_->routeTrackPos(current_lanelet.centerline2d().front()).downtrack;
1473 current_lanelet_ending_downtrack = wm_->routeTrackPos(current_lanelet.centerline2d().back()).downtrack;
1474 speed_progress = getManeuverEndSpeed(resp->new_plan.maneuvers.back());
1475 target_speed = getLaneletSpeedLimit(current_lanelet);
1476 time_progress += getManeuverDuration(resp->new_plan.maneuvers.back(), epsilon_);
1477
1478 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Next maneuver starting downtrack is " << downtrack_progress << ", end of plan is at " << maneuver_plan_ending_downtrack);
1479 }
1480 }
1481 }
1482 }
1483
1484 boost::optional<lanelet::ConstLanelet> ApproachingEmergencyVehiclePlugin::getLaneletOnEgoRouteFromMapPosition(const double& x_position, const double& y_position)
1485 {
1486 // Get lanelet(s) that the provided map coordinates are contained within
1487 std::vector<lanelet::ConstLanelet> ego_route_lanelets = wm_->getLaneletsFromPoint({x_position, y_position});
1488 boost::optional<lanelet::ConstLanelet> ego_route_lanelet;
1489
1490 if (ego_route_lanelets.empty())
1491 {
1492 RCLCPP_WARN_STREAM(rclcpp::get_logger(logger_name), "Given vehicle position is not on the road.");
1493 return boost::optional<lanelet::ConstLanelet>();
1494 }
1495
1496 // Get the lanelet that is on the ego vehicle's route in case vehicle is located on overlapping lanelets
1497 for(const auto& lanelet : ego_route_lanelets)
1498 {
1499 if(wm_->getRoute()->contains(lanelet)){
1500 ego_route_lanelet = lanelet;
1501 break;
1502 }
1503 }
1504
1505 return ego_route_lanelet;
1506 }
1507
1509 std::shared_ptr<rmw_request_id_t>,
1510 carma_planning_msgs::srv::PlanManeuvers::Request::SharedPtr req,
1511 carma_planning_msgs::srv::PlanManeuvers::Response::SharedPtr resp)
1512 {
1513 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Received request to plan maneuvers");
1514
1515 // Check whether ego vehicle is currently in the middle of a lane change, or whether its planned lane change has been completed
1516 bool is_currently_lane_changing = false;
1518 if((upcoming_lc_params_.start_dist < req->veh_downtrack) && (req->veh_downtrack < upcoming_lc_params_.end_dist)){
1519 is_currently_lane_changing = true;
1520 }
1521 else if(req->veh_downtrack > upcoming_lc_params_.end_dist){
1522 // Update flag since the planned lane change has been completed
1524 }
1525 }
1526
1527 boost::optional<lanelet::ConstLanelet> ego_current_lanelet_optional = getLaneletOnEgoRouteFromMapPosition(req->veh_x, req->veh_y);
1528
1529 if(ego_current_lanelet_optional){
1530 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "starting lanelet for maneuver plan is " << ego_current_lanelet_optional.get().id());
1531 }
1532 else{
1533 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Given vehicle position is not within a lanelet on the route. Returning empty maneuver plan");
1534 return;
1535 }
1536
1537 // Get ego vehicle's current lane index
1538 // Note: For 'lane index', 0 is rightmost lane, 1 is second rightmost, etc.; Only the current travel direction is considered
1539 ego_lane_index_ = wm_->getMapRoutingGraph()->rights(ego_current_lanelet_optional.get()).size();
1540
1541 // Update state machine if there is currently an ERV being tracked and if ego vehicle is not currently in the middle of a lane change
1542 if(has_tracked_erv_ && !is_currently_lane_changing){
1544 // Trigger state machine transition for case in which the ego vehicle is in the ERV's path
1545 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Ego vehicle and ERV are both in lane index " << ego_lane_index_);
1546
1550 }
1553 }
1556
1559 }
1560 else{
1561 // ERV is close enough (in the same lane) that if the ego vehicle slows down it could cause a safety hazard; set internal members for ego vehicle to maintain its current speed
1564 non_reduced_speed_to_maintain_ = req->veh_logitudinal_velocity;
1565 }
1566 }
1567
1568 // Set flag to broadcast EmergencyVehicleResponse warning messages to ERV if they have not already been broadcasted
1572 }
1573 }
1574 else{
1575 RCLCPP_WARN_STREAM(rclcpp::get_logger(logger_name), "Unsupported seconds_until_passing of " << tracked_erv_.seconds_until_passing << ", returning empty maneuver plan");
1576 resp->new_plan.maneuvers = {};
1577 return;
1578 }
1579 }
1580 else{
1581 // Trigger state machine transition for case in which the ego vehicle is not in the ERV's path
1582 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "Ego vehicle is not in ERV's path");
1583
1587 }
1590 }
1591 else{
1592 RCLCPP_WARN_STREAM(rclcpp::get_logger(logger_name), "Unsupported seconds_until_passing of " << tracked_erv_.seconds_until_passing << ", returning empty maneuver plan");
1593 resp->new_plan.maneuvers = {};
1594 return;
1595 }
1596 }
1597 }
1598
1599 // Set initial maneuver plan parameters based on the ego vehicle's initial state
1600 double downtrack_progress = req->veh_downtrack;
1601 double current_lanelet_ending_downtrack = wm_->routeTrackPos(ego_current_lanelet_optional.get().centerline2d().back()).downtrack;
1602 double speed_progress = req->veh_logitudinal_velocity;
1603 double target_speed = getLaneletSpeedLimit(ego_current_lanelet_optional.get());
1604 rclcpp::Time time_progress = rclcpp::Time(req->header.stamp);
1605
1606 // Generate maneuver plan based on the current state
1607 switch (transition_table_.getState())
1608 {
1610 resp->new_plan.maneuvers = {};
1611 RCLCPP_DEBUG_STREAM(rclcpp::get_logger(logger_name), "No approaching ERV. Returning empty maneuver plan");
1612 is_maintaining_non_reduced_speed_ = false; // Reset flag since ego vehicle does not need to maintain a non-reduced speed
1613 break;
1614
1616 is_maintaining_non_reduced_speed_ = false; // Reset flag since ego vehicle does not need to maintain a non-reduced speed
1617
1618 generateMoveOverManeuverPlan(resp, ego_current_lanelet_optional.get(), downtrack_progress, current_lanelet_ending_downtrack, speed_progress,
1619 target_speed, time_progress, ego_lane_index_, tracked_erv_.lane_index);
1620 break;
1621
1623 // Generate a maneuver plan consisting of only lane-following maneuvers at a reduced speed since the ERV is actively passing the ego vehicle
1624 generateReducedSpeedLaneFollowingeManeuverPlan(resp, ego_current_lanelet_optional.get(), downtrack_progress, current_lanelet_ending_downtrack, speed_progress,
1625 target_speed, time_progress, is_maintaining_non_reduced_speed_);
1626 break;
1627
1628 default:
1629 throw std::invalid_argument("Transition table in unsupported state");
1630 }
1631
1632 latest_maneuver_plan_ = resp->new_plan;
1633 }
1634
1636 return true;
1637 }
1638
1640 return "v1.0";
1641 }
1642
1643} // approaching_emergency_vehicle_plugin
1644
1645#include "rclcpp_components/register_node_macro.hpp"
1646
1647// Register the component with class_loader
#define GET_MANEUVER_PROPERTY(mvr, property)
Macro definition to enable easier access to fields shared across the maneuver types.
Class that implements the Approaching Emergency Vehicle Plugin (ERV) strategic plugin....
boost::optional< lanelet::ConstLanelet > getRouteIntersectingLanelet(const lanelet::routing::Route &erv_future_route)
Helper function to obtain the earliest lanelet that exists on both an ERV's future route and the ego ...
rcl_interfaces::msg::SetParametersResult parameter_update_callback(const std::vector< rclcpp::Parameter > &parameters)
Callback for dynamic parameter updates.
void publishApproachingErvStatus()
This is a callback function for the approaching_emergency_vehicle_status_timer_. It makes a call to g...
rclcpp::Duration getManeuverDuration(const carma_planning_msgs::msg::Maneuver &maneuver, double epsilon) const
Helper function to obtain the (seconds) of a provided maneuver.
carma_ros2_utils::SubPtr< carma_v2x_msgs::msg::EmergencyVehicleAck > incoming_emergency_vehicle_ack_sub_
carma_ros2_utils::CallbackReturn on_activate_plugin()
This method is used to create a timer and will be called on the activate transition.
void generateMoveOverManeuverPlan(carma_planning_msgs::srv::PlanManeuvers::Response::SharedPtr resp, lanelet::ConstLanelet current_lanelet, double downtrack_progress, double current_lanelet_ending_downtrack, double speed_progress, double target_speed, rclcpp::Time time_progress, int ego_lane_index, int erv_lane_index)
Function to generate a maneuver plan when the ego vehicle must change lanes due to being in the MOVIN...
lanelet::Optional< lanelet::routing::Route > generateErvRoute(double current_latitude, double current_longitude, std::vector< carma_v2x_msgs::msg::Position3D > erv_destination_points)
Helper function to generate an ERV's route based on its current position and its future route destina...
void broadcastWarningToErv()
This is a callback function for the warning_broadcast_timer_, and is called to broadcast an Emergency...
carma_planning_msgs::msg::Maneuver composeLaneFollowingManeuverMessage(double start_dist, double end_dist, double start_speed, double target_speed, int lanelet_id, rclcpp::Time &start_time) const
Function to compose a lane following maneuver message based on the provided maneuver parameters.
void checkForErvTimeout()
This is a callback function for the erv_timeout_timer_ timer, and is called to determine whether a ti...
boost::optional< lanelet::ConstLanelet > getLaneletOnEgoRouteFromMapPosition(const double &x_position, const double &y_position)
Helper function to convert a map x,y coordinate pair to a lanelet on the ego vehicle's route.
void addStopAndWaitToEndOfPlan(carma_planning_msgs::srv::PlanManeuvers::Response::SharedPtr resp, double downtrack_progress, double stop_maneuver_beginning_downtrack, double end_of_route_downtrack, double stopping_entry_speed, double stopping_deceleration, double current_lanelet_ending_downtrack, lanelet::ConstLanelet current_lanelet, rclcpp::Time time_progress)
Function to add a stop and wait maneuver to the end of the maneuver plan contained in the provided 'r...
ApproachingEmergencyVehiclePlugin(const rclcpp::NodeOptions &)
ApproachingEmergencyVehiclePlugin constructor.
bool get_availability() override
Get the availability status of this plugin based on the current operating environment....
carma_ros2_utils::CallbackReturn on_configure_plugin()
This method is used to load parameters and will be called on the configure state transition.
carma_ros2_utils::PubPtr< carma_v2x_msgs::msg::EmergencyVehicleResponse > outgoing_emergency_vehicle_response_pub_
void routeStateCallback(carma_planning_msgs::msg::RouteState::UniquePtr msg)
Route State subscription callback, with is used to update this plugin's latest_route_state_ object.
void guidanceStateCallback(const carma_planning_msgs::msg::GuidanceState::UniquePtr msg)
Subscription callback to process the latest guidance state and update the is_guidance_engaged_ flag a...
void incomingEmergencyVehicleAckCallback(const carma_v2x_msgs::msg::EmergencyVehicleAck::UniquePtr msg)
Subscription callback for incoming EmergencyVehicleAck messages. If the message is from the currently...
carma_planning_msgs::msg::Maneuver composeStopAndWaitManeuverMessage(double start_dist, double end_dist, double start_speed, lanelet::Id starting_lane_id, lanelet::Id ending_lane_id, double stopping_deceleration, rclcpp::Time &start_time) const
Function to compose a stop and wait maneuver message based on the provided maneuver parameters.
boost::optional< double > getSecondsUntilPassing(lanelet::Optional< lanelet::routing::Route > &erv_future_route, const lanelet::BasicPoint2d &erv_position_in_map, const double &erv_current_speed, lanelet::ConstLanelet &intersecting_lanelet)
Helper function to calculate the estimated seconds until an ERV will pass the ego vehicle....
void generateReducedSpeedLaneFollowingeManeuverPlan(carma_planning_msgs::srv::PlanManeuvers::Response::SharedPtr resp, lanelet::ConstLanelet current_lanelet, double downtrack_progress, double current_lanelet_ending_downtrack, double speed_progress, double target_speed, rclcpp::Time time_progress, bool is_maintaining_non_reduced_speed)
Function to generate a maneuver plan when the ego vehicle must remain in its lane due to being in the...
void publishHazardLightStatus()
This is a callback function for publishing turn ON/OFF (true/false) hazard light command to the ssc d...
carma_planning_msgs::msg::Maneuver composeLaneChangeManeuverMessage(double start_dist, double end_dist, double start_speed, double target_speed, lanelet::Id starting_lane_id, lanelet::Id ending_lane_id, rclcpp::Time &start_time) const
Function to compose a lane change maneuver message based on the provided maneuver parameters.
boost::optional< lanelet::BasicPoint2d > getErvPositionInMap(const double &current_latitude, const double &current_longitude)
Helper function to obtain an ERV's position in the map frame from its current latitude and longitude.
void incomingBsmCallback(carma_v2x_msgs::msg::BSM::UniquePtr msg)
Incoming BSM subscription callback, which determines whether a BSM should be processed,...
void plan_maneuvers_callback(std::shared_ptr< rmw_request_id_t >, carma_planning_msgs::srv::PlanManeuvers::Request::SharedPtr req, carma_planning_msgs::srv::PlanManeuvers::Response::SharedPtr resp) override
Extending class provided callback which should return a planned trajectory based on the provided traj...
carma_ros2_utils::SubPtr< carma_planning_msgs::msg::RouteState > route_state_sub_
double getLaneletSpeedLimit(const lanelet::ConstLanelet &lanelet)
Helper function to extract the speed limit (m/s) from a provided lanelet.
std::vector< lanelet::BasicPoint2d > filter_points_ahead(const lanelet::BasicPoint2d &reference_point, const std::vector< lanelet::BasicPoint2d > &original_points) const
Helper function that return points ahead of the given reference point accounting for the list of poin...
boost::optional< ErvInformation > getErvInformationFromBsm(carma_v2x_msgs::msg::BSM::UniquePtr msg)
Through internal logic and calls to separate helper functions, this function processes a received BSM...
void routeCallback(carma_planning_msgs::msg::Route::UniquePtr msg)
Route subscription callback, with is used to update this plugin's future_route_lanelet_ids_ object.
void twistCallback(geometry_msgs::msg::TwistStamped::UniquePtr msg)
Subscription callback for the twist subscriber, which will store latest current velocity of the ego v...
carma_ros2_utils::SubPtr< carma_planning_msgs::msg::GuidanceState > guidance_state_sub_
void georeferenceCallback(const std_msgs::msg::String::UniquePtr msg)
Subscription callback for the georeference.
carma_msgs::msg::UIInstructions generateApproachingErvStatusMessage()
Function to generate a carma_msgs::msg::UIInstructions message that describes whether there is curren...
void event(ApproachingEmergencyVehicleEvent event)
Trigger event for the transition table.
virtual carma_wm::WorldModelConstPtr get_world_model() final
Method to return the default world model provided as a convience by this base class If this method or...
Class which implements the WorldModel interface. In addition this class provides write access to the ...
double getManeuverEndSpeed(const carma_planning_msgs::msg::Maneuver &mvr)
Anonymous function to extract maneuver end speed which cannot be obtained with GET_MANEUVER_PROPERY c...
auto to_string(const UtmZone &zone) -> std::string
Definition: utm_zone.cpp:21
std::shared_ptr< lanelet::routing::Route > LaneletRoutePtr
Definition: WorldModel.hpp:45
Stuct containing the algorithm configuration values for approaching_emergency_vehicle_plugin.
Convenience struct for storing relevant data for an Emergency Response Vehicle (ERV).
Convenience struct for storing the parameters of an upcoming lane change to ensure that the same para...