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.
platoon_strategic_ihp.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2019-2021 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 */
16
17/*
18 * Developed by the UCLA Mobility Lab, 10/20/2021.
19 *
20 * Creator: Xu Han
21 * Author: Xu Han, Xin Xia, Jiaqi Ma
22 */
23
24
25#include <rclcpp/logging.hpp>
26#include <string>
28#include <array>
29#include <stdlib.h>
30
31
33{
34
35 // -------------- constructor --------------//
37 MobilityRequestCB mobility_request_publisher, MobilityOperationCB mobility_operation_publisher,
38 PlatooningInfoCB platooning_info_publisher,
39 std::shared_ptr<carma_ros2_utils::timers::TimerFactory> timer_factory)
40 : mobility_request_publisher_(mobility_request_publisher),
41 mobility_response_publisher_(mobility_response_publisher), mobility_operation_publisher_(mobility_operation_publisher),
42 platooning_info_publisher_(platooning_info_publisher), wm_(wm), config_(config), timer_factory_(timer_factory), pm_(timer_factory_)
43 {
44 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Top of PlatoonStrategicIHP ctor.");
45 std::string hostStaticId = config_.vehicleID; //static ID for this vehicle
46 pm_.HostMobilityId = hostStaticId;
47 // construct platoon member for host vehicle as the first element in the vector, since it starts life as a solo vehicle
48 long cur_t = timer_factory_->now().nanoseconds()/1000000; // time in millisecond
49 PlatoonMember hostVehicleMember = PlatoonMember(hostStaticId, 0.0, 0.0, 0.0, 0.0, cur_t);
50 pm_.host_platoon_.push_back(hostVehicleMember);
51 plugin_discovery_msg_.name = "platoon_strategic_ihp";
52 plugin_discovery_msg_.version_id = "v1.0";
53 plugin_discovery_msg_.available = true;
54 plugin_discovery_msg_.activated = true;
55 plugin_discovery_msg_.type = carma_planning_msgs::msg::Plugin::STRATEGIC;
56 plugin_discovery_msg_.capability = "strategic_plan/plan_maneuvers";
57 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "ctor complete. hostStaticId = " << hostStaticId);
58 }
59
60
61 //-------------------------------- Extract Data --------------------------------------//
62
63 // Find ecef point based on pose message
64 carma_v2x_msgs::msg::LocationECEF PlatoonStrategicIHPPlugin::pose_to_ecef(geometry_msgs::msg::PoseStamped pose_msg)
65 {
66
67 if (!map_projector_)
68 {
69 throw std::invalid_argument("No map projector available for ecef conversion");
70 }
71
72 carma_v2x_msgs::msg::LocationECEF location;
73
74 // note: ecef point read from map projector is in m.
75 lanelet::BasicPoint3d ecef_point = map_projector_->projectECEF({pose_msg.pose.position.x, pose_msg.pose.position.y, 0.0}, 1);
76 location.ecef_x = ecef_point.x() * 100.0;
77 location.ecef_y = ecef_point.y() * 100.0;
78 location.ecef_z = ecef_point.z() * 100.0;
79
80
81 // RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "location.ecef_x: " << location.ecef_x);
82 // RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "location.ecef_y: " << location.ecef_y);
83 // RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "location.ecef_z: " << location.ecef_z);
84
85 // note: the returned ecef is in cm.
86 return location;
87 }
88
89 // Function to assign host pose_ecef_point_
90 void PlatoonStrategicIHPPlugin::setHostECEF(carma_v2x_msgs::msg::LocationECEF pose_ecef_point)
91 {
92 // Note, the ecef here is in cm.
93 pose_ecef_point_ = pose_ecef_point;
94 }
95
96 // Function to get pm_ object
98 {
99 return pm_;
100 }
101
102 // Function to set platoon manager state
104 {
105 pm_.current_platoon_state = desiredState;
106 }
107
108 // Update platoon list (Unit Test function)
109 void PlatoonStrategicIHPPlugin::updatePlatoonList(std::vector<PlatoonMember> platoon_list)
110 {
111 pm_.host_platoon_ = platoon_list;
112 }
113
114 // Callback to calculate downtrack based on pose message.
115 void PlatoonStrategicIHPPlugin::pose_cb(const geometry_msgs::msg::PoseStamped::UniquePtr msg)
116 {
117 pose_msg_ = geometry_msgs::msg::PoseStamped(*msg);
118
120 {
121 lanelet::BasicPoint2d current_loc(pose_msg_.pose.position.x, pose_msg_.pose.position.y);
122 carma_wm::TrackPos tc = wm_->routeTrackPos(current_loc);
123
124 // update host's DtD and CtD
127 // RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "current_downtrack_ = " << current_downtrack_ << ", current_crosstrack_ = " << current_crosstrack_);
129
130 // note: the ecef read from "pose_ecef_point" is in cm.
131 carma_v2x_msgs::msg::LocationECEF pose_ecef_point = pose_to_ecef(pose_msg_);
132 setHostECEF(pose_ecef_point);
133 }
134 }
135
136 // callback kto update the command speed on x direction, in m/s.
137 void PlatoonStrategicIHPPlugin::cmd_cb(const geometry_msgs::msg::TwistStamped::UniquePtr msg)
138 {
139 cmd_speed_ = msg->twist.linear.x;
140 }
141
142 // twist command, linear speed on x direction, in m/s.
143 void PlatoonStrategicIHPPlugin::twist_cb(const geometry_msgs::msg::TwistStamped::UniquePtr msg)
144 {
145 current_speed_ = msg->twist.linear.x;
147 {
148 current_speed_ = 0.0;
149 }
150 }
151
152 // Return the lanelet id.
153 int PlatoonStrategicIHPPlugin::findLaneletIndexFromPath(int target_id, lanelet::routing::LaneletPath& path)
154 {
155 for(size_t i = 0; i < path.size(); ++i)
156 {
157 if(path[i].id() == target_id)
158 {
159 return i;
160 }
161 }
162 return -1;
163 }
164
165 // Find the speed limit for the current road (also used as the desired speed).
166 double PlatoonStrategicIHPPlugin::findSpeedLimit(const lanelet::ConstLanelet& llt)
167 {
168 double target_speed = 0.0;
169
170 lanelet::Optional<carma_wm::TrafficRulesConstPtr> traffic_rules = wm_->getTrafficRules();
171 if (traffic_rules)
172 {
173 target_speed =(*traffic_rules)->speedLimit(llt).speedLimit.value();
174 }
175 else
176 {
177 throw std::invalid_argument("Valid traffic rules object could not be built");
178 }
179
180 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "target speed (limit) " << target_speed);
181
182 return target_speed;
183 }
184
185 // Find the lane width based on current location
187 {
188 lanelet::BasicPoint2d current_loc(pose_msg_.pose.position.x, pose_msg_.pose.position.y);
189
190 auto current_lanelets = lanelet::geometry::findNearest(wm_->getMap()->laneletLayer, current_loc, 10);
191 lanelet::ConstLanelet current_lanelet = current_lanelets[0].second;
192
193 // find left and right bound
194 lanelet::ConstLineString3d left_bound = current_lanelet.leftBound();
195 lanelet::ConstLineString3d right_bound = current_lanelet.leftBound();
196
197 // find lane width
198 double dx = abs (left_bound[0].x() - right_bound[0].x());
199 double dy = abs (left_bound[0].y() - right_bound[0].y());
200 double laneWidth = sqrt(dx*dx + dy*dy);
201 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "calculated lane width: " << laneWidth);
202
203 // TODO temporary disable this function and return constant value
204 laneWidth = 3.5;
205
206
207 return laneWidth;
208 }
209
210 // Check if target platoon is in front of the host vehicle, and within the same lane (downtrack is the DTD of the target vehicle).
211 bool PlatoonStrategicIHPPlugin::isVehicleRightInFront(double downtrack, double crosstrack)
212 {
213 // Position info of the host vehcile
214 double currentDtd = current_downtrack_;
215 double currentCtd = current_crosstrack_;
216 bool samelane = abs(currentCtd-crosstrack) <= config_.maxCrosstrackError;
217
218 if (downtrack > currentDtd && samelane)
219 {
220 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Found a platoon in front. We are able to join");
221 return true;
222 }
223 else
224 {
225 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Ignoring platoon that is either behind host or in another lane.");
226 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The front platoon dtd is " << downtrack << " and we are current at " << currentDtd);
227 return false;
228 }
229 }
230
231 // UCLA: check if target platoon at back, and within the same lane. Used for same-lane frontal join.
232 bool PlatoonStrategicIHPPlugin::isVehicleRightBehind(double downtrack, double crosstrack)
233 {
234 double currentDtd = current_downtrack_;
235 double currentCtd = current_crosstrack_;
236 bool samelane = abs(currentCtd-crosstrack) <= config_.maxCrosstrackError;
237
238 if (downtrack < currentDtd && samelane)
239 {
240 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Found a platoon at behind. We are able to join");
241 return true;
242 }
243 else
244 {
245 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Ignoring platoon that is either ahead of us or in another lane.");
246 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The front platoon dtd is " << downtrack << " and we are current at " << currentDtd);
247 return false;
248 }
249 }
250
251 // Return the ecef point projected to local map point.
252 lanelet::BasicPoint2d PlatoonStrategicIHPPlugin::ecef_to_map_point(carma_v2x_msgs::msg::LocationECEF ecef_point)
253 {
254 if (!map_projector_)
255 {
256 throw std::invalid_argument("No map projector available for ecef conversion");
257 }
258
259 lanelet::BasicPoint3d map_point = map_projector_->projectECEF( { (double)ecef_point.ecef_x/100.0, (double)ecef_point.ecef_y/100.0, (double)ecef_point.ecef_z/100.0 } , -1);
260
261 lanelet::BasicPoint2d output {map_point.x(), map_point.y()};
262 return output;
263 }
264
265 // Build map projector from proj string (georefernce).
266 void PlatoonStrategicIHPPlugin::georeference_cb(const std_msgs::msg::String::UniquePtr msg)
267 {
268 if (georeference_ != msg->data)
269 {
270 georeference_ = msg->data;
271 map_projector_ = std::make_shared<lanelet::projection::LocalFrameProjector>(msg->data.c_str());
272 }
273 }
274
275 //-------------------------------- Mobility Communication --------------------------------------//
276
277 // ------ 1. compose Mobility Operation messages and platoon info ------ //
278
279 // UCLA: Return a Mobility operation message with STATUS params.
280 carma_v2x_msgs::msg::MobilityOperation PlatoonStrategicIHPPlugin::composeMobilityOperationSTATUS()
281 {
288 //TODO future: consider setting recipient_id to the platoon ID to make it obvious that anyone in that group is intended reader.
289 // This requires an architectural agreement on use of group messaging protocol.
290
291 // Extract data
292 carma_v2x_msgs::msg::MobilityOperation msg;
293 msg.m_header.plan_id = pm_.currentPlatoonID;
294 msg.m_header.recipient_id = "";
295 std::string hostStaticId = config_.vehicleID;
296 msg.m_header.sender_id = hostStaticId;
297 msg.m_header.timestamp = timer_factory_->now().nanoseconds() / 1000000;
298 msg.strategy = PLATOONING_STRATEGY;
299
300 // form message
301 double cmdSpeed = cmd_speed_;
302 boost::format fmter(OPERATION_STATUS_PARAMS);
303 fmter% cmdSpeed; // index = 0, in m/s.
304 fmter% current_speed_; // index = 1, in m/s.
305 fmter% pose_ecef_point_.ecef_x; // index = 2, in cm.
306 fmter% pose_ecef_point_.ecef_y; // index = 3, in cm.
307 fmter% pose_ecef_point_.ecef_z; // index = 4, in cm.
308
309 // compose message
310 std::string statusParams = fmter.str();
311 msg.strategy_params = statusParams;
312 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Composed a mobility operation message with params " << msg.strategy_params);
313 return msg;
314 }
315
316 // UCLA: Return a Mobility operation message with INFO params.
317 carma_v2x_msgs::msg::MobilityOperation PlatoonStrategicIHPPlugin::composeMobilityOperationINFO()
318 {
324 carma_v2x_msgs::msg::MobilityOperation msg;
325 msg.m_header.plan_id = pm_.currentPlatoonID; // msg.m_header.plan_id is the platoon ID of the request sender (rear join and frontal join).
326 msg.m_header.recipient_id = "";
327 std::string hostStaticId = config_.vehicleID;
328 msg.m_header.sender_id = hostStaticId;
329 msg.m_header.timestamp = timer_factory_->now().nanoseconds() / 1000000;;
330 msg.strategy = PLATOONING_STRATEGY;
331
332 double CurrentPlatoonLength = pm_.getCurrentPlatoonLength();
333 int PlatoonSize = pm_.getHostPlatoonSize();
334
335 boost::format fmter(OPERATION_INFO_PARAMS);
336 // Note: need to update "OPERATION_INFO_PARAMS" in m_header file --> strategic_platoon_ihp.h
337 fmter% CurrentPlatoonLength; // index = 0, physical length of the platoon, in m.
338 fmter% current_speed_; // index = 1, in m/s.
339 fmter% PlatoonSize; // index = 2, number of members
340 fmter% pose_ecef_point_.ecef_x; // index = 3, in cm.
341 fmter% pose_ecef_point_.ecef_y; // index = 4, in cm.
342 fmter% pose_ecef_point_.ecef_z; // index = 5, in cm.
343
344 std::string infoParams = fmter.str();
345 msg.strategy_params = infoParams;
346 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Composed a mobility operation message with params " << msg.strategy_params);
347 return msg;
348 }
349
350 // ----------------------------- UCLA: helper functions for cut-in from front -------------------------------//
351
352 // Note: The function "find_target_lanelet_id" was used to test the IHP platooning logic and is only a pre-written scenario.
353 // The IHP platooning should provide necessary data in a maneuver plan for the arbitrary lane change module.
354
355 // This is the platoon leader's function to determine if the joining vehicle is closeby
356 bool PlatoonStrategicIHPPlugin::isJoiningVehicleNearPlatoon(double joining_downtrack, double joining_crosstrack)
357 {
358 // Position info of the host vehicle
359 double frontVehicleDtd = current_downtrack_;
360 double frontVehicleCtd = current_crosstrack_;
361 // platoon rear positions
362 int lastVehicleIndex = pm_.host_platoon_.size()-1;
363 double rearVehicleDtd = pm_.host_platoon_[lastVehicleIndex].vehiclePosition;
364
365 // lateral error for two lanes (lane width was calculated based on current lanelet)
366 double two_lane_cross_error = 2*config_.maxCrosstrackError + findLaneWidth();
367 // add longitudinal check threshold in config file
368 bool longitudinalCheck = joining_downtrack >= rearVehicleDtd - config_.longitudinalCheckThresold ||
369 joining_downtrack <= frontVehicleDtd + config_.maxCutinGap;
370 bool lateralCheck = joining_crosstrack >= frontVehicleCtd - two_lane_cross_error ||
371 joining_crosstrack <= frontVehicleCtd + two_lane_cross_error;
372 // logs for longitudinal and lateral check
373 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The longitudinalCheck result is: " << longitudinalCheck );
374 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The lateralCheck result is: " << lateralCheck );
375
376 if (longitudinalCheck && lateralCheck)
377 {
378 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Joining vehicle is nearby. It is able to join.");
379 return true;
380 }
381 else
382 {
383 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The joining vehicle is not close by, the join request will not be approved.");
384 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The joining vehicle downtrack is " << joining_downtrack << " and the host (platoon leader) downtrack is " << frontVehicleDtd);
385 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The joining vehicle crosstrack is " << joining_crosstrack << " and the host (platoon leader) crosstrack is " << frontVehicleCtd);
386 return false;
387 }
388 }
389
390 // Check if the host vehicle is close to the platoon for cut-in join.
391 bool PlatoonStrategicIHPPlugin::isVehicleNearTargetPlatoon(double rearVehicleDtd, double frontVehicleDtd, double frontVehicleCtd)
392 {
393
394 // lateral error for two lanes (lane width was calculated based on current lanelet)
395 double two_lane_cross_error = 2*config_.maxCrosstrackError + findLaneWidth();
396 // add longitudinal check threshold in config file
397 bool longitudinalCheck = current_downtrack_ >= rearVehicleDtd - config_.longitudinalCheckThresold ||
399 bool lateralCheck = abs(current_crosstrack_ - frontVehicleCtd) <= two_lane_cross_error;
400 // current_crosstrack_ >= frontVehicleCtd - two_lane_cross_error ||
401 // current_crosstrack_ <= frontVehicleCtd + two_lane_cross_error;
402 // logs for longitudinal and lateral check
403 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The longitudinalCheck result is: " << longitudinalCheck );
404 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The lateralCheck result is: " << lateralCheck );
405
406 if (longitudinalCheck && lateralCheck)
407 {
408 // host vehicle is close to target platoon longitudinally (within 10m) and laterally (within 5m)
409 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Found a platoon nearby. We are able to join.");
410 return true;
411 }
412 else
413 {
414 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Ignoring platoon.");
415 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The platoon leader dtd is " << frontVehicleDtd << " and we are current at " << current_downtrack_);
416 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The platoon leader ctd is " << frontVehicleCtd << " and we are current at " << current_crosstrack_);
417 return false;
418 }
419 }
420
421 // Compose platoon info msg for all states.
422 carma_planning_msgs::msg::PlatooningInfo PlatoonStrategicIHPPlugin::composePlatoonInfoMsg()
423 {
431 carma_planning_msgs::msg::PlatooningInfo status_msg;
432
434 {
435 status_msg.state = carma_planning_msgs::msg::PlatooningInfo::DISABLED;
436 }
438 {
439 status_msg.state = pm_.getHostPlatoonSize() == 1 ? carma_planning_msgs::msg::PlatooningInfo::SEARCHING : carma_planning_msgs::msg::PlatooningInfo::LEADING;
440 }
442 {
443 status_msg.state = carma_planning_msgs::msg::PlatooningInfo::CONNECTING_TO_NEW_FOLLOWER;
444 }
446 {
447 status_msg.state = carma_planning_msgs::msg::PlatooningInfo::CONNECTING_TO_NEW_LEADER;
448 }
450 {
451 status_msg.state = carma_planning_msgs::msg::PlatooningInfo::FOLLOWING;
452 }
453 // UCLA: add leader aborting for frontal join (inherited from candidate follower).
455 {
456 status_msg.state = carma_planning_msgs::msg::PlatooningInfo::CONNECTING_TO_NEW_LEADER;
457 }
458 // UCLA: add candidate leader for frontal join (inherited from leader waiting).
460 {
461 status_msg.state = carma_planning_msgs::msg::PlatooningInfo::CONNECTING_TO_NEW_FOLLOWER;
462 }
463 // UCLA: add "lead with operation" for frontal join (inherited from leader waiting).
465 {
466 status_msg.state = carma_planning_msgs::msg::PlatooningInfo::CONNECTING_TO_NEW_FOLLOWER;
467 }
468 // UCLA: add "prepare to join" for frontal join (inherited from leader waiting).
470 {
471 status_msg.state = carma_planning_msgs::msg::PlatooningInfo::CONNECTING_TO_NEW_LEADER;
472 }
473 //TODO: Place holder for departure (PREPARE TO DEPART)
474
475 // compose msgs for anything other than standby
477 {
478 status_msg.platoon_id = pm_.currentPlatoonID;
479 status_msg.size = pm_.getHostPlatoonSize();
480 status_msg.size_limit = config_.maxPlatoonSize;
481
483 {
484 PlatoonMember platoon_leader = pm_.getDynamicLeader();
485 status_msg.leader_id = platoon_leader.staticId;
486 status_msg.leader_downtrack_distance = platoon_leader.vehiclePosition;
487 status_msg.leader_cmd_speed = platoon_leader.commandSpeed;
488 status_msg.host_platoon_position = pm_.getNumberOfVehicleInFront();
489 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "pm platoonsize: " << pm_.getHostPlatoonSize() << ", platoon_leader " << platoon_leader.staticId);
490
491 int numOfVehiclesGaps = pm_.getNumberOfVehicleInFront() - pm_.dynamic_leader_index_;
492 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The host vehicle have " << numOfVehiclesGaps << " vehicles between itself and its leader (includes the leader)");
493
494 // use current position to find lanelet ID
495 lanelet::BasicPoint2d current_loc(pose_msg_.pose.position.x, pose_msg_.pose.position.y);
496
497 auto llts = wm_->getLaneletsFromPoint(current_loc, 1);
498
499 double lanelet_digitalgap = config_.standStillHeadway;
500
501 if (!llts.empty())
502 {
503 auto geofence_gaps = llts[0].regulatoryElementsAs<lanelet::DigitalMinimumGap>();
504
505 if (!geofence_gaps.empty())
506 {
507 lanelet_digitalgap = geofence_gaps[0]->getMinimumGap();
508 }
509 }
510
511 else
512 {
513 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "No lanelets in this location!!!: ");
514 }
515
516 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "lanelet_digitalgap: " << lanelet_digitalgap);
517 double desired_headway = std::max(current_speed_ * config_.timeHeadway, lanelet_digitalgap);
518 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "speed based gap: " << current_speed_ * config_.timeHeadway);
519 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "max desired_headway " << desired_headway);
520 // TODO: currently the average length of the vehicle is obtained from a config parameter. In future, plugin needs to be updated to receive each vehicle's actual length through status or BSM messages for more accuracy.
521 status_msg.desired_gap = std::max(config_.standStillHeadway * numOfVehiclesGaps, desired_headway * numOfVehiclesGaps) + (numOfVehiclesGaps - 1) * 5.0;//config_.averageVehicleLength;
522 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The desired gap with the leader is " << status_msg.desired_gap);
523
524
525 // TODO: To uncomment the following lines, platooninfo msg must be updated
526 // UCLA: Add the value of the summation of "veh_len/veh_speed" for all predecessors
527 status_msg.current_predecessor_time_headway_sum = pm_.getPredecessorTimeHeadwaySum();
528 // UCLA: preceding vehicle info
529 status_msg.predecessor_speed = pm_.getPredecessorSpeed();
530 status_msg.predecessor_position = pm_.getPredecessorPosition();
531
532 // Note: use isCreateGap to adjust the desired gap send to control plugin
533 double regular_gap = status_msg.desired_gap;
534 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "regular_gap: " << regular_gap);
535 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "current_speed_: " << current_speed_);
536 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "speed based gap: " << desired_headway);
537 if (pm_.isCreateGap){
538 // enlarged desired gap for gap creation
539 status_msg.desired_gap = regular_gap*(1 + config_.createGapAdjuster);
540 }
541 status_msg.actual_gap = platoon_leader.vehiclePosition - current_downtrack_;
542 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "status_msg.actual_gap: " << status_msg.actual_gap);
543 }
544 else
545 {
546 status_msg.leader_id = config_.vehicleID;
547 status_msg.leader_downtrack_distance = current_downtrack_;
548 status_msg.leader_cmd_speed = cmd_speed_;
549 status_msg.host_platoon_position = 0;
550
551 }
552
553 // This info is updated at platoon control plugin
554 status_msg.host_cmd_speed = cmd_speed_;
555
556 }
557 return status_msg;
558 }
559
560 // Compose the Mobility Operation message for leader state. Message parameter types: STATUS and INFO.
561 carma_v2x_msgs::msg::MobilityOperation PlatoonStrategicIHPPlugin::composeMobilityOperationLeader(const std::string& type)
562 {
563 carma_v2x_msgs::msg::MobilityOperation msg;
564
565 // info params
566 if (type == OPERATION_INFO_TYPE)
567 {
569 }
570
571 // status params
572 else if (type == OPERATION_STATUS_TYPE)
573 {
575 }
576 // Unknown strategy param.
577 else
578 {
579 RCLCPP_ERROR(rclcpp::get_logger("platoon_strategic_ihp"),"UNKNOWN strategy param string!!!");
580 msg.strategy_params = "";
581 }
582 return msg;
583 }
584
585 // Compose the Mobility Operation message for Follower state. Message parameter types: STATUS.
586 carma_v2x_msgs::msg::MobilityOperation PlatoonStrategicIHPPlugin::composeMobilityOperationFollower()
587 {
588 carma_v2x_msgs::msg::MobilityOperation msg;
590 return msg;
591 }
592
593 // Compose the Mobility Operation message for LeaderWaiting state. Message parameter types: STATUS.
595 {
596 //TODO: shouldn't a leaderwaiting also be sending INFO messages since it is still leading?
597
598 carma_v2x_msgs::msg::MobilityOperation msg;
600 return msg;
601 }
602
603 // Compose the Mobility Operation message for CandidateFollower state. Message parameter types: STATUS.
605 {
606 carma_v2x_msgs::msg::MobilityOperation msg;
608 return msg;
609 }
610
611 // UCLA: add compose msgs for LeaderAborting (inherited from candidate follower). Message parameter types: STATUS.
613 {
614 /*
615 UCLA Implementation note:
616 Sending STATUS info for member updates and platoon trajectory regulation.
617 */
618 carma_v2x_msgs::msg::MobilityOperation msg;
620 return msg;
621 }
622
623 // UCLA: add compose msgs for CandidateLeader (inherited from leader waiting). Message parameter types: STATUS.
625 {
626 /*
627 UCLA Implementation note:
628 This is the joiner which will later become the new leader,
629 host vehicle publish status msgs and waiting to lead the rear platoon
630 */
631 carma_v2x_msgs::msg::MobilityOperation msg;
633 return msg;
634 }
635
636 // UCLA: compose mobility message for prepare to join (cut-in join state, inherited from follower state's compose mob_op)
638 {
639 /*
640 UCLA Implementation note:
641 This is the joiner that is preapring for cut-in join.
642 host vehicle publish status msgs and waiting to lead the rear platoon.
643 */
644 carma_v2x_msgs::msg::MobilityOperation msg;
646 return msg;
647 }
648 // UCLA: compose mobility message for leading with operation (cut-in join state, inherited from leader state's compose mob_op)
649 carma_v2x_msgs::msg::MobilityOperation PlatoonStrategicIHPPlugin::composeMobilityOperationLeadWithOperation(const std::string& type)
650 {
651 carma_v2x_msgs::msg::MobilityOperation msg;
652
653 // info params
654 if (type == OPERATION_INFO_TYPE)
655 {
657 }
658
659 // status params
660 else if (type == OPERATION_STATUS_TYPE)
661 {
663 }
664 // Unknown strategy param.
665 else
666 {
667 RCLCPP_ERROR(rclcpp::get_logger("platoon_strategic_ihp"),"UNKNOWN strategy param string!!!");
668 msg.strategy_params = "";
669 }
670 return msg;
671 }
672
673 // TODO: Place holder for compose mobility operation msg for prepare to depart.
674
675 // ------ 2. Mobility operation callback ------ //
676
677 // read ecef pose from STATUS
678 carma_v2x_msgs::msg::LocationECEF PlatoonStrategicIHPPlugin::mob_op_find_ecef_from_STATUS_params(std::string strategyParams)
679 {
680 /*
681 * Helper function that extract ecef location from STATUS msg.
682 * Note: STATUS params format:
683 * STATUS | --> "CMDSPEED:%1%,SPEED:%2%,ECEFX:%3%,ECEFY:%4%,ECEFZ:%5%"
684 * |----------0----------1---------2---------3---------4------|
685 */
686
687 std::vector<std::string> inputsParams;
688 boost::algorithm::split(inputsParams, strategyParams, boost::is_any_of(","));
689
690 std::vector<std::string> ecef_x_parsed;
691 boost::algorithm::split(ecef_x_parsed, inputsParams[2], boost::is_any_of(":"));
692 double ecef_x = std::stod(ecef_x_parsed[1]);
693
694 std::vector<std::string> ecef_y_parsed;
695 boost::algorithm::split(ecef_y_parsed, inputsParams[3], boost::is_any_of(":"));
696 double ecef_y = std::stod(ecef_y_parsed[1]);
697
698 std::vector<std::string> ecef_z_parsed;
699 boost::algorithm::split(ecef_z_parsed, inputsParams[4], boost::is_any_of(":"));
700 double ecef_z = std::stod(ecef_z_parsed[1]);
701
702 carma_v2x_msgs::msg::LocationECEF ecef_loc;
703 ecef_loc.ecef_x = ecef_x;
704 ecef_loc.ecef_y = ecef_y;
705 ecef_loc.ecef_z = ecef_z;
706
707 return ecef_loc;
708 }
709
710 // UCLA: Handle STATUS operation messages
711 void PlatoonStrategicIHPPlugin::mob_op_cb_STATUS(const carma_v2x_msgs::msg::MobilityOperation& msg)
712 {
719 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Entered mob_op_cb_STATUS");
720 std::string strategyParams = msg.strategy_params;
721 std::string vehicleID = msg.m_header.sender_id;
722 std::string platoonId = msg.m_header.plan_id;
723 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "strategyParams = " << strategyParams);
724 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "platoonId = " << platoonId << ", sender ID = " << vehicleID);
725 std::string statusParams = strategyParams.substr(OPERATION_STATUS_TYPE.size() + 1);
726 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "pm_.currentPlatoonID = " << pm_.currentPlatoonID << ", targetPlatoonID = " << pm_.targetPlatoonID);
727
728 // read Downtrack
729 carma_v2x_msgs::msg::LocationECEF ecef_loc = mob_op_find_ecef_from_STATUS_params(strategyParams);
730 lanelet::BasicPoint2d incoming_pose = ecef_to_map_point(ecef_loc);
731 double dtd = wm_->routeTrackPos(incoming_pose).downtrack;
732 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "DTD calculated from ecef is: " << dtd);
733 // read Crosstrack
734 double ctd = wm_->routeTrackPos(incoming_pose).crosstrack;
735 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "CTD calculated from ecef is: " << ctd);
736
737 // If it comes from a member of an identified neighbor platoon, then
738 if (platoonId.compare(pm_.neighborPlatoonID) == 0 && platoonId.compare(pm_.dummyID) != 0)
739 {
740 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Incoming platoonID matches target platoon id");
741 // // Update this member's status (or add if it's unknown to us)
742 pm_.neighborMemberUpdates(vehicleID, platoonId, statusParams, dtd, ctd);
743 }
744
745 // else if this message is for our platoon then store its info
746 else if (platoonId.compare(pm_.currentPlatoonID) == 0 && platoonId.compare(pm_.dummyID) != 0)
747 {
748 pm_.hostMemberUpdates(vehicleID, platoonId, statusParams, dtd, ctd);
749 }
750
751 // else it represents an uninteresting platoon
752 else
753 {
754 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Received mob op for platoon " << platoonId << " that doesn't match our platoon: " << pm_.currentPlatoonID
755 << " or known neighbor platoon: " << pm_.targetPlatoonID);
756 }
757 }
758
759 //
761 {
767 // For INFO params, the string format is INFO|REAR:%s,LENGTH:%.2f,SPEED:%.2f,SIZE:%d,DTD:%.2f
768 std::vector<std::string> inputsParams;
769 boost::algorithm::split(inputsParams, strategyParams, boost::is_any_of(","));
770
771 // Use the strategy params' length value and leader location to determine DTD of its rear
772 std::vector<std::string> target_platoon_length;
773 boost::algorithm::split(target_platoon_length, inputsParams[0], boost::is_any_of(":"));
774 double platoon_length = std::stod(target_platoon_length[1]);
775
776 return platoon_length;
777 }
778
779 // UCLA: Parse ecef location from INFO params
780 carma_v2x_msgs::msg::LocationECEF PlatoonStrategicIHPPlugin::mob_op_find_ecef_from_INFO_params(std::string strategyParams)
781 {
787 // For INFO params, the string format is INFO|REAR:%s,LENGTH:%.2f,SPEED:%.2f,SIZE:%d,DTD:%.2f
788 std::vector<std::string> inputsParams;
789 boost::algorithm::split(inputsParams, strategyParams, boost::is_any_of(","));
790
791 std::vector<std::string> ecef_x_parsed;
792 boost::algorithm::split(ecef_x_parsed, inputsParams[3], boost::is_any_of(":"));
793 double ecef_x = std::stod(ecef_x_parsed[1]);
794
795 std::vector<std::string> ecef_y_parsed;
796 boost::algorithm::split(ecef_y_parsed, inputsParams[4], boost::is_any_of(":"));
797 double ecef_y = std::stod(ecef_y_parsed[1]);
798
799 std::vector<std::string> ecef_z_parsed;
800 boost::algorithm::split(ecef_z_parsed, inputsParams[5], boost::is_any_of(":"));
801 double ecef_z = std::stod(ecef_z_parsed[1]);
802
803 carma_v2x_msgs::msg::LocationECEF ecef_loc;
804 ecef_loc.ecef_x = ecef_x;
805 ecef_loc.ecef_y = ecef_y;
806 ecef_loc.ecef_z = ecef_z;
807
808 return ecef_loc;
809 }
810
811 // handle message for each states.
812 void PlatoonStrategicIHPPlugin::mob_op_cb(const carma_v2x_msgs::msg::MobilityOperation::UniquePtr msg)
813 {
815 {
816 return;
817 }
818
819 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "mob_op_cb received msg with sender ID " << msg->m_header.sender_id
820 << ", plan ID " << msg->m_header.plan_id);
821 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "...strategy " << msg->strategy << ", strategy params " << msg->strategy_params);
822
823 // Check that this is a message about platooning (could be from some other Carma activity nearby)
824 std::string strategy = msg->strategy;
825 if (strategy.rfind(PLATOONING_STRATEGY, 0) != 0)
826 {
827 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Ignoring mobility operation message for " << strategy << " strategy.");
828 return;
829 }
830
831 // Ignore messages as long as host vehicle is stopped
833 {
834 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Ignoring message since host is stopped.");
835 return;
836 }
837
838 // Perform common operations that apply to all states
839 std::string strategyParams = msg->strategy_params;
840 bool isPlatoonStatusMsg = strategyParams.rfind(OPERATION_STATUS_TYPE, 0) == 0;
841 bool isPlatoonInfoMsg = strategyParams.rfind(OPERATION_INFO_TYPE, 0) == 0;
842 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "strategyParams: " << strategyParams << "isPlatoonStatusMsg: " << isPlatoonStatusMsg << "isPlatoonInfoMsg: " << isPlatoonInfoMsg);
843 if (isPlatoonStatusMsg)
844 {
845 mob_op_cb_STATUS(*msg);
846 }
847 else if (isPlatoonInfoMsg)
848 {
849 // Note: this is where confusion will reign if multiple neighbor platoons are discovered; need more sophisticated
850 // logic to handle that situation
851
852 // If it is a legitimate platoon (2 or more members) other than our own then
853 std::vector<std::string> inputsParams;
854 boost::algorithm::split(inputsParams, strategyParams, boost::is_any_of(","));
855 std::vector<std::string> p_size;
856 boost::algorithm::split(p_size, inputsParams[2], boost::is_any_of(":"));
857 int platoon_size = std::stoi(p_size[1]);
858 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "neighbor platoon_size from INFO: " << platoon_size);
859 if (platoon_size > 1 && msg->m_header.plan_id.compare(pm_.currentPlatoonID) != 0)
860 {
861 // If platoon ID doesn't match our known target platoon then clear any old neighbor platoon info and record
862 // the platoon ID and the sender as the leader (only leaders send INFO)
863 if (msg->m_header.plan_id.compare(pm_.neighborPlatoonID) != 0)
864 {
866 pm_.neighborPlatoonID = msg->m_header.plan_id;
867 }
868 pm_.neighbor_platoon_leader_id_ = msg->m_header.sender_id;
869 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "pm_.neighbor_platoon_leader_id_: " << pm_.neighbor_platoon_leader_id_);
870 pm_.neighbor_platoon_info_size_ = platoon_size;
871 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "pm_.neighbor_platoon_info_size_: " << pm_.neighbor_platoon_info_size_);
872 }
873 }
874
875 else
876 {
877 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Invalid Mob Op received");
878 }
879
880 // Perform state-specific additional actions
882 {
883 mob_op_cb_leader(*msg);
884 }
886 {
887 mob_op_cb_follower(*msg);
888 }
890 {
892 }
894 {
896 }
898 {
899 mob_op_cb_standby(*msg);
900 }
901 // UCLA: add leader aborting
903 {
905 }
906 // UCLA: add candidate leader
908 {
910 }
911 // UCLA: add lead with operation for cut-in join
913 {
915 }
916 // UCLA: add prepare to join for cut-in join
918 {
920 }
921 // TODO: Place holder for prepare to depart
922
923 // TODO: If needed (with large size platoons), add a queue for status messages
924 // INFO messages always processed, STATUS messages if saved in que
925 }
926
927 void PlatoonStrategicIHPPlugin::mob_op_cb_standby(const carma_v2x_msgs::msg::MobilityOperation& msg)
928 {
929 // In standby state, it will ignore operation message since it is not actively operating
930 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "STANDBY state no further action on message from " << msg.m_header.sender_id);
931 }
932
933 // Handle STATUS operation message
934 void PlatoonStrategicIHPPlugin::mob_op_cb_candidatefollower(const carma_v2x_msgs::msg::MobilityOperation& msg)
935 {
936 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "CANDIDATEFOLLOWER state no further action on message from " << msg.m_header.sender_id);
937 }
938
939 // Handle STATUS operation message
940 void PlatoonStrategicIHPPlugin::mob_op_cb_follower(const carma_v2x_msgs::msg::MobilityOperation& msg)
941 {
942 //std::string strategyParams = msg.strategy_params;
943 //bool isPlatoonStatusMsg = (strategyParams.rfind(OPERATION_STATUS_TYPE, 0) == 0);
944
945 // TODO: Place holder for prepare to depart
946
947 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "FOLLOWER state no further action on message from " << msg.m_header.sender_id);
948 }
949
950 // Handle STATUS operation message
951 void PlatoonStrategicIHPPlugin::mob_op_cb_leaderwaiting(const carma_v2x_msgs::msg::MobilityOperation& msg)
952 {
953 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "LEADERWAITING state no further action on message from " << msg.m_header.sender_id);
954 }
955
956 // UCLA: Handle both STATUS and INFO operation message. Front join and rear join are all handled if incoming operation message have INFO param.
957 void PlatoonStrategicIHPPlugin::mob_op_cb_leader(const carma_v2x_msgs::msg::MobilityOperation& msg)
958 {
974 // Read incoming message info
975 std::string strategyParams = msg.strategy_params;
976 std::string senderId = msg.m_header.sender_id;
977 std::string platoonId = msg.m_header.plan_id;
978
979 // In the current state, host vehicle care about the INFO heart-beat operation message if we are not currently in
980 // a negotiation, and host also need to care about operation from members in our current platoon.
981 bool isPlatoonInfoMsg = strategyParams.rfind(OPERATION_INFO_TYPE, 0) == 0; // INFO message only broadcast by leader and single CAV.
982 bool isInNegotiation = pm_.current_plan.valid || pm_.currentPlatoonID.compare(pm_.dummyID) != 0; // In negotiation indicates host is not available to become a joiner
983 // (i.e., not currently in a platoon or trying to join a platoon).
984 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Top of mob_op_cb_leader, isInNegotiation = " << isInNegotiation);
985
986 // Condition 1. Host vehicle is the single CAV joining the platoon.
987 if (isPlatoonInfoMsg && !isInNegotiation)
988 {
989 //TODO future enhancement: add logic here for if/how to join two existing platoons
990 // (e.g. the shorter platoon should join the longer one, or rear joins front if same length)
991
992 // step 1. read INFO message from the target platoon leader
993
994 // read ecef location from strategy params.
995 carma_v2x_msgs::msg::LocationECEF ecef_loc;
996 ecef_loc = mob_op_find_ecef_from_INFO_params(strategyParams);
997
998 // use ecef_loc to calculate front Dtd in m.
999 lanelet::BasicPoint2d incoming_pose = ecef_to_map_point(ecef_loc);
1000 double frontVehicleDtd = wm_->routeTrackPos(incoming_pose).downtrack;
1001
1002 // use ecef_loc to calculate front Ctd in m.
1003 double frontVehicleCtd = wm_->routeTrackPos(incoming_pose).crosstrack;
1004 // downtrack and crosstrack of the platoon leader --> used for frontal join
1005 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Neighbor platoon frontVehicleDtd from ecef: " << frontVehicleDtd << ", frontVehicleCtd from ecef: " << frontVehicleCtd);
1006
1007 // use INFO param to find platoon rear vehicle DTD and CTD.
1008 double platoon_length = mob_op_find_platoon_length_from_INFO_params(strategyParams); // length of the entire platoon in meters.
1019 double rearVehicleDtd = frontVehicleDtd - platoon_length;
1020 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "rear veh dtd from platoon length: " << rearVehicleDtd);
1021 if (!pm_.neighbor_platoon_.empty())
1022 {
1023 rearVehicleDtd = pm_.neighbor_platoon_.back().vehiclePosition;
1024 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "rear veh dtd from neighbor platoon: " << rearVehicleDtd);
1025 }
1026
1027 // Note: For one platoon, we assume all members are in the same lane.
1028 double rearVehicleCtd = frontVehicleCtd;
1029 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Neighbor platoon rearVehicleDtd: " << rearVehicleDtd << ", rearVehicleCtd: " << rearVehicleCtd);
1030
1031 // Parse the strategy params
1032 std::vector<std::string> inputsParams;
1033 boost::algorithm::split(inputsParams, strategyParams, boost::is_any_of(","));
1034
1035 // Get the target platoon's size (number of members) from strategy params
1036 std::vector<std::string> targetPlatoonSize_parsed;
1037 boost::algorithm::split(targetPlatoonSize_parsed, inputsParams[2], boost::is_any_of(":"));
1038 int targetPlatoonSize = std::stoi(targetPlatoonSize_parsed[1]);
1039 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "target Platoon Size: " << targetPlatoonSize);
1040 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Found a vehicle/platoon with id = " << platoonId << " within range.");
1041
1042 //TODO future: add logic here to assess closeness of the neighbor platoon, as well as its speed, destination
1043 // & other attributes to decide if we want to join before assembling a join request
1044
1045 // step 2. Generate default info for join request
1046 carma_v2x_msgs::msg::MobilityRequest request;
1047 request.m_header.plan_id = boost::uuids::to_string(boost::uuids::random_generator()());
1048 request.m_header.recipient_id = senderId;
1049 request.m_header.sender_id = config_.vehicleID;
1050 request.m_header.timestamp = timer_factory_->now().nanoseconds()/1000000;
1051 request.location = pose_to_ecef(pose_msg_);
1052 request.strategy = PLATOONING_STRATEGY;
1053 request.urgency = 50;
1054
1055 int platoon_size = pm_.getHostPlatoonSize();
1056
1057 // step 3. Request Rear Join
1058 if(isVehicleRightInFront(rearVehicleDtd, rearVehicleCtd) && !config_.test_front_join)
1059 {
1063 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Neighbor platoon is right in front of us");
1064
1065 request.plan_type.type = carma_v2x_msgs::msg::PlanType::JOIN_PLATOON_AT_REAR;
1066
1067 /*
1068 * JOIN_PARAMS format:
1069 * JOIN_PARAMS| --> "SIZE:%1%,SPEED:%2%,ECEFX:%3%,ECEFY:%4%,ECEFZ:%5%,JOINIDX:%6%"
1070 * |-------0------ --1---------2---------3---------4----------5-------|
1071 */
1072
1073 boost::format fmter(JOIN_PARAMS);
1074 int dummy_join_index = -2; //not used for this message, but message spec requires it
1075 fmter %platoon_size; // index = 0
1076 fmter %current_speed_; // index = 1, in m/s
1077 fmter %pose_ecef_point_.ecef_x; // index = 2, in cm.
1078 fmter %pose_ecef_point_.ecef_y; // index = 3, in cm.
1079 fmter %pose_ecef_point_.ecef_z; // index = 4, in cm.
1080 fmter %dummy_join_index; // index = 5
1081 request.strategy_params = fmter.str();
1083 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Publishing request to leader " << senderId << " with params " << request.strategy_params << " and plan id = " << request.m_header.plan_id);
1084
1085 // Create a new join plan
1086 pm_.current_plan = ActionPlan(true, request.m_header.timestamp, request.m_header.plan_id, senderId);
1087
1088 // If we are asking to join an actual platoon (not a solo vehicle), then save its ID for later use
1089 if (platoonId.compare(pm_.dummyID) != 0)
1090 {
1091 pm_.targetPlatoonID = platoonId;
1092 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Detected neighbor as a real platoon & storing its ID: " << platoonId);
1093 }
1094 }
1095
1096 // step 4. Request frontal join, if the neighbor is a real platoon
1097 else if ((targetPlatoonSize > 1 || config_.test_front_join) && isVehicleRightBehind(frontVehicleDtd, frontVehicleCtd))
1098 {
1102 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Neighbor platoon leader is right behind us");
1103
1104 // UCLA: assign a new plan type
1105 request.plan_type.type = carma_v2x_msgs::msg::PlanType::JOIN_PLATOON_FROM_FRONT;
1106
1112 boost::format fmter(JOIN_PARAMS); // Note: Front and rear join uses same params, hence merge to one param for both condition.
1113 int dummy_join_index = -2; //not used for this message, but message spec requires it
1114 fmter %platoon_size; // index = 0
1115 fmter %current_speed_; // index = 1, in m/s
1116 fmter %pose_ecef_point_.ecef_x; // index = 2, in cm.
1117 fmter %pose_ecef_point_.ecef_y; // index = 3, in cm.
1118 fmter %pose_ecef_point_.ecef_z; // index = 4, in cm.
1119 fmter %dummy_join_index; // index = 5
1120 request.strategy_params = fmter.str();
1122 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Publishing front join request to the leader " << senderId << " with params " << request.strategy_params << " and plan id = " << request.m_header.plan_id);
1123
1124 // Create a new join plan
1125 pm_.current_plan = ActionPlan(true, request.m_header.timestamp, request.m_header.plan_id, senderId);
1126
1127 // If testing with a solo vehicle to represent the target platoon, then use the current join plan ID for that platoon;
1128 // otherwise, it will already have and ID so store it for future use
1130 {
1131 pm_.targetPlatoonID = request.m_header.plan_id;
1132 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Since neighbor is a fake platoon, storing " << pm_.targetPlatoonID << " as its platoon ID");
1133 }
1134 else
1135 {
1136 pm_.targetPlatoonID = platoonId;
1137 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Storing real neighbor platoon's ID as target: " << pm_.targetPlatoonID);
1138 }
1139 }
1140
1141 // step 5. Request cut-in join (front, middle or rear, from adjacent lane)
1142 else if ((targetPlatoonSize > 1 || config_.test_cutin_join) && !config_.test_front_join
1143 && isVehicleNearTargetPlatoon(rearVehicleDtd, frontVehicleDtd, frontVehicleCtd))
1144 {
1145
1146 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "starting cut-in join process");
1147 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "rearVehicleDtd " << rearVehicleDtd);
1148 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "rearVehicleCtd " << rearVehicleCtd);
1149
1150 // If we are asking to join an actual platoon (not a solo vehicle), then save its ID for later use
1151 if (platoonId.compare(pm_.dummyID) != 0)
1152 {
1153 pm_.targetPlatoonID = platoonId;
1154 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Detected neighbor as a real platoon & storing its ID: " << platoonId);
1155 }
1156
1157
1158 carma_wm::TrackPos target_trackpose(rearVehicleDtd, rearVehicleCtd);
1159 auto target_rear_pose = wm_->pointFromRouteTrackPos(target_trackpose);
1160 if (target_rear_pose)
1161 {
1162 target_cutin_pose_ = incoming_pose;
1163
1164 auto target_lanelets = lanelet::geometry::findNearest(wm_->getMap()->laneletLayer, target_rear_pose.get(), 1);
1165 if (!target_lanelets.empty())
1166 {
1167 long target_rear_pose_lanelet_id = target_lanelets[0].second.id();
1168 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "target_rear_pose_lanelet_id: " << target_rear_pose_lanelet_id);
1169 }
1170 else
1171 {
1172 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "target_rear_pose_lanelet not found!!");
1173 }
1174 }
1175
1176 else
1177 {
1178 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "No target pose is found, so we cannot prodeed with a cutin join request.");
1179 return;
1180 }
1181
1188 //TODO: verify purpose of this logic then its correctness accordingly:
1189 // -is step 5 (this block) really for all 3 types of joining?
1190 // -ensure join_index gets an appropriate value for the type of join
1191 // -ensure plan type corresponds to join_index
1192
1193 // complete the request
1194 // UCLA: this is a newly added plan type
1195 // Note: Request conposed outside of if conditions
1196 // UCLA: Desired joining index for cut-in join, indicate the index of gap-leading vehicle. -1 indicate cut-in from front.
1197 // Note: remove join_index to info param.
1198 request.plan_type.type = carma_v2x_msgs::msg::PlanType::PLATOON_CUT_IN_JOIN;
1199
1200 // At this step all cut-in types start with this request, so the join_index at this point is set to default, -2.
1201 int join_index = -2;
1202 boost::format fmter(JOIN_PARAMS); // Note: Front and rear join uses same params, hence merge to one param for both condition.
1203 fmter %platoon_size; // index = 0
1204 fmter %current_speed_; // index = 1, in m/s
1205 fmter %pose_ecef_point_.ecef_x; // index = 2
1206 fmter %pose_ecef_point_.ecef_y; // index = 3
1207 fmter %pose_ecef_point_.ecef_z; // index = 4
1208 fmter %join_index; // index = 5
1209 request.strategy_params = fmter.str();
1211 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Publishing request to the leader " << senderId << " with params " << request.strategy_params << " and plan id = " << request.m_header.plan_id);
1212
1213 // Create a new join plan
1214 pm_.current_plan = ActionPlan(true, request.m_header.timestamp, request.m_header.plan_id, senderId);
1215 }
1216
1217 // step 6. Return none if no platoon nearby
1218 else
1219 {
1220 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Ignore platoon with platoon id: " << platoonId << " because it is too far away to join.");
1221 }
1222 }
1223
1224 // TODO: Place holder for prepare to depart (else if isdepart)
1225
1226 }
1227
1228 // UCLA: mob_op_cb for the new leader aborting state (inherited from candidate follower), handle STATUS message.
1229 void PlatoonStrategicIHPPlugin::mob_op_cb_leaderaborting(const carma_v2x_msgs::msg::MobilityOperation& msg)
1230 {
1231 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "LEADERABORTING state no further action on message from " << msg.m_header.sender_id);
1232 }
1233
1234 // UCLA: mob_op_candidateleader for the new candidate leader state (inherited from leader waiting), handle STATUS message.
1235 void PlatoonStrategicIHPPlugin::mob_op_cb_candidateleader(const carma_v2x_msgs::msg::MobilityOperation& msg)
1236 {
1237 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "CANDIDATELEADER state no further action on message from " << msg.m_header.sender_id);
1238 }
1239
1240 // UCLA: Mobility operation callback for lead_with_operation state (cut-in join).
1241 void PlatoonStrategicIHPPlugin::mob_op_cb_leadwithoperation(const carma_v2x_msgs::msg::MobilityOperation& msg)
1242 {
1243 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "LEADWITHOPERATION state no further action on message from " << msg.m_header.sender_id);
1244 }
1245
1246 // UCLA: Mobility operation callback for prepare to join state (cut-in join).
1247 void PlatoonStrategicIHPPlugin::mob_op_cb_preparetojoin(const carma_v2x_msgs::msg::MobilityOperation& msg)
1248 {
1249 /*
1250 * If same lane with leader, then send request to do same lane join.
1251 * Otherwise, just send status params.
1252 * Note: leader in state 'leading with operation' also send out INFO msg
1253 */
1254
1255 // read parameters
1256 std::string strategyParams = msg.strategy_params;
1257 std::string senderId = msg.m_header.sender_id;
1258
1259 // locate INFO type
1260 bool isPlatoonInfoMsg = strategyParams.rfind(OPERATION_INFO_TYPE, 0) == 0;
1261
1262 // If this is an INFO message and our record of the neighbor platoon is complete then
1263 // pm_.is_neighbor_record_complete_ = true; //TODO temporary
1264 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "pm_.is_neighbor_record_complete_" << pm_.is_neighbor_record_complete_);
1265
1266 if (isPlatoonInfoMsg && pm_.is_neighbor_record_complete_)
1267 {
1268
1269 //TODO: would be good to have a timeout here; if a neighbor platoon has been identified, and no INFO messages
1270 // from it are received in a while, then its record should be erased, and any in-work joining should be
1271 // aborted.
1272
1273 // read ecef location from strategy params.
1274 carma_v2x_msgs::msg::LocationECEF ecef_loc;
1275 ecef_loc = mob_op_find_ecef_from_INFO_params(strategyParams);
1276 // use ecef_loc to calculate front Dtd in m.
1277 lanelet::BasicPoint2d incoming_pose = ecef_to_map_point(ecef_loc);
1278
1279 double frontVehicleDtd = wm_->routeTrackPos(incoming_pose).downtrack;
1280
1281 // use ecef_loc to calculate front Ctd in m.
1282 double frontVehicleCtd = wm_->routeTrackPos(incoming_pose).crosstrack;
1283
1284 // // Find neighbor platoon end vehicle and its downtrack in m
1285 int rearVehicleIndex = pm_.neighbor_platoon_.size() - 1;
1286 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "rearVehicleIndex: " << rearVehicleIndex);
1287 double rearVehicleDtd = pm_.neighbor_platoon_[rearVehicleIndex].vehiclePosition;
1288 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Neighbor rearVehicleDtd from ecef: " << rearVehicleDtd);
1289
1290 // If lane change has not yet been authorized, stop here (this method will be running before the negotiations
1291 // with the platoon leader are complete)
1292 if (!safeToLaneChange_)
1293 {
1294 return;
1295 }
1296
1297 // determine if the lane change is finished
1298 bool isSameLaneWithPlatoon = abs(frontVehicleCtd - current_crosstrack_) <= config_.maxCrosstrackError;
1299 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Lane change has been authorized. isSameLaneWithPlatoon = " << isSameLaneWithPlatoon);
1300 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "crosstrack diff" << abs(frontVehicleCtd - current_crosstrack_));
1301 if (isSameLaneWithPlatoon)
1302 {
1303 // request 1. reset the safeToChangLane indicators if lane change is finished
1304 safeToLaneChange_ = false;
1305
1306 // request 2. change to same lane operation states (determine based on DTD differences)
1307 carma_v2x_msgs::msg::MobilityRequest request;
1308 request.m_header.plan_id = boost::uuids::to_string(boost::uuids::random_generator()());
1309 request.m_header.recipient_id = senderId;
1310 request.m_header.sender_id = config_.vehicleID;
1311 request.m_header.timestamp = timer_factory_->now().nanoseconds()/1000000;
1312 request.location = pose_to_ecef(pose_msg_);
1313
1314 // UCLA: send request based on cut-in type
1315 if (frontVehicleDtd < current_downtrack_)
1316 {
1317 request.plan_type.type = carma_v2x_msgs::msg::PlanType::CUT_IN_FRONT_DONE;
1318 }
1319 else
1320 {
1321 request.plan_type.type = carma_v2x_msgs::msg::PlanType::CUT_IN_MID_OR_REAR_DONE;
1322 }
1323 request.strategy = PLATOONING_STRATEGY;
1324 double host_platoon_size = pm_.getHostPlatoonSize();
1325
1326 boost::format fmter(JOIN_PARAMS);
1327 fmter %host_platoon_size; // index = 0
1328 fmter %current_speed_; // index = 1, in m/s
1329 fmter %pose_ecef_point_.ecef_x; // index = 2
1330 fmter %pose_ecef_point_.ecef_y; // index = 3
1331 fmter %pose_ecef_point_.ecef_z; // index = 4
1332 fmter %target_join_index_; // index = 5
1333 request.strategy_params = fmter.str();
1334 request.urgency = 50;
1335
1337 if (pm_.currentPlatoonID.compare(pm_.dummyID) == 0)
1338 {
1339 pm_.currentPlatoonID = request.m_header.plan_id;
1340 }
1341
1342 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "new platoon id: " << pm_.currentPlatoonID);
1343 pm_.current_plan = ActionPlan(true, request.m_header.timestamp, request.m_header.plan_id, senderId);
1344
1345 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Published Mobility request to revert to same-lane operation");
1346 }
1347 else
1348 {
1349 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Lane Change not completed");
1350 }
1351 }
1352 }
1353
1354 // TODO: Place holder for prepare to depart (mob_op_cb_depart)
1355
1356 //------- 3. Mobility Request Callback -------
1357 MobilityRequestResponse PlatoonStrategicIHPPlugin::handle_mob_req(const carma_v2x_msgs::msg::MobilityRequest& msg)
1358 {
1360
1361 // Check that this is a message about platooning (could be from some other Carma activity nearby)
1362 std::string strategy = msg.strategy;
1363 if (strategy.rfind(PLATOONING_STRATEGY, 0) != 0)
1364 {
1365 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Ignoring mobility operation message for " << strategy << " strategy.");
1367 }
1368
1369 // Handle the message based on host's current state
1371 {
1372 mobility_response = mob_req_cb_leader(msg);
1373 }
1375 {
1376 mobility_response = mob_req_cb_follower(msg);
1377 }
1379 {
1380 mobility_response = mob_req_cb_candidatefollower(msg);
1381 }
1383 {
1384 mobility_response = mob_req_cb_leaderwaiting(msg);
1385 }
1387 {
1388 mobility_response = mob_req_cb_standby(msg);
1389 }
1390 // UCLA: leader aborting
1392 {
1393 mobility_response = mob_req_cb_leaderaborting(msg);
1394 }
1395 // UCLA: candidate leader
1397 {
1398 mobility_response = mob_req_cb_candidateleader(msg);
1399 }
1400
1401 // UCLA: lead with operation (for cut-in join)
1403 {
1404 mobility_response = mob_req_cb_leadwithoperation(msg);
1405 }
1406 // UCLA: prepare to join (for cut-in join)
1408 {
1409 mobility_response = mob_req_cb_preparetojoin(msg);
1410 }
1411 // TODO: Place holder for prepare to depart
1412
1413 return mobility_response;
1414 }
1415
1416 MobilityRequestResponse PlatoonStrategicIHPPlugin::mob_req_cb_standby(const carma_v2x_msgs::msg::MobilityRequest& msg)
1417 {
1418 // In standby state, the plugin is not responsible for replying to any request messages
1419 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "STANDBY state does nothing with msg from " << msg.m_header.sender_id);
1421 }
1422
1424 {
1425 // This state does not handle any mobility request for now
1426 // TODO Maybe it should handle some ABORT request from a waiting leader
1427 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Received mobility request with type " << msg.plan_type.type << " but ignored.");
1429 }
1430
1431 MobilityRequestResponse PlatoonStrategicIHPPlugin::mob_req_cb_follower(const carma_v2x_msgs::msg::MobilityRequest& msg)
1432 {
1443 carma_v2x_msgs::msg::PlanType plan_type = msg.plan_type;
1444 std::string reccipientID = msg.m_header.recipient_id;
1445 std::string reqSenderID = msg.m_header.sender_id;
1446
1447 // Check joining plan type.
1448 bool isCutInJoin = plan_type.type == carma_v2x_msgs::msg::PlanType::PLATOON_CUT_IN_JOIN;
1449 bool isGapCreated = plan_type.type == carma_v2x_msgs::msg::PlanType::STOP_CREATE_GAP;
1450 // TODO: Place holder for departure
1451
1452 // Check if host is intended recipient
1453 bool isHostRecipent = pm_.getHostStaticID() == reccipientID;
1454
1455 if (isCutInJoin && isHostRecipent)
1456 {
1457 // Read requesting vehicle's joining index
1458 std::string strategyParams = msg.strategy_params;
1459 std::vector<std::string> inputsParams;
1460 boost::algorithm::split(inputsParams, strategyParams, boost::is_any_of(","));
1461 std::vector<std::string> join_index_parsed;
1462 boost::algorithm::split(join_index_parsed, inputsParams[5], boost::is_any_of(":"));
1463 int req_sender_join_index = std::stoi(join_index_parsed[1]);
1464 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Requesting join_index parsed: " << req_sender_join_index);
1465
1466 // Control vehicle speed based on cut-in type
1467 // 1. cut-in from rear
1468 if (static_cast<size_t>(req_sender_join_index) == pm_.host_platoon_.size()-1)
1469 {
1470 // Accept plan and idle (becasue rear join, gap leading vehicle do not need to slow down).
1471 RCLCPP_WARN(rclcpp::get_logger("platoon_strategic_ihp"),"Requested cut-in from rear, start approve cut-in and wait for lane change.");
1472 RCLCPP_WARN(rclcpp::get_logger("platoon_strategic_ihp"),"Due to the rear join nature, there is no need to slow down or create gap.");
1474
1475 }
1476 // 2. cut-in from middle
1477 else if (req_sender_join_index >= 0 && static_cast<size_t>(req_sender_join_index) < pm_.host_platoon_.size()-1)
1478 {
1479 // Accept plan and slow down to create gap.
1480 pm_.isCreateGap = true;
1481 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Requested cut-in index is: " << req_sender_join_index << ", approve cut-in and start create gap.");
1483 }
1484 // 3. Abnormal join index
1485 else
1486 {
1487 // Note: Leader will abort plan if reponse is not ACK for plantype "PLATOON_CUT_IN_JOIN".
1488 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Abnormal cut-in index, abort operation.");
1490 }
1491 }
1492
1493
1494 // 4. Reset to normal speed once the gap is created.
1495 else if (isGapCreated)
1496 {
1497 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Gap is created, revert to normal operating speed.");
1498 // Only reset create gap indicator, no need to send response.
1499 pm_.isCreateGap = false;
1501 }
1502
1503 // 5. Place holder for departure (host is follower)
1504
1505 // 6. Same-lane join, no need to respond.
1506 else
1507 {
1509 }
1510 }
1511
1512 // Middle state that decided whether to accept joiner
1514 {
1515 bool isTargetVehicle = msg.m_header.sender_id == pm_.current_plan.peerId;
1516 bool isCandidateJoin = msg.plan_type.type == carma_v2x_msgs::msg::PlanType::PLATOON_FOLLOWER_JOIN;
1517
1518 lanelet::BasicPoint2d incoming_pose = ecef_to_map_point(msg.location);
1519 double obj_cross_track = wm_->routeTrackPos(incoming_pose).crosstrack;
1520 bool inTheSameLane = abs(obj_cross_track - current_crosstrack_) < config_.maxCrosstrackError;
1521 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "current_cross_track error = " << abs(obj_cross_track - current_crosstrack_));
1522 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "inTheSameLane = " << inTheSameLane);
1523
1524 // If everything is agreeable then approve the request; if it is from an unexpected vehicle or
1525 // is not a candidate join request, then we can just ignore it with no action
1527 if (isTargetVehicle && isCandidateJoin)
1528 {
1529 if (inTheSameLane)
1530 {
1531 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Target vehicle " << pm_.current_plan.peerId << " is actually joining.");
1532 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Changing to PlatoonLeaderState and send ACK to target vehicle");
1533
1534 // Change state to LEADER
1536
1537 // If we are not already in a platoon, then use this activity plan ID for the newly formed platoon
1538 if (pm_.currentPlatoonID.compare(pm_.dummyID) == 0)
1539 {
1540 pm_.currentPlatoonID = msg.m_header.plan_id;
1541 }
1542
1543 // Add the joiner to our platoon record (ASSUMES that we have not yet received a mob_op STATUS msg from joiner!)
1544 PlatoonMember newMember = PlatoonMember();
1545 newMember.staticId = msg.m_header.sender_id;
1546 newMember.vehiclePosition = wm_->routeTrackPos(incoming_pose).downtrack;
1547 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "New member being added to platoon vector whose size is currently " << pm_.host_platoon_.size());
1548 pm_.host_platoon_.push_back(newMember);
1549 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "pm_ now thinks platoon size is " << pm_.getHostPlatoonSize());
1550
1551 // Send approval of the request
1553 // Indicate the current join activity is complete
1555 }
1556 else //correct vehicle and intent, but it's in the wrong lane
1557 {
1558 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Received platoon request with vehicle id = " << msg.m_header.sender_id << " but in wrong lane. NACK");
1560
1561 // // Remove the candidate joiner from the platoon structure
1562 // if (!pm_.removeMemberById(msg.m_header.sender_id))
1563 // {
1564 // RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Failed to remove candidate joiner from platoon record: " << msg.m_header.sender_id);
1565 // }
1566 }
1567 }
1568
1569
1570 return response;
1571 }
1572
1573 // UCLA: add condition to handle frontal join request
1574 MobilityRequestResponse PlatoonStrategicIHPPlugin::mob_req_cb_leader(const carma_v2x_msgs::msg::MobilityRequest& msg)
1575 {
1588 // Check joining plan type.
1589 carma_v2x_msgs::msg::PlanType plan_type = msg.plan_type;
1596 bool isFrontJoin = plan_type.type == carma_v2x_msgs::msg::PlanType::JOIN_PLATOON_FROM_FRONT;
1597 bool isRearJoin = plan_type.type == carma_v2x_msgs::msg::PlanType::JOIN_PLATOON_AT_REAR;
1598 bool isCutInJoin = plan_type.type == carma_v2x_msgs::msg::PlanType::PLATOON_CUT_IN_JOIN;
1599 bool isDepart = (plan_type.type == carma_v2x_msgs::msg::PlanType::PLATOON_DEPARTURE);
1600
1601 // Ignore the request if we are already working with a join/departure process or if no join type was requested (prevents multiple applicants)
1602 if (isFrontJoin || isRearJoin || isCutInJoin || isDepart)
1603 {
1604 if (pm_.current_plan.valid){
1605 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Ignoring incoming request since we are already negotiating a join.");
1606 return MobilityRequestResponse::NO_RESPONSE; //TODO: replace with NACK that indicates to ask me later
1607 }
1608 }
1609 else
1610 {
1611 RCLCPP_WARN_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Received request with bogus message type " << plan_type.type << "; ignoring");
1613 }
1614
1615 // TODO: Generalize - We currently ignore the lane information for now and assume the applicant is in the same lane with us.
1616 // Determine intra-platoon conditions
1617 // We are currently checking two basic JOIN conditions:
1618 // 1. The size limitation on current platoon based on the plugin's parameters.
1619 // 2. Calculate how long that vehicle can be in a reasonable distance to actually join us.
1620 carma_v2x_msgs::msg::MobilityHeader msgHeader = msg.m_header;
1621 std::string params = msg.strategy_params;
1622 std::string applicantId = msgHeader.sender_id;
1623 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Received mobility JOIN request from " << applicantId << " and PlanId = " << msgHeader.plan_id);
1624 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The strategy parameters are " << params);
1625 if (params.length() == 0)
1626 {
1627 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The strategy parameters are empty, return no response");
1629 }
1630
1631 // The incoming message is "mobility Request", which has a location category.
1632 std::vector<std::string> inputsParams;
1633 boost::algorithm::split(inputsParams, params, boost::is_any_of(","));
1634
1635 // Parse applicantSize
1636 std::vector<std::string> applicantSize_parsed;
1637 boost::algorithm::split(applicantSize_parsed, inputsParams[0], boost::is_any_of(":"));
1638 int applicantSize = std::stoi(applicantSize_parsed[1]);
1639 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "applicantSize: " << applicantSize);
1640
1641 // Parse applicant Current Speed in m/s
1642 std::vector<std::string> applicantCurrentSpeed_parsed;
1643 boost::algorithm::split(applicantCurrentSpeed_parsed, inputsParams[1], boost::is_any_of(":"));
1644 double applicantCurrentSpeed = std::stod(applicantCurrentSpeed_parsed[1]);
1645 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "applicantCurrentSpeed: " << applicantCurrentSpeed);
1646
1647 // Calculate downtrack (m) based on incoming pose.
1648 lanelet::BasicPoint2d incoming_pose = ecef_to_map_point(msg.location);
1649 double applicantCurrentDtd = wm_->routeTrackPos(incoming_pose).downtrack;
1650 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "applicantCurrentmemberUpdates from ecef pose: " << applicantCurrentDtd);
1651
1652 // Calculate crosstrack (m) based on incoming pose.
1653 double applicantCurrentCtd = wm_->routeTrackPos(incoming_pose).crosstrack;
1654 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "applicantCurrentCtd from ecef pose: " << applicantCurrentCtd);
1655 bool isInLane = abs(applicantCurrentCtd - current_crosstrack_) < config_.maxCrosstrackError;
1656 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "isInLane = " << isInLane);
1657
1658 // Check if we have enough room for that applicant
1659 int currentPlatoonSize = pm_.getHostPlatoonSize();
1660 bool hasEnoughRoomInPlatoon = applicantSize + currentPlatoonSize <= config_.maxPlatoonSize;
1661
1662 // rear join; platoon leader --> leader waiting
1663 if (isRearJoin)
1664 {
1665 // Log the request type
1666 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The received mobility JOIN request from " << applicantId << " and PlanId = " << msgHeader.plan_id << " is a same-lane REAR-JOIN request !");
1667
1668 // -- core condition to decided accept joiner or not
1669 if (hasEnoughRoomInPlatoon && isInLane)
1670 {
1671 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The current platoon has enough room for the applicant with size " << applicantSize);
1672 double currentRearDtd = pm_.getPlatoonRearDowntrackDistance();
1673 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The current platoon rear dtd is " << currentRearDtd);
1674 double currentGap = currentRearDtd - applicantCurrentDtd - config_.vehicleLength;
1675 double currentTimeGap = currentGap / applicantCurrentSpeed;
1676 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The gap between current platoon rear and applicant is " << currentGap << "m or " << currentTimeGap << "s");
1677 if (currentGap < config_.minAllowedJoinGap)
1678 {
1679 RCLCPP_WARN(rclcpp::get_logger("platoon_strategic_ihp"),"We should not receive any request from the vehicle in front of us. NACK it.");
1681 }
1682
1683 // Check if the applicant can join based on max timeGap/gap
1684 bool isDistanceCloseEnough = currentGap <= config_.maxAllowedJoinGap || currentTimeGap <= config_.maxAllowedJoinTimeGap;
1685 if (isDistanceCloseEnough)
1686 {
1687 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The applicant is close enough and we will allow it to try to join");
1688 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Change to LeaderWaitingState and waiting for " << msg.m_header.sender_id << " to join");
1689
1690 // change state to leaderwaiting !
1692 waitingStartTime = timer_factory_->now().nanoseconds() / 1000000;
1693 pm_.current_plan = ActionPlan(true, waitingStartTime, msgHeader.plan_id, applicantId);
1696 }
1697 else
1698 {
1699 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The applicant is too far away from us. NACK.");
1700 return MobilityRequestResponse::NACK; //TODO: add reason & request to try again
1701 }
1702 }
1703 else
1704 {
1705 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The current platoon does not have enough room for applicant of size " << applicantSize << ". NACK");
1707 }
1708 }
1709
1710 // front join; platoon leader --> leader aborting
1711 else if (isFrontJoin)
1712 {
1713 // Log the request type
1714 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The received mobility JOIN request from " << applicantId << " and PlanId = " << msgHeader.plan_id << " is a same-lane FRONT-JOIN request !");
1715
1716 // -- core condition to decided accept joiner or not
1717 if (hasEnoughRoomInPlatoon && isInLane)
1718 {
1719 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The current platoon has enough room for the applicant with size " << applicantSize);
1720
1721 // UCLA: change to read platoon front info
1722 double currentFrontDtd = pm_.getPlatoonFrontDowntrackDistance();
1723 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The current platoon front dtd is " << currentFrontDtd);
1724 // UCLA: adjust for calculating gap between new leader and old leader
1725 double currentGap = applicantCurrentDtd - currentFrontDtd - config_.vehicleLength;
1726 double currentTimeGap = currentGap / applicantCurrentSpeed;
1727 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The gap between current platoon front and applicant is " << currentGap << "m or " << currentTimeGap << "s");
1728
1729 if (currentGap < config_.minAllowedJoinGap)
1730 {
1731 RCLCPP_WARN(rclcpp::get_logger("platoon_strategic_ihp"),"The current time gap is not suitable for frontal join. NACK it.");
1733 }
1734
1735 // Check if the applicant can join based on max timeGap/gap
1736 bool isDistanceCloseEnough = currentGap <= config_.maxAllowedJoinGap || currentTimeGap <= config_.maxAllowedJoinTimeGap;
1737
1738 // UCLA: add condition: only allow front join when host platoon size >= 2 (make sure when two single vehicle join, only use back join)
1739 bool isPlatoonNotSingle = pm_.getHostPlatoonSize() >= 2 || config_.test_front_join;
1740
1741 if (isDistanceCloseEnough && isPlatoonNotSingle)
1742 {
1743 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The applicant is close enough for frontal join, send acceptance response");
1744 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Change to LeaderAborting state and waiting for " << msg.m_header.sender_id << " to join as the new platoon leader");
1745
1746 // ----------------- give up leader position and look for new leader --------------------------
1747
1748 // adjust for frontal join. Platoon info is related to the platoon at back of the candidate leader vehicle.
1749 // Don't want an action plan here
1751 candidatestateStartTime = timer_factory_->now().nanoseconds() / 1000000;
1752
1753 // If we are testing with a single vehicle representing this platoon, then we don't yet have a platoon ID,
1754 // so use the ID for the proposed joining action plan
1756 {
1757 pm_.currentPlatoonID = msgHeader.plan_id;
1758 }
1759
1760 // Store the leader ID as that of the joiner to allow run_leader_aborting to work correctly
1761 pm_.platoonLeaderID = applicantId;
1762
1763 waitingStartTime = timer_factory_->now().nanoseconds() / 1000000;
1764 pm_.current_plan.valid = false;
1766 }
1767 else
1768 {
1769 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The joining gap (" << currentGap << " m) is too far away from us or the target platoon size (" << pm_.getHostPlatoonSize() << ") is one. NACK.");
1770 return MobilityRequestResponse::NACK; //TODO: add reason & request to try again
1771 }
1772 }
1773 else
1774 {
1775 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The current platoon does not have enough room for applicant of size " << applicantSize << ". NACK");
1777 }
1778 }
1779
1780 // UCLA: conditions for cut-in join; platoon leader --> leading with operation
1781 else if (isCutInJoin)
1782 {
1783 // Log the request type
1784 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The received mobility JOIN request from " << applicantId << " and PlanId = " << msgHeader.plan_id << " is a CUT-IN-JOIN request !");
1785
1786 // -- core condition to decided accept joiner or not. It is necessary leader only process the first cut-in joining request.
1787 // Note: The host is the platoon leader, need to use a different method to determine if joining vehicle is nearby.
1788 if (hasEnoughRoomInPlatoon && isJoiningVehicleNearPlatoon(applicantCurrentDtd, applicantCurrentCtd))
1789 {
1790 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The current platoon has enough room for the applicant with size " << applicantSize);
1791 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The applicant is close enough for cut-in join, send acceptance response");
1792 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Change to Leading with operation state and waiting for " << msg.m_header.sender_id << " to change lane");
1793 // change state to lead with operation
1795 waitingStartTime = timer_factory_->now().nanoseconds() / 1000000;
1796 pm_.current_plan = ActionPlan(true, waitingStartTime, msgHeader.plan_id, applicantId);
1799 }
1800 else
1801 {
1802 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The current platoon does not have enough room or the applicant is too far away from us. NACK the request.");
1803 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The current applicant size: " << applicantSize << ".");
1804 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The applicant downtrack is: " << current_downtrack_ << ".");
1805 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The applicant crosstrack is: " << current_crosstrack_ << ".");
1807 }
1808 }
1809
1810 // TODO: Place holder for deaprture.
1811
1812 // no response
1813 else
1814 {
1815 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Received mobility request with type " << msg.plan_type.type << " and ignored.");
1817 }
1818 }
1819
1820 // UCLA: mobility request leader aborting (inherited from candidate follower)
1822 {
1823 // This state does not handle any mobility request for now
1824 // TODO Maybe it should handle some ABORT request from a candidate leader
1825 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Received mobility request with type " << msg.plan_type.type << " but ignored.");
1827 }
1828
1829 // UCLA: mobility request candidate leader (inherited from leader waiting)
1831 {
1832 bool isTargetVehicle = msg.m_header.sender_id == pm_.current_plan.peerId; // need to check: senderID (old leader)
1833 bool isCandidateJoin = msg.plan_type.type == carma_v2x_msgs::msg::PlanType::PLATOON_FRONT_JOIN;
1834
1835 lanelet::BasicPoint2d incoming_pose = ecef_to_map_point(msg.location);
1836 double obj_cross_track = wm_->routeTrackPos(incoming_pose).crosstrack;
1837 bool inTheSameLane = abs(obj_cross_track - current_crosstrack_) < config_.maxCrosstrackError;
1838 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "current_cross_track error = " << abs(obj_cross_track - current_crosstrack_));
1839 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "obj_cross_track = " << obj_cross_track);
1840 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "current_crosstrack_ = " << current_crosstrack_);
1841 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "inTheSameLane = " << inTheSameLane);
1842 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "isTargetVehicle = " << isTargetVehicle);
1843 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "isCandidateJoin = " << isCandidateJoin);
1844 if (isCandidateJoin && inTheSameLane && isTargetVehicle)
1845 {
1846 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Old platoon leader " << pm_.current_plan.peerId << " has agreed to joining.");
1847 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Changing to PlatoonLeaderState and send ACK to the previous leader vehicle");
1849
1850 // Clean up planning info
1853
1854 // Clean up neighbor platoon info since we just joined it
1856
1858 }
1859 else
1860 {
1861 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Received platoon request with vehicle id = " << msg.m_header.sender_id);
1862 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The request type is " << msg.plan_type.type << " and we choose to ignore");
1864 pm_.resetHostPlatoon(); //ASSUMES host is a solo joiner
1865
1866 // return to leader state as a solo vehicle
1869 }
1870 }
1871
1872 // UCLA: add request call-back function for lead with operation state (for cut-in join)
1874 {
1875 /*
1876 * Current leader change state to lead with opertaion once the cut-in join request is accepeted.
1877 For cut-in front, the leading vehicle will open gap for the joining vehicle.
1878 For cut-in middle, the leading vechile will request gap following vehcile to create gap.
1879 The host leading vheicle will also send lane cut-in approval response to the joining vehicle.
1880 The host leading vehicle will change to same-lane state once the lanechange completion plan was recieved.
1881
1882 */
1883
1884 // Check request plan type
1885 carma_v2x_msgs::msg::PlanType plan_type = msg.plan_type;
1886 std::string strategyParams = msg.strategy_params;
1887 std::string reqSenderID = msg.m_header.sender_id;
1888
1889 // Calculate downtrack (m) based on ecef.
1890 lanelet::BasicPoint2d incoming_pose = ecef_to_map_point(msg.location);
1891 // read downtrack
1892 double applicantCurrentDtd = wm_->routeTrackPos(incoming_pose).downtrack;
1893 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Applicant downtrack from ecef pose: " << applicantCurrentDtd);
1894
1895 // Read requesting join index
1896 std::vector<std::string> inputsParams;
1897 boost::algorithm::split(inputsParams, strategyParams, boost::is_any_of(","));
1898
1899 std::vector<std::string> join_index_parsed;
1900 boost::algorithm::split(join_index_parsed, inputsParams[5], boost::is_any_of(":"));
1901 int req_sender_join_index = std::stoi(join_index_parsed[1]);
1902 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Requesting join_index parsed: " << req_sender_join_index);
1903
1904 if (plan_type.type == carma_v2x_msgs::msg::PlanType::PLATOON_CUT_IN_JOIN)
1905 {
1906 // Send response
1907 // ----- CUT-IN front -----
1908 if (req_sender_join_index == -1)
1909 {
1910 // determine if joining vehicle in position for cut-in front - this value must be > 0
1911 double cutinDtdDifference = applicantCurrentDtd - current_downtrack_ - config_.vehicleLength;
1912 // check dtd between current leader (host) and joining vehicle is far enough away to avoid a collision
1913 bool isFrontJoinerInPosition = cutinDtdDifference >= 1.5*config_.vehicleLength;
1914
1915 if (isFrontJoinerInPosition)
1916 {
1917 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The joining vehicle is cutting in from front. Gap is already sufficient.");
1919 }
1920 else if (cutinDtdDifference > 0.0 && cutinDtdDifference < 1.5*config_.vehicleLength)
1921 {
1922 // slow down leader to allow joiner cut-in
1923 pm_.isCreateGap = true;
1924 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The joining vehicle is cutting in from front.");
1925 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Host (leader) slow down notified, joining vehicle can prepare to join");
1927 }
1928
1929 else
1930 {
1931 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Front join geometry violation. NACK. cutinDtdDifference = " << cutinDtdDifference);
1934 }
1935 }
1936
1937 // ----- CUT-IN rear -----
1938 else if (static_cast<size_t>(req_sender_join_index) == pm_.host_platoon_.size()-1)
1939 {
1940 // determine if joining vehicle in position for cut-in rear
1941 // To pass, the joining vehicle need to be behind the last member, within three vehicle length.
1942 //TODO: should it also test CTE to ensure vehicle is in same lane?
1943 double platoonEndVehicleDtd = pm_.host_platoon_[pm_.host_platoon_.size()-1].vehiclePosition - config_.vehicleLength;
1944 double rearGap = platoonEndVehicleDtd - applicantCurrentDtd;
1945 bool isRearJoinerInPosition = rearGap >= 0 && rearGap <= config_.maxCutinGap;//3*config_.vehicleLength; TODO: temporary increase
1946
1947 if (isRearJoinerInPosition)
1948 {
1949 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Published Mobility cut-in-rear-Join request to relavent platoon member, host is leader.");
1950 RCLCPP_WARN(rclcpp::get_logger("platoon_strategic_ihp"),"Published Mobility cut-in-rear-Join request to relavent platoon members to signal gap creation.");
1951 pm_.isCreateGap = true;
1953 }
1954 else
1955 {
1956 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Rear join geometry violation. NACK. rearGap = " << rearGap);
1959 }
1960 }
1961 // ----- CUT-IN middle -----
1962 else //any other join_index value
1963 {
1964 // determine if joining vehicle in position for cut-in mid
1965 // To pass, the joining vehicle should be in front of the gap following vehicle, within three vehicle length.
1966 double gapFollowingVehicleDtd = pm_.host_platoon_[req_sender_join_index].vehiclePosition;
1967 double gapFollowerDiff = applicantCurrentDtd - gapFollowingVehicleDtd;
1968 bool isMidJoinerInPosition = gapFollowerDiff >=0 && gapFollowerDiff <= 3*config_.vehicleLength;
1969
1970 // Task 4: send request to index member to slow down (i.e., set isCreateGap to true)
1971 // Note: Request recieving vehicle set pm_.isCreateGap
1972 if (isMidJoinerInPosition)
1973 {
1974 // compose request (Note: The recipient should be the gap following vehicle.)
1975 carma_v2x_msgs::msg::MobilityRequest request;
1976 request.m_header.plan_id = boost::uuids::to_string(boost::uuids::random_generator()());
1977 // Note: For cut-in mid, notify gap rear member to create/increase gap.
1978 std::string recipient_ID = pm_.host_platoon_[req_sender_join_index+1].staticId;
1979 request.m_header.sender_id = config_.vehicleID;
1980 request.m_header.timestamp = timer_factory_->now().nanoseconds() / 1000000;;
1981 // UCLA: add plan type, add this in cav_mwgs/plan_type
1982 request.plan_type.type = carma_v2x_msgs::msg::PlanType::PLATOON_CUT_IN_JOIN;
1983 request.strategy = PLATOONING_STRATEGY;
1984
1985 double platoon_size = pm_.getHostPlatoonSize();
1986
1987 boost::format fmter(JOIN_PARAMS); // Note: Front and rear join uses same params, hence merge to one param for both condition.
1988 fmter %platoon_size; // index = 0
1989 fmter %current_speed_; // index = 1, in m/s
1990 fmter %pose_ecef_point_.ecef_x; // index = 2
1991 fmter %pose_ecef_point_.ecef_y; // index = 3
1992 fmter %pose_ecef_point_.ecef_z; // index = 4
1993 fmter %req_sender_join_index; // index = 5
1994
1995 request.strategy_params = fmter.str();
1996 request.urgency = 50;
1997 request.location = pose_to_ecef(pose_msg_);
1998 // note: for rear join, cut-in index == host_platoon_.size()-1; for join from front, index == -1
1999 // for cut-in in middle, index indicate the gap leading vehicle's index
2001 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Published Mobility cut-in-mid-Join request to relavent platoon members to signal gap creation, host is leader.");
2002 RCLCPP_WARN(rclcpp::get_logger("platoon_strategic_ihp"),"Published Mobility cut-in-mid-Join request to relavent platoon members to signal gap creation.");
2003 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The joining vehicle is cutting in at index: "<< req_sender_join_index <<". Notify gap rear vehicle with ID: " << recipient_ID << " to slow down");
2005 }
2006 else
2007 {
2008 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Mid join geometry violation. NACK. gapFollwerDiff = " << gapFollowerDiff);
2011 }
2012 }
2013 }
2014
2015 // task 2: For cut-in from front, the leader need to stop creating gap
2016 else if (plan_type.type == carma_v2x_msgs::msg::PlanType::STOP_CREATE_GAP)
2017 {
2018 // reset create gap indicator
2019 pm_.isCreateGap = false;
2020 // no need to response, simple reset the indicator
2022 }
2023
2024 // task 3 cut-in front: After creating gap, revert back to same-lane operation
2025 else if (plan_type.type == carma_v2x_msgs::msg::PlanType::CUT_IN_FRONT_DONE)
2026 {
2027 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Cut-in from front lane change finished, leader revert to same-lane maneuver.");
2029 candidatestateStartTime = timer_factory_->now().nanoseconds() / 1000000;
2030 // if testing with two vehicles, use plan id as platoon id
2031 if (pm_.currentPlatoonID.compare(pm_.dummyID) == 0)
2032 // if (config_.allowCutinJoin)
2033 {
2034 pm_.currentPlatoonID = msg.m_header.plan_id;
2035 }
2036 // Store the leader ID as that of the joiner to allow run_leader_aborting to work correctly
2037 pm_.platoonLeaderID = msg.m_header.sender_id;
2038 pm_.current_plan.valid = false;
2040 }
2041
2042 // task 4 cut-in from middle/rear
2043 else if (plan_type.type == carma_v2x_msgs::msg::PlanType::CUT_IN_MID_OR_REAR_DONE)
2044 {
2045 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Cut-in from mid/rear lane change finished, leader revert to same-lane maneuver.");
2047 waitingStartTime = timer_factory_->now().nanoseconds() / 1000000;
2049 }
2050
2051
2052 // task 5: if other joining vehicle send joning request, NACK it since there is already a cut-in join going on.
2053 else
2054 {
2055 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "CUT-IN join maneuver is already in operation, NACK incoming join requests from other candidates.");
2056 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Plan Type: " << plan_type.type );
2058 }
2059
2060 // this statement should never be reached, but will ensure reasonable behavior in case of coding error above
2061 RCLCPP_WARN_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "End of method reached! Apparent logic fault above.");
2062 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "End of method reached! Apparent logic fault above."); //since WARN doesn't always print
2064 }
2065
2066 // UCLA: add request call-back function for prepare to join (for cut-in join)
2068 {
2069 // This state does not handle any mobility request for now
2070 // TODO: if joining vehicle need to adjust speed, the leader should request it and the request should be handled here.
2071 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Received mobility request with type " << msg.plan_type.type << " but ignored.");
2073 }
2074
2075 // TODO: Place holder for departure
2076
2077 // ------ 4. Mobility response callback ------ //
2078
2079 // Mobility response callback for all states.
2080 void PlatoonStrategicIHPPlugin::mob_resp_cb(const carma_v2x_msgs::msg::MobilityResponse::UniquePtr msg)
2081 {
2082 // Firstly, check eligibility of the received message.
2083 bool isCurrPlanValid = pm_.current_plan.valid; // Check if current plan is still valid (i.e., not timed out).
2084 bool isForCurrentPlan = msg->m_header.plan_id == pm_.current_plan.planId; // Check if plan Id matches.
2085 bool isFromTargetVehicle = msg->m_header.sender_id == pm_.current_plan.peerId; // Check if expected peer ID and sender ID matches.
2086 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "mob_resp_cb: isCurrPlanValid = " << isCurrPlanValid << ", isForCurrentPlan = " <<
2087 isForCurrentPlan << ", isFromTargetVehicle = " << isFromTargetVehicle);
2088 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "sender ID = " << msg->m_header.sender_id << ", current peer ID = " << pm_.current_plan.peerId);
2089 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "incoming plan ID = " << msg->m_header.plan_id << "current plan ID = " << pm_.current_plan.planId);
2090
2091 if (!(isCurrPlanValid && isForCurrentPlan && isFromTargetVehicle))
2092 {
2097 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), " Ignore the received response message as it was not intended for the host vehicle.");
2098 return;
2099 }
2101 {
2102 mob_resp_cb_leader(*msg);
2103 }
2105 {
2107 }
2109 {
2111 }
2113 {
2115 }
2117 {
2118 mob_resp_cb_standby(*msg);
2119 }
2120 // UCLA: add leader aboorting
2122 {
2124 }
2125 //UCLA: add candidate leader
2127 {
2129 }
2130 // UCLA: add lead with operation for cut-in join
2132 {
2134 }
2135 // UCLA: add prepare to join for cut-in join
2137 {
2139 }
2140 // TODO: Place holder for departure.
2141 }
2142
2143 void PlatoonStrategicIHPPlugin::mob_resp_cb_standby(const carma_v2x_msgs::msg::MobilityResponse& msg)
2144 {
2145 // In standby state, it will not send out any requests so it will also ignore all responses
2146 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "STANDBY state does nothing with msg from " << msg.m_header.sender_id);
2147 }
2148
2149 void PlatoonStrategicIHPPlugin::mob_resp_cb_candidatefollower(const carma_v2x_msgs::msg::MobilityResponse& msg)
2150 {
2151 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Callback for candidate follower ");
2152
2153 // Check if current plan is still valid (i.e., not timed out)
2155 {
2156 bool isForCurrentPlan = msg.m_header.plan_id == pm_.current_plan.planId;
2157 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "isForCurrentPlan " << isForCurrentPlan);
2158
2159 // Check the response is received correctly (i.e., host vehicle is the desired receiver).
2160 if (isForCurrentPlan)
2161 {
2162 if (msg.is_accepted)
2163 {
2164 // We change to follower state and start to actually follow that leader
2165 // The platoon manager also need to change the platoon Id to the one that the target leader is using
2167 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "pm_.currentPlatoonID: " << pm_.currentPlatoonID);
2168 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "pm_.targetPlatoonID: " << pm_.currentPlatoonID);
2169
2170 if (pm_.targetPlatoonID.compare(pm_.dummyID) != 0)
2171 {
2173 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "pm_.currentPlatoonID now: " << pm_.currentPlatoonID);
2175 }
2176 else
2177 {
2178 pm_.currentPlatoonID = msg.m_header.plan_id;
2179 }
2180
2181 pm_.changeFromLeaderToFollower(pm_.currentPlatoonID, msg.m_header.sender_id);
2182 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The leader " << msg.m_header.sender_id << " agreed on our join. Change to follower state.");
2183 RCLCPP_WARN(rclcpp::get_logger("platoon_strategic_ihp"),"changed to follower");
2185 }
2186 else
2187 {
2188 // We change back to normal leader state and try to join other platoons
2189 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The leader " << msg.m_header.sender_id << " does not agree on our join. Change back to leader state.");
2190 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Trying again..");
2191 // join plan failed, but we still need the peerid
2192 pm_.current_plan.valid = false;
2193
2194 // Clear out any platooning plan we don't need
2195 if (pm_.getHostPlatoonSize() == 1)
2196 {
2198 }
2199 }
2200
2201 }
2202 else
2203 {
2204 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Ignore received response message because it is not for the current plan.");
2205 }
2206 }
2207 else
2208 {
2209 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Ignore received response message because we are not in any negotiation process.");
2210 }
2211 }
2212
2213 void PlatoonStrategicIHPPlugin::mob_resp_cb_leaderwaiting(const carma_v2x_msgs::msg::MobilityResponse& msg)
2214 {
2220 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "LEADERWAITING state does nothing with msg from " << msg.m_header.sender_id);
2221 }
2222
2223 void PlatoonStrategicIHPPlugin::mob_resp_cb_follower(const carma_v2x_msgs::msg::MobilityResponse& msg)
2224 {
2237 // UCLA: read plan type
2238 carma_v2x_msgs::msg::PlanType plan_type = msg.plan_type;
2239
2240 // UCLA: determine joining type
2241 bool isFrontJoin = (plan_type.type == carma_v2x_msgs::msg::PlanType::JOIN_PLATOON_FROM_FRONT);
2242
2243 //TODO: when would this code block ever be used? A normal follower would have to talk to a front joiner.
2244 // UCLA: add response so follower can change to candidate follower, then change leader
2245 if (isFrontJoin && msg.is_accepted)
2246 {
2247 // if frontal join is accepted, change followers to candidate follower to update leader
2248 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Received positive response for front-join plan id = " << pm_.current_plan.planId);
2249 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Change to CandidateFollower state and prepare to update platoon information");
2250 // Change to candidate follower state and request a new plan to catch up with the front platoon
2252 candidatestateStartTime = timer_factory_->now().nanoseconds() / 1000000;
2253 }
2254
2255 // TODO: Place holder for follower departure.
2256 }
2257
2258 // UCLA: add conditions to account for frontal join states (candidate follower)
2259 void PlatoonStrategicIHPPlugin::mob_resp_cb_leader(const carma_v2x_msgs::msg::MobilityResponse& msg)
2260 {
2275 // UCLA: read plan type
2276 carma_v2x_msgs::msg::PlanType plan_type = msg.plan_type;
2277 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "plan_type.type = " << plan_type.type);
2278
2279 // UCLA: determine joining type
2280 bool isCutInJoin = plan_type.type == carma_v2x_msgs::msg::PlanType::PLATOON_CUT_IN_JOIN && !config_.test_front_join;
2281 bool isRearJoin = plan_type.type == carma_v2x_msgs::msg::PlanType::JOIN_PLATOON_AT_REAR && !config_.test_front_join;
2282 bool isFrontJoin = plan_type.type == carma_v2x_msgs::msg::PlanType::JOIN_PLATOON_FROM_FRONT || config_.test_front_join;
2283 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Joining type: isRearJoin = " << isRearJoin);
2284 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Joining type: isFrontJoin = " << isFrontJoin);
2285 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Joining type: isCutInJoin = " << isCutInJoin);
2286
2287 // Check if current plan is still valid (i.e., not timed out).
2289 {
2290 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "My plan id = " << pm_.current_plan.planId << " and response plan Id = " << msg.m_header.plan_id);
2291 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Expected peer id = " << pm_.current_plan.peerId << " and response sender Id = " << msg.m_header.sender_id);
2292
2293 // Check the response is received correctly (i.e., host vehicle is the desired receiver).
2294 if (pm_.current_plan.planId == msg.m_header.plan_id && pm_.current_plan.peerId == msg.m_header.sender_id)
2295 {
2296 // rear join
2297 if (isRearJoin && msg.is_accepted)
2298 {
2299 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Received positive response for plan id = " << pm_.current_plan.planId);
2300 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Change to CandidateFollower state and notify trajectory failure in order to replan");
2301
2302 // Change to candidate follower state and wait to catch up with the front platoon
2304 candidatestateStartTime = timer_factory_->now().nanoseconds() / 1000000;
2305 pm_.current_plan.valid = false; //but leave peerId intact for use in second request
2306 }
2307
2308 // UCLA: frontal join (candidate leader, inherited from leaderwaiting)
2309 else if (isFrontJoin && msg.is_accepted)
2310 {
2311 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Received positive response for plan id = " << pm_.current_plan.planId);
2312 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Change to CandidateLeader state and prepare to become new leader. ");
2313
2314 // Change to candidate leader and idle
2316 candidatestateStartTime = timer_factory_->now().nanoseconds() / 1000000;
2317 pm_.current_plan.valid = false; //but leave peerId intact for use in second request
2318
2319 // Set the platoon ID to that of the target platoon even though we haven't yet joined;
2320 // for front join this is necessary for the aborting leader to recognize us as an incoming
2321 // member (via our published op STATUS messages)
2323 }
2324
2325 // UCLA: CutIn join
2326 else if (isCutInJoin && msg.is_accepted)
2327 {
2328 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Received positive response for plan id = " << pm_.current_plan.planId);
2329 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Change to Prepare to join state and prepare to change lane. ");
2330
2331 // Change to candidate leader and idle
2333 pm_.neighbor_platoon_leader_id_ = msg.m_header.sender_id;
2334 candidatestateStartTime = timer_factory_->now().nanoseconds() / 1000000;
2335 pm_.current_plan.valid = false; //but leave peerId intact for use in second request
2336 }
2337
2338 // Current leader of an actual platoon (to be in this method host is in leader state)
2339 else if(pm_.getHostPlatoonSize() >= 2)
2340 {
2341 //TODO future: add logic here to allow two platoons to join together
2342
2343 // Keep the leader idling, since this must be a bogus response
2344 RCLCPP_WARN_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Host received response for joining vehicles, remain idling as the host is a current platoon leader.");
2345 }
2346 else
2347 {
2348 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Received negative response for plan id = " << pm_.current_plan.planId << ". Resetting plan & platoon info.");
2349 // Forget about the previous plan totally
2352 }
2353 }
2354 else
2355 {
2356 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Ignore the response message because planID match: " << (pm_.current_plan.planId == msg.m_header.plan_id));
2357 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "My plan id = " << pm_.current_plan.planId << " and response plan Id = " << msg.m_header.plan_id);
2358 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "And peer id match " << (pm_.current_plan.peerId == msg.m_header.sender_id));
2359 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Expected peer id = " << pm_.current_plan.peerId << " and response sender Id = " << msg.m_header.sender_id);
2360 }
2361 }
2362 }
2363
2364 // UCLA: response for leader aborting (inherited from candidate follower)
2365 void PlatoonStrategicIHPPlugin::mob_resp_cb_leaderaborting(const carma_v2x_msgs::msg::MobilityResponse& msg)
2366 {
2378 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Callback for leader aborting !");
2379
2380 // Check if current plan is still valid (i.e., not timed out).
2382 {
2383 bool isForCurrentPlan = msg.m_header.plan_id == pm_.current_plan.planId;
2384 bool isForFrontJoin = msg.plan_type.type == carma_v2x_msgs::msg::PlanType::PLATOON_FRONT_JOIN;
2385
2386 if (msg.plan_type.type == carma_v2x_msgs::msg::PlanType::UNKNOWN){
2387 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "*** plan type UNKNOWN");
2388 }else if (msg.plan_type.type == carma_v2x_msgs::msg::PlanType::JOIN_PLATOON_FROM_FRONT){
2389 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "*** plan type JOIN_PLATOON_FROM_FRONT");
2390 }else if (msg.plan_type.type == carma_v2x_msgs::msg::PlanType::PLATOON_CUT_IN_JOIN){
2391 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "*** plan type PLATOON_CUT_IN_JOIN");
2392 }else {
2393 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "*** plan type not captured.");
2394 }
2395
2396 bool isFromTargetVehicle = msg.m_header.sender_id == pm_.current_plan.peerId;
2397 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "msg.m_header.sender_id " << msg.m_header.sender_id);
2398 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Plan Type " << msg.plan_type.type);
2399 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "isForFrontJoin " << isForFrontJoin);
2400 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "isForCurrentPlan " << isForCurrentPlan);
2401 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "isFromTargetVehicle " << isFromTargetVehicle);
2402
2403 // Check the response is received correctly (i.e., host vehicle is the desired receiver).
2404 if (isForCurrentPlan && isFromTargetVehicle && isForFrontJoin)
2405 {
2406 if (msg.is_accepted)
2407 {
2408 // We change to follower state and start to actually follow the new leader
2409 // The platoon manager also need to change the platoon Id to the one that the target leader is using
2411 pm_.changeFromLeaderToFollower(pm_.currentPlatoonID, msg.m_header.sender_id);
2412 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The new leader " << msg.m_header.sender_id << " agreed on the frontal join. Change to follower state.");
2413 RCLCPP_WARN(rclcpp::get_logger("platoon_strategic_ihp"),"changed to follower");
2414
2415 // reset leader aborting request marker
2417 }
2418 else
2419 {
2420 // We change back to normal leader state
2421 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The new leader " << msg.m_header.sender_id << " does not agree on the frontal join. Change back to leader state.");
2423 // We were already leading a platoon, so don't erase any of that info. But we need to remove the erstwhile candidate
2424 // leader from our platoon roster; we know it is in position 0, so just remove that element
2425 if (!pm_.removeMember(0))
2426 {
2427 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Failed to remove candidate leader from the platoon!");
2428 }
2429 }
2430
2431 // Clean up the joining plan
2433 }
2434 else
2435 {
2436 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Ignore received response message because it is not for the current plan.");
2437 }
2438 }
2439 else
2440 {
2441 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Ignore received response message because we are not in any negotiation process.");
2442 }
2443 }
2444
2445 // UCLA: response for candidate leader (inherited from leader waiting)
2446 void PlatoonStrategicIHPPlugin::mob_resp_cb_candidateleader(const carma_v2x_msgs::msg::MobilityResponse& msg)
2447 {
2448 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "CANDIDATELEADER state does nothing with msg from " << msg.m_header.sender_id);
2449 }
2450
2451 // UCLA: response callback for lead with operation
2452 void PlatoonStrategicIHPPlugin::mob_resp_cb_leadwithoperation(const carma_v2x_msgs::msg::MobilityResponse& msg)
2453 {
2454 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "LEADWITHOPERATION state does nothing with msg from " << msg.m_header.sender_id);
2455 }
2456
2457 // UCLA: response callback for prepare to join (inherited from leader waiting)
2458 void PlatoonStrategicIHPPlugin::mob_resp_cb_preparetojoin(const carma_v2x_msgs::msg::MobilityResponse& msg)
2459 {
2460 /*
2461 If leader notify the member to slow down and ACK the request,
2462 start to check the gap and change lane when gap is large enough
2463 */
2464
2465 carma_v2x_msgs::msg::PlanType plan_type = msg.plan_type;
2466 bool isCreatingGap = plan_type.type == carma_v2x_msgs::msg::PlanType::PLATOON_CUT_IN_JOIN;
2467 bool isFinishLaneChangeFront = plan_type.type == carma_v2x_msgs::msg::PlanType::CUT_IN_FRONT_DONE;
2468 bool isFinishLaneChangeMidorRear = plan_type.type == carma_v2x_msgs::msg::PlanType::CUT_IN_MID_OR_REAR_DONE;
2469 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "isCreatingGap = " << isCreatingGap << ", is_neighbor_record_complete = " << pm_.is_neighbor_record_complete_);
2470
2471 if (!msg.is_accepted)
2472 {
2473 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Request " << msg.m_header.plan_id << " was rejected by leader.");
2474 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Action Plan reset.");
2475 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Trying again....");
2476 pm_.current_plan.valid = false;
2478 return;
2479 }
2480 // UCLA: Create Gap or perform a rear join (no gap creation necessary)
2481 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "pm_.is_neighbor_record_complete_ " << pm_.is_neighbor_record_complete_);
2482 if (isCreatingGap && pm_.is_neighbor_record_complete_)
2483 {
2484
2485 // task 2: set indicator if gap is safe
2486 safeToLaneChange_ = true;
2487 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Gap is now sufficiently large.");
2488 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "in mob_resp_cb safeToLaneChange_: " << safeToLaneChange_);
2489
2490 // task 3: notify gap-rear vehicle to stop slowing down
2491 carma_v2x_msgs::msg::MobilityRequest request;
2492 request.m_header.plan_id = boost::uuids::to_string(boost::uuids::random_generator()());
2493 request.m_header.recipient_id = pm_.current_plan.peerId;
2494 request.m_header.sender_id = config_.vehicleID;
2495 request.m_header.timestamp = timer_factory_->now().nanoseconds() / 1000000;;
2496 // UCLA: A new plan type to stop creat gap.
2497 request.plan_type.type = carma_v2x_msgs::msg::PlanType::STOP_CREATE_GAP;
2498 request.strategy = PLATOONING_STRATEGY;
2499 request.urgency = 50;
2500 request.location = pose_to_ecef(pose_msg_);
2501 double platoon_size = pm_.getHostPlatoonSize();
2502
2503 boost::format fmter(JOIN_PARAMS);
2504 fmter %platoon_size; // index = 0
2505 fmter %current_speed_; // index = 1, in m/s
2506 fmter %pose_ecef_point_.ecef_x; // index = 2
2507 fmter %pose_ecef_point_.ecef_y; // index = 3
2508 fmter %pose_ecef_point_.ecef_z; // index = 4
2509 fmter %target_join_index_; // index = 5
2510
2511 request.strategy_params = fmter.str();
2513 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Published Mobility Candidate-Join request to the leader to stop creating gap");
2514 }
2515
2516 // UCLA: Revert to same-lane for cut-in front
2517 else if (isFinishLaneChangeFront)
2518 {
2519 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Cut-in from front lane change finished, the joining vehicle revert to same-lane maneuver.");
2521 candidatestateStartTime = timer_factory_->now().nanoseconds() / 1000000;
2522 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "pm_.currentPlatoonID: " << pm_.currentPlatoonID);
2523 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "pm_.targetPlatoonID: " << pm_.targetPlatoonID);
2524 if (pm_.targetPlatoonID.compare(pm_.dummyID) != 0)
2525 {
2527 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "pm_.currentPlatoonID now: " << pm_.currentPlatoonID);
2528 }
2529
2530 pm_.current_plan.valid = false; //but leave peerId intact for use in second request
2531 }
2532
2533 // UCLA: Revert to same-lane operation for cut-in from middle/rear
2534 else if (isFinishLaneChangeMidorRear)
2535 {
2536 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Cut-in from mid or rear, the lane change finished, the joining vehicle revert to same-lane maneuver.");
2538 candidatestateStartTime = timer_factory_->now().nanoseconds() / 1000000;
2539 pm_.current_plan.valid = false; //but leave peerId intact for use in second request
2540
2541 }
2542
2543 else
2544 {
2545 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "End of mob_resp_cb_preparetojoin");
2546 }
2547 }
2548
2549 // TODO: Place holder for departure.
2550
2551 // ------ 5. response types ------- //
2552
2553 // ACK --> yes,accept host as member; NACK --> no, cannot accept host as member
2554 void PlatoonStrategicIHPPlugin::mob_req_cb(const carma_v2x_msgs::msg::MobilityRequest::UniquePtr msg)
2555 {
2556 // Ignore messages as long as host vehicle is stopped
2558 {
2559 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Ignoring message since host speed is below platooning speed.");
2560 return;
2561 }
2562
2563 // Check that this is a message about platooning (could be from some other Carma activity nearby)
2564 std::string strategy = msg->strategy;
2565 if (strategy.rfind(PLATOONING_STRATEGY, 0) != 0)
2566 {
2567 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Ignoring mobility operation message for " << strategy << " strategy.");
2568 return;
2569 }
2570
2571 carma_v2x_msgs::msg::MobilityResponse response;
2572 response.m_header.sender_id = config_.vehicleID;
2573 response.m_header.recipient_id = msg->m_header.sender_id;
2574 response.m_header.plan_id = msg->m_header.plan_id;
2575 response.m_header.timestamp = timer_factory_->now().nanoseconds() / 1000000;
2576
2577 // UCLA: add plantype in response
2578 response.plan_type.type = msg->plan_type.type;
2579
2580 MobilityRequestResponse req_response = handle_mob_req(*msg);
2581 if (req_response == MobilityRequestResponse::ACK)
2582 {
2583 response.is_accepted = true;
2585 }
2586 else if (req_response == MobilityRequestResponse::NACK)
2587 {
2588 response.is_accepted = false;
2590 }
2591 else
2592 {
2593 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), " NO response to mobility request. ");
2594 }
2595 }
2596
2597
2598 //------------------------------------------ FSM states --------------------------------------------------//
2599
2601 {
2602 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Run LeaderWaiting State ");
2603 long tsStart = timer_factory_->now().nanoseconds() / 1000000;
2604 // Task 1
2605 if (tsStart - waitingStartTime > waitingStateTimeout * 1000)
2606 {
2607 //TODO if the current state timeouts, we need to have a kind of ABORT message to inform the applicant
2608 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "LeaderWaitingState is timeout, changing back to PlatoonLeaderState.");
2611 }
2612 // Task 2
2613 carma_v2x_msgs::msg::MobilityOperation status;
2616 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "publish status message");
2617 long tsEnd = timer_factory_->now().nanoseconds() / 1000000;
2618 long sleepDuration = std::max((int32_t)(statusMessageInterval_ - (tsEnd - tsStart)), 0);
2619
2620 // TODO this solution is not sim-time complient and should be replaced with one which is when possible
2621 // https://github.com/usdot-fhwa-stol/carma-platform/issues/1888
2622 std::this_thread::sleep_for(std::chrono::milliseconds(sleepDuration));
2623 }
2624
2626 {
2627 unsigned long tsStart = timer_factory_->now().nanoseconds() / 1000000;
2628
2629 // If vehicle is not rolling then return
2631 {
2632 return;
2633 }
2634
2635 // Task 1: heart beat timeout: send INFO mob_op
2636 bool isTimeForHeartBeat = tsStart - prevHeartBeatTime_ >= infoMessageInterval_;
2637 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "time since last heart beat: " << tsStart - prevHeartBeatTime_);
2638 if (isTimeForHeartBeat)
2639 {
2640 carma_v2x_msgs::msg::MobilityOperation infoOperation;
2642 mobility_operation_publisher_(infoOperation);
2643 prevHeartBeatTime_ = timer_factory_->now().nanoseconds() / 1000000;
2644 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Published heart beat platoon INFO mobility operation message");
2645 }
2646
2647 // Task 3: plan time out, check if any current join plan is still valid (i.e., not timed out).
2649 {
2650 bool isCurrentPlanTimeout = tsStart - pm_.current_plan.planStartTime > NEGOTIATION_TIMEOUT;
2651 if (isCurrentPlanTimeout)
2652 {
2653 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Give up current on waiting plan with planId: " << pm_.current_plan.planId);
2655 }
2656 }
2657
2658 // Task 4: STATUS msgs
2659 bool hasFollower = pm_.getHostPlatoonSize() > 1 || config_.test_cutin_join;
2660 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "hasFollower" << hasFollower);
2661 // if has follower, publish platoon message as STATUS mob_op
2662 if (hasFollower)
2663 {
2664 carma_v2x_msgs::msg::MobilityOperation statusOperation;
2666 // mob_op_pub_.publish(statusOperation);
2667 mobility_operation_publisher_(statusOperation);
2668 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Published platoon STATUS operation message as a Leader with Follower");
2669 }
2670
2671 long tsEnd = timer_factory_->now().nanoseconds() / 1000000;
2672 long sleepDuration = std::max((int32_t)(statusMessageInterval_ - (tsEnd - tsStart)), 0);
2673 // TODO this solution is not sim-time complient and should be replaced with one which is when possible
2674 // https://github.com/usdot-fhwa-stol/carma-platform/issues/1888
2675 std::this_thread::sleep_for(std::chrono::milliseconds(sleepDuration));
2676
2677 // Job 5: Dissoleve request.
2678 // TODO: Place holder for departure. Need to change to departing state and tracking departng ID.
2679
2680 }
2681
2683 {
2684 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "run follower");
2685 // This is a interrupted-safe loop.
2686 // This loop has four tasks:
2687 // 1. Check the state start time, if it exceeds a limit it will give up current plan and change back to leader state
2688 // 2. Abort current request if we wait for long enough time for response from leader and change back to leader state
2689
2690 long tsStart = timer_factory_->now().nanoseconds() / 1000000;
2691 // Job 1
2692 carma_v2x_msgs::msg::MobilityOperation status;
2695 // Job 2
2696 // Get the number of vehicles in this platoon who is in front of us
2697 int vehicleInFront = pm_.getNumberOfVehicleInFront();
2698 if (vehicleInFront == 0)
2699 {
2702 {
2703 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "noLeaderUpdatesCounter = " << noLeaderUpdatesCounter << " and change to leader state");
2707 }
2708 }
2709 else
2710 {
2711 // reset counter to zero when we get updates again
2713 }
2714 long tsEnd = timer_factory_->now().nanoseconds() / 1000000;
2715 long sleepDuration = std::max((int32_t)(statusMessageInterval_ - (tsEnd - tsStart)), 0);
2716 // TODO this solution is not sim-time complient and should be replaced with one which is when possible
2717 // https://github.com/usdot-fhwa-stol/carma-platform/issues/1888
2718 std::this_thread::sleep_for(std::chrono::milliseconds(sleepDuration));
2719
2720 // Job 3: Dissoleve request.
2721 //TODO: set departure indicator
2722
2723 }
2724
2726 {
2727 long tsStart = timer_factory_->now().nanoseconds() / 1000000;
2728
2729 // Task 1: state timeout
2730 bool isCurrentStateTimeout = (tsStart - candidatestateStartTime) > waitingStateTimeout * 1000;
2731 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "timeout1: " << tsStart - candidatestateStartTime);
2732 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "waitingStateTimeout: " << waitingStateTimeout * 1000);
2733 if (isCurrentStateTimeout)
2734 {
2735 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The current candidate follower state is timeout. Change back to leader state.");
2738 }
2739
2740 // Task 2: plan timeout, check if current plan is still valid (i.e., not timed out).
2742 {
2743 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "pm_.current_plan.planStartTime: " << pm_.current_plan.planStartTime);
2744 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "timeout2: " << tsStart - pm_.current_plan.planStartTime);
2745 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "NEGOTIATION_TIMEOUT: " << NEGOTIATION_TIMEOUT);
2746 bool isPlanTimeout = tsStart - pm_.current_plan.planStartTime > NEGOTIATION_TIMEOUT;
2747 if (isPlanTimeout)
2748 {
2751 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The current plan did not receive any response. Abort and change to leader state.");
2752 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Changed the state back to Leader");
2753 }
2754 }
2755
2756 // Task 3: update plan calculate gap, update plan: send PLATOON_FOLLOWER_JOIN request with new gap
2757 double desiredJoinGap2 = config_.desiredJoinTimeGap * current_speed_;
2758 double maxJoinGap = std::max(config_.desiredJoinGap, desiredJoinGap2);
2759 double currentGap = 0.0;
2760 if (!pm_.neighbor_platoon_.empty())
2761 {
2762 currentGap = pm_.neighbor_platoon_.back().vehiclePosition - current_downtrack_;
2763 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "curent gap calculated from back of neighbor platoon: " << currentGap);
2764 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "pm_.neighbor_platoon_.back().vehiclePosition " << pm_.neighbor_platoon_.back().vehiclePosition);
2765
2766 }
2767 else
2768 {
2769 currentGap = pm_.getDistanceToPredVehicle();
2770 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "curent gap when there is no neighbor platoon: " << currentGap);
2771 }
2772
2773 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Based on desired join time gap, the desired join distance gap is " << desiredJoinGap2 << " ms");
2774 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Since we have max allowed gap as " << config_.desiredJoinGap << " m then max join gap became " << maxJoinGap << " m");
2775 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The current gap from radar is " << currentGap << " m");
2776 if (currentGap <= maxJoinGap && !pm_.current_plan.valid)
2777 {
2778 carma_v2x_msgs::msg::MobilityRequest request;
2779 std::string planId = boost::uuids::to_string(boost::uuids::random_generator()());
2780 long currentTime = timer_factory_->now().nanoseconds() / 1000000;
2781 request.m_header.plan_id = planId;
2782 request.m_header.recipient_id = pm_.current_plan.peerId;
2783 request.m_header.sender_id = config_.vehicleID;
2784 request.m_header.timestamp = currentTime;
2785
2786 request.plan_type.type = carma_v2x_msgs::msg::PlanType::PLATOON_FOLLOWER_JOIN;
2787 request.strategy = PLATOONING_STRATEGY;
2788 request.strategy_params = ""; //params will not be read by receiver since this is 2nd msg in sequence
2789 request.urgency = 50;
2790 request.location = pose_to_ecef(pose_msg_);
2792 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Published Mobility Candidate-Join request to the leader");
2793 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "current plan peer id: " << pm_.current_plan.peerId);
2794
2795 // Update the local record of the new activity plan and now establish that we have a platoon plan as well,
2796 // which allows us to start sending necessary op STATUS messages
2797 pm_.current_plan = ActionPlan(true, currentTime, planId, pm_.current_plan.peerId);
2799
2800 // Initialize counter to delay transmission of first operation STATUS message
2802 }
2803
2804 //Task 4: publish platoon status message (as single joiner)
2806 {
2807 // Don't want to do this until after the above MobReq message is delivered, otherwise recipient will double-count us in their platoon
2809 {
2810 carma_v2x_msgs::msg::MobilityOperation status;
2813 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Published platoon STATUS operation message as Candidate Follower");
2814 }
2815 }
2816
2817 long tsEnd = timer_factory_->now().nanoseconds() / 1000000;
2818 long sleepDuration = std::max((int32_t)(statusMessageInterval_ - (tsEnd - tsStart)), 0);
2819 // TODO this solution is not sim-time complient and should be replaced with one which is when possible
2820 // https://github.com/usdot-fhwa-stol/carma-platform/issues/1888
2821 std::this_thread::sleep_for(std::chrono::milliseconds(sleepDuration));
2822 }
2823
2824 // UCLA: frontal join state (inherit from candidate follower: prepare to give up leading state and accept the new leader)
2826 {
2827 /*
2828 UCLA implementation note:
2829 1. this function send step plan type: "PLATOON_FRONT_JOIN"
2830 2. the sender of the plan (host vehicle) is the previous leader, it wil prepare to follow front joiner (new leader)
2831 */
2832 long tsStart = timer_factory_->now().nanoseconds() / 1000000;
2833 // Task 1: state timeout
2834 bool isCurrentStateTimeout = (tsStart - candidatestateStartTime) > waitingStateTimeout * 1000;
2835 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "timeout1: " << tsStart - candidatestateStartTime);
2836 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "waitingStateTimeout: " << waitingStateTimeout * 1000);
2837 if (isCurrentStateTimeout)
2838 {
2839 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The current leader aborting state is timeout. Change back to leader state.");
2841
2842 //clear plan validity & end; leave platoon info alone, as we may still be leading a valid platoon
2844 return;
2845 }
2846
2847 // Task 3: update plan: PLATOON_FRONT_JOIN with new gap
2848 double desiredJoinGap2 = config_.desiredJoinTimeGap * current_speed_;
2849 double maxJoinGap = std::max(config_.desiredJoinGap, desiredJoinGap2);
2850
2851 // check if compatible for front join --> return front gap, no veh type check, is compatible
2852 // Note that pm_ only represents host's platoon members. As the aborting leader, the only vehicle
2853 // preceding host is the candidate joiner. For this code to work, it depends on the candidate to publish
2854 // mobility operation STATUS messages so that host can include it in the pm_ platoon membership.
2855 double currentGap = pm_.getDistanceToPredVehicle(); //returns 0 if we haven't received op STATUS from joiner yet
2856 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Based on desired join time gap, the desired join distance gap is " << desiredJoinGap2 << " m");
2857 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Since we have max allowed gap as " << config_.desiredJoinGap << " m then max join gap became " << maxJoinGap << " m");
2858 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The current gap to joiner is " << currentGap << " m");
2859
2860 // NOTE: The front join depends upon the joiner to publish op STATUS messages with this platoon's ID, then host receives at least one
2861 // and thereby adds the joiner to the platoon record. This process requires host's mob_req_cb_leader() to ACK the join request, then
2862 // the remote vehicle to handle that ACK and broadcast its first op STATUS, then host to receive and process it. All that has to happen
2863 // before the below code block runs, even though this method starts to spin immediately after we send out the mentioned ACK. To avoid
2864 // a race condition, we must wait a few cycles to ensure the 2-way messaging has completed. The race is that above calculation for
2865 // current gap will be returned as 0 until we have received said STATUS message from the joiner, which would prematurely trigger the
2866 // process to move forward.
2867
2868 // Check if gap is big enough and if there is no currently active plan and this method has been called several times
2869 // Add a condition to prevent sending repeated requests (Note: This is a same-lane maneuver, so no need to consider lower bound of joining gap.)
2871 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "numLeaderAbortingCalls = " << numLeaderAbortingCalls_ << ", max = " << config_.maxLeaderAbortingCalls);
2872 if (currentGap <= maxJoinGap && !pm_.current_plan.valid && numLeaderAbortingCalls_ > config_.maxLeaderAbortingCalls)
2873 {
2874 // compose frontal joining plan, senderID is the old leader
2875 carma_v2x_msgs::msg::MobilityRequest request;
2876 std::string planId = boost::uuids::to_string(boost::uuids::random_generator()());
2877 long currentTime = timer_factory_->now().nanoseconds() / 1000000;
2878 request.m_header.plan_id = planId;
2879 request.m_header.recipient_id = pm_.platoonLeaderID; //the new joiner
2880 request.m_header.sender_id = config_.vehicleID;
2881 request.m_header.timestamp = currentTime;
2882
2883 int platoon_size = pm_.getHostPlatoonSize(); //depends on joiner to send op STATUS messages while joining
2884
2885 boost::format fmter(JOIN_PARAMS); // Note: Front and rear join uses same params, hence merge to one param for both condition.
2886 int dummy_join_index = -2; //leader aborting doesn't need join_index so use default value
2887 fmter %platoon_size; // index = 0
2888 fmter %current_speed_; // index = 1, in m/s
2889 fmter %pose_ecef_point_.ecef_x; // index = 2
2890 fmter %pose_ecef_point_.ecef_y; // index = 3
2891 fmter %pose_ecef_point_.ecef_z; // index = 4
2892 fmter %dummy_join_index; // index = 5
2893 request.strategy_params = fmter.str();
2894
2895 // assign a new plan type
2896 request.plan_type.type = carma_v2x_msgs::msg::PlanType::PLATOON_FRONT_JOIN;
2897 request.strategy = PLATOONING_STRATEGY;
2898 request.urgency = 50;
2899 request.location = pose_to_ecef(pose_msg_);
2901 RCLCPP_WARN(rclcpp::get_logger("platoon_strategic_ihp"),"Published Mobility Candidate-Join request to the new leader");
2902
2903 // Create a new join action plan
2904 pm_.current_plan = ActionPlan(true, currentTime, planId, pm_.platoonLeaderID);
2905 }
2906
2907 //Task 4: publish platoon status message
2908 carma_v2x_msgs::msg::MobilityOperation status;
2911 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Published platoon STATUS operation message");
2912
2913 long tsEnd = timer_factory_->now().nanoseconds() / 1000000;
2914 long sleepDuration = std::max((int32_t)(statusMessageInterval_ - (tsEnd - tsStart)), 0);
2915 // TODO this solution is not sim-time complient and should be replaced with one which is when possible
2916 // https://github.com/usdot-fhwa-stol/carma-platform/issues/1888
2917 std::this_thread::sleep_for(std::chrono::milliseconds(sleepDuration));
2918 }
2919
2920 // UCLA: frontal join state (inherited from leader waiting: prepare to join as th new leader)
2922 {
2923 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Run Candidate Leader State ");
2924 long tsStart = timer_factory_->now().nanoseconds() / 1000000;
2925 // Task 1: State time out
2926 if (tsStart - candidatestateStartTime > waitingStateTimeout * 1000)
2927 {
2928 //TODO if the current state timeouts, we need to have a kind of ABORT message to inform the applicant
2929 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "CandidateLeader state is timeout, changing back to PlatoonLeaderState.");
2933 }
2934
2935 // Task 2: publish status message
2936 carma_v2x_msgs::msg::MobilityOperation status;
2939 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "publish status message");
2940 long tsEnd = timer_factory_->now().nanoseconds() / 1000000;
2941 long sleepDuration = std::max((int32_t)(statusMessageInterval_ - (tsEnd - tsStart)), 0);
2942 // TODO this solution is not sim-time complient and should be replaced with one which is when possible
2943 // https://github.com/usdot-fhwa-stol/carma-platform/issues/1888
2944 std::this_thread::sleep_for(std::chrono::milliseconds(sleepDuration));
2945 }
2946
2947 // UCLA: add leading with operation state for cut-in join platoon leader
2949 {
2950 long tsStart = timer_factory_->now().nanoseconds() / 1000000;
2951 // Task 1: heart beat timeout: constantly send INFO mob_op
2952 bool isTimeForHeartBeat = tsStart - prevHeartBeatTime_ >= infoMessageInterval_;
2953 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "time since last heart beat: " << tsStart - prevHeartBeatTime_);
2954 if (isTimeForHeartBeat)
2955 {
2956 carma_v2x_msgs::msg::MobilityOperation infoOperation;
2958 mobility_operation_publisher_(infoOperation);
2959 prevHeartBeatTime_ = timer_factory_->now().nanoseconds() / 1000000;
2960 }
2961
2962
2963 // Task 4: STATUS msgs
2964 bool hasFollower = pm_.getHostPlatoonSize() > 1 || config_.test_cutin_join;
2965 // if has follower, publish platoon message as STATUS mob_op
2966 if (hasFollower)
2967 {
2968 carma_v2x_msgs::msg::MobilityOperation statusOperation;
2970 mobility_operation_publisher_(statusOperation);
2971 }
2972 long tsEnd = timer_factory_->now().nanoseconds() / 1000000;
2973 long sleepDuration = std::max((int32_t)(statusMessageInterval_ - (tsEnd - tsStart)), 0);
2974 // TODO this solution is not sim-time complient and should be replaced with one which is when possible
2975 // https://github.com/usdot-fhwa-stol/carma-platform/issues/1888
2976 std::this_thread::sleep_for(std::chrono::milliseconds(sleepDuration));
2977 }
2978
2979 // UCLA: add prepare to join state for cut-in joining vehicle
2981 {
2982 /*
2983 * The prepare join state should have the following tasks:
2984 * 1. Compose mobility operation param: status.
2985 * 2. Time out check.
2986 * 3. Calculate proper cut_in index
2987 * 4. Send out lane change intend to leader.
2988 * Note: 1. safeToLaneChange_ monitors the gap condition.
2989 * 2. Once it is safe to lane change, the updated plan will send-out in "plan_maneuver_cb"
2990 */
2991
2992 // Task 2.1: state timeout
2993 long tsStart = timer_factory_->now().nanoseconds() / 1000000;
2994 bool isCurrentStateTimeout = (tsStart - candidatestateStartTime) > waitingStateTimeout * 1000;
2995 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "timeout1: " << tsStart - candidatestateStartTime);
2996 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "waitingStateTimeout: " << waitingStateTimeout * 1000);
2997 if (isCurrentStateTimeout)
2998 {
2999 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The current prepare to join state is timeout. Change back to leader state and abort lane change.");
3001 safeToLaneChange_ = false;
3004 // Leave neighbor platoon info in place, as we may retry the join later
3005 }
3006
3007 // TODO: Plan timeout is not needed for this state
3008
3009 // If we aren't already waiting on a response to one of these plans, create one once neighbor info is available
3010 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "current_plan.valid = " << pm_.current_plan.valid << ", is_neighbor_record_complete = " << pm_.is_neighbor_record_complete_);
3011
3013 {
3014 // Task 1: compose mobility operation (status)
3015 carma_v2x_msgs::msg::MobilityOperation status;
3016 status = composeMobilityOperationPrepareToJoin(); //TODO: I bet we could consolidate a lot of these compose methods
3018 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Published platoon STATUS operation message");
3019
3020 // Task 3: Calculate proper cut_in index
3021 // Note: The cut-in index is zero-based and points to the gap-leading vehicle's index. For cut-in from front, the join index = -1.
3022 double joinerDtD = current_downtrack_;
3024 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "calculated join index: " << target_join_index_);
3025
3026 // Task 4: Send out request to leader about cut-in position
3027 carma_v2x_msgs::msg::MobilityRequest request;
3028 std::string planId = boost::uuids::to_string(boost::uuids::random_generator()());
3029 long currentTime = timer_factory_->now().nanoseconds() / 1000000;
3030 request.m_header.plan_id = planId;
3031 request.m_header.recipient_id = pm_.neighbor_platoon_leader_id_;
3032 request.m_header.sender_id = config_.vehicleID;
3033 request.m_header.timestamp = currentTime;
3034 request.plan_type.type = carma_v2x_msgs::msg::PlanType::PLATOON_CUT_IN_JOIN;
3035 request.strategy = PLATOONING_STRATEGY;
3036 request.urgency = 50;
3037 request.location = pose_to_ecef(pose_msg_);
3038 double platoon_size = pm_.getHostPlatoonSize();
3039
3040 boost::format fmter(JOIN_PARAMS);
3041 fmter %platoon_size; // index = 0
3042 fmter %current_speed_; // index = 1, in m/s
3043 fmter %pose_ecef_point_.ecef_x; // index = 2
3044 fmter %pose_ecef_point_.ecef_y; // index = 3
3045 fmter %pose_ecef_point_.ecef_z; // index = 4
3046 fmter %target_join_index_; // index = 5
3047 request.strategy_params = fmter.str();
3049 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Published Mobility cut-in join request to leader " << request.m_header.recipient_id << " with planId = " << planId);
3050
3051 // Create a new join action plan
3052 pm_.current_plan = ActionPlan(true, currentTime, planId, pm_.neighbor_platoon_leader_id_);
3053 }
3054 }
3055
3056 // UCLA: run prepare to depart state for depart from platoon
3057 // TODO: Place holder for departure
3058
3059
3060 //------------------------------------------- main functions for platoon plugin --------------------------------------------//
3061
3062 // Platoon on spin
3064 {
3065 // Update the platoon manager for host's current location & speeds
3068
3070 {
3071 run_leader();
3072 }
3074 {
3075 run_follower();
3076 }
3078 {
3080 }
3082 {
3084 }
3085 // UCLA: added for frontal join
3087 {
3089 }
3090 // UCLA: added for frontal join
3092 {
3094 }
3095 // UCLA: added lead with operationfor CUT-IN join
3097 {
3099 }
3100 // UCLA: added prepare to join for CUT-IN join
3102 {
3104 }
3106 {
3107 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "standby state, nothing to do");
3108 }
3109 // coding oversight
3110 else
3111 {
3112 RCLCPP_ERROR_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "///// unhandled state " << pm_.current_platoon_state);
3113 }
3114 // TODO: Place holder for departure
3115
3116 carma_planning_msgs::msg::PlatooningInfo platoon_status = composePlatoonInfoMsg();
3117 platooning_info_publisher_(platoon_status);
3118
3119 return true;
3120 }
3121
3122 // ------- Generate maneuver plan (Service Callback) ------- //
3123
3124 // compose maneuver message
3125 carma_planning_msgs::msg::Maneuver PlatoonStrategicIHPPlugin::composeManeuverMessage(double current_dist, double end_dist, double current_speed, double target_speed, int lane_id, rclcpp::Time& current_time)
3126 {
3127 carma_planning_msgs::msg::Maneuver maneuver_msg;
3128 maneuver_msg.type = carma_planning_msgs::msg::Maneuver::LANE_FOLLOWING;
3129 maneuver_msg.lane_following_maneuver.parameters.negotiation_type = carma_planning_msgs::msg::ManeuverParameters::PLATOONING;
3130 maneuver_msg.lane_following_maneuver.parameters.presence_vector = carma_planning_msgs::msg::ManeuverParameters::HAS_TACTICAL_PLUGIN;
3131 maneuver_msg.lane_following_maneuver.parameters.planning_tactical_plugin = "platooning_tactical_plugin";
3132 maneuver_msg.lane_following_maneuver.parameters.planning_strategic_plugin = "platoon_strategic_ihp";
3133 maneuver_msg.lane_following_maneuver.start_dist = current_dist;
3134 maneuver_msg.lane_following_maneuver.start_speed = current_speed;
3135 maneuver_msg.lane_following_maneuver.start_time = current_time;
3136 maneuver_msg.lane_following_maneuver.end_dist = end_dist;
3137 maneuver_msg.lane_following_maneuver.end_speed = target_speed;
3138
3139 // because it is a rough plan, assume vehicle can always reach to the target speed in a lanelet
3140 maneuver_msg.lane_following_maneuver.end_time = current_time + rclcpp::Duration(config_.time_step*1e9);
3141 maneuver_msg.lane_following_maneuver.lane_ids = { std::to_string(lane_id) };
3142
3143 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "in compose maneuver lane id:"<< lane_id);
3144
3145 lanelet::ConstLanelet current_lanelet = wm_->getMap()->laneletLayer.get(lane_id);
3146 if(!wm_->getMapRoutingGraph()->following(current_lanelet, false).empty())
3147 {
3148
3149 auto next_lanelet_id = wm_->getMapRoutingGraph()->following(current_lanelet, false).front().id();
3150 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "next_lanelet_id:"<< next_lanelet_id);
3151 maneuver_msg.lane_following_maneuver.lane_ids.push_back(std::to_string(next_lanelet_id));
3152 }
3153 else
3154 {
3155 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "No following lanelets");
3156 }
3157
3158 current_time = maneuver_msg.lane_following_maneuver.end_time;
3159 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Creating lane follow start dist:"<<current_dist<<" end dist:"<<end_dist);
3160 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Duration:"<< rclcpp::Time(maneuver_msg.lane_following_maneuver.end_time).seconds() - rclcpp::Time(maneuver_msg.lane_following_maneuver.start_time).seconds());
3161 return maneuver_msg;
3162 }
3163
3164 // UCLA: compose maneuver message for lane change
3165 carma_planning_msgs::msg::Maneuver PlatoonStrategicIHPPlugin::composeLaneChangeManeuverMessage(double current_dist, double end_dist, double current_speed, double target_speed, int starting_lane_id, int ending_lane_id, rclcpp::Time& current_time)
3166 {
3167 carma_planning_msgs::msg::Maneuver maneuver_msg;
3168 // UCLA: change to lane change maneuvers
3169 maneuver_msg.type = carma_planning_msgs::msg::Maneuver::LANE_CHANGE;
3170 maneuver_msg.lane_change_maneuver.parameters.negotiation_type = carma_planning_msgs::msg::ManeuverParameters::PLATOONING;
3171 maneuver_msg.lane_change_maneuver.parameters.presence_vector = carma_planning_msgs::msg::ManeuverParameters::HAS_TACTICAL_PLUGIN;
3172 maneuver_msg.lane_change_maneuver.parameters.planning_tactical_plugin = "cooperative_lanechange";
3173 maneuver_msg.lane_change_maneuver.parameters.planning_strategic_plugin = "platoon_strategic_ihp";
3174 maneuver_msg.lane_change_maneuver.start_dist = current_dist;
3175 maneuver_msg.lane_change_maneuver.start_speed = current_speed;
3176 maneuver_msg.lane_change_maneuver.start_time = current_time;
3177 maneuver_msg.lane_change_maneuver.end_dist = end_dist;
3178 maneuver_msg.lane_change_maneuver.end_speed = target_speed;
3179 // Generate a new maneuver ID for the lane change maneuver (not needed for lane following maneuver).
3180 maneuver_msg.lane_following_maneuver.parameters.maneuver_id = boost::uuids::to_string(boost::uuids::random_generator()());
3181
3182 // because it is a rough plan, assume vehicle can always reach to the target speed in a lanelet
3183 double cur_plus_target = current_speed + target_speed;
3184 if (cur_plus_target < 0.00001)
3185 {
3186 maneuver_msg.lane_change_maneuver.end_time = current_time + rclcpp::Duration(config_.time_step*1e9);
3187 }
3188 else
3189 {
3190 // maneuver_msg.lane_change_maneuver.end_time = current_time + rclcpp::Duration((end_dist - current_dist) / (0.5 * cur_plus_target));
3191 maneuver_msg.lane_change_maneuver.end_time = current_time + rclcpp::Duration(20.0*1e9);
3192
3193 }
3194
3195 // UCLA: need both start laneID and end laneID for lane change
3196 maneuver_msg.lane_change_maneuver.starting_lane_id = { std::to_string(starting_lane_id) };
3197 maneuver_msg.lane_change_maneuver.ending_lane_id = { std::to_string(ending_lane_id) };
3198
3199 current_time = maneuver_msg.lane_change_maneuver.end_time;
3200 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Creating lane change start dist:"<<current_dist<<" end dist:"<<end_dist);
3201 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Duration:"<< rclcpp::Time(maneuver_msg.lane_change_maneuver.end_time).seconds() - rclcpp::Time(maneuver_msg.lane_change_maneuver.start_time).seconds());
3202 return maneuver_msg;
3203 }
3204
3205 // update current status based on maneuver
3206 void PlatoonStrategicIHPPlugin::updateCurrentStatus(carma_planning_msgs::msg::Maneuver maneuver, double& speed, double& current_progress, int& lane_id)
3207 {
3208 if(maneuver.type == carma_planning_msgs::msg::Maneuver::LANE_FOLLOWING){
3209 speed = maneuver.lane_following_maneuver.end_speed;
3210 current_progress = maneuver.lane_following_maneuver.end_dist;
3211 if (maneuver.lane_following_maneuver.lane_ids.empty())
3212 {
3213 RCLCPP_WARN_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Lane id of lane following maneuver not set. Using 0");
3214 lane_id = 0;
3215 }
3216 else
3217 {
3218 lane_id = stoi(maneuver.lane_following_maneuver.lane_ids[0]);
3219 }
3220 }
3221 }
3222
3223 // maneuver plan callback (provide cav_srvs for arbitrator)
3224 bool PlatoonStrategicIHPPlugin::plan_maneuver_cb(carma_planning_msgs::srv::PlanManeuvers::Request &req, carma_planning_msgs::srv::PlanManeuvers::Response &resp)
3225 {
3226 // use current position to find lanelet ID
3227 lanelet::BasicPoint2d current_loc(pose_msg_.pose.position.x, pose_msg_.pose.position.y);
3228
3229 // *** get the actually closest lanelets that relate to current location (n=10) ***//
3230 auto current_lanelets = lanelet::geometry::findNearest(wm_->getMap()->laneletLayer, current_loc, 10);
3231
3232 lanelet::ConstLanelet current_lanelet;
3233
3234 // To avoid overlapping lanelets, compare the nearest lanelets with the route
3235 for (auto llt: current_lanelets)
3236 {
3237 if (wm_->getRoute()->contains(llt.second))
3238 {
3239 current_lanelet = llt.second;
3240 break;
3241 }
3242 }
3243
3244 // raise warn if no path was found
3245 if(current_lanelets.size() == 0)
3246 {
3247 RCLCPP_WARN_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Cannot find any lanelet in map!");
3248 return true;
3249 }
3250
3251 // locate lanelet on shortest path
3252 auto shortest_path = wm_->getRoute()->shortestPath(); // find path amoung route
3253
3254 // read status data
3255 double current_progress = wm_->routeTrackPos(current_loc).downtrack;
3256 double speed_progress = current_speed_;
3257 rclcpp::Time time_progress = timer_factory_->now();
3258
3259 // ---------------- use IHP platoon trajectory regulation here --------------------
3260 // Note: The desired gap will be adjusted and send to control plugin (via platoon_info_msg) where gap creation will be handled.
3261 double target_speed;
3262
3263 // Note: gap regulation has moved to control plug-in, no need to adjust speed here.
3264 target_speed = findSpeedLimit(current_lanelet); //get Speed Limit
3265 double total_maneuver_length = current_progress + config_.time_step * target_speed;
3266 // ----------------------------------------------------------------
3267
3268 // pick smaller length, accomendate when host is close to the route end
3269 double route_length = wm_->getRouteEndTrackPos().downtrack;
3270 total_maneuver_length = std::min(total_maneuver_length, route_length);
3271
3272 // Update current status based on prior plan
3273 if(req.prior_plan.maneuvers.size()!= 0)
3274 {
3275 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Provided with initial plan...");
3276 time_progress = req.prior_plan.planning_completion_time;
3277 int end_lanelet = 0;
3278 updateCurrentStatus(req.prior_plan.maneuvers.back(), speed_progress, current_progress, end_lanelet);
3279 }
3280
3281 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Starting Loop");
3282 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "total_maneuver_length: " << total_maneuver_length << " route_length: " << route_length);
3283
3284
3285 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "in mvr callback safeToLaneChange: " << safeToLaneChange_);
3286
3287 // Note: Use current_lanlet list (which was determined based on vehicle pose) to find current lanelet ID.
3288 lanelet::Id current_lanelet_id = current_lanelet.id();
3289
3290
3291
3292 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "current_lanelet_id: " << current_lanelet_id);
3293 // lane change maneuver
3295 {
3296 // for testing purpose only, check lane change status
3297 double target_crosstrack = wm_->routeTrackPos(target_cutin_pose_).crosstrack;
3298 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "target_crosstrack: " << target_crosstrack);
3299 double crosstrackDiff = current_crosstrack_ - target_crosstrack;
3300 bool isLaneChangeFinished = abs(crosstrackDiff) <= config_.maxCrosstrackError;
3301 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "crosstrackDiff: " << crosstrackDiff);
3302 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "isLaneChangeFinished: " << isLaneChangeFinished);
3303
3304 // lane change not finished, use lane change plan
3305 if(!isLaneChangeFinished)
3306 {
3307 // send out lane change plan
3308 while (current_progress < total_maneuver_length)
3309 {
3310 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Lane Change Maneuver for Cut-in join ! ");
3311 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "current_progress: "<< current_progress);
3312 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "speed_progress: " << speed_progress);
3313 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "target_speed: " << target_speed);
3314 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "time_progress: " << rclcpp::Time(time_progress).seconds());
3315
3316 // set to next lane destination, consider sending ecef instead of dtd
3317 double end_dist = total_maneuver_length;
3318 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "end_dist: " << end_dist);
3319 // consider calculate dtd_diff and ctd_diff
3320 double dist_diff = end_dist - current_progress;
3321 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "dist_diff: " << dist_diff);
3322
3323
3324 //TODO: target_cutin_pose_ represents the platoon leader. It seems this may be the wrong answer for mid- or rear-cutins?
3325 //SAINA: currently, the functions do not provide the correct point of rear vehicle of the platoon
3326 double lc_end_dist = wm_->routeTrackPos(target_cutin_pose_).downtrack;
3327 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "lc_end_dist before buffer: " << lc_end_dist);
3328 lc_end_dist = std::max(lc_end_dist, current_progress + config_.maxCutinGap);
3329 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "lc_end_dist after buffer: " << lc_end_dist);
3330
3331 //TODO: target_cutin_pose_ represents the platoon leader. Is this the best pose to use here?
3332 // get the actually closest lanelets,
3333 auto target_lanelets = lanelet::geometry::findNearest(wm_->getMap()->laneletLayer, target_cutin_pose_, 1);
3334 if (target_lanelets.empty())
3335 {
3336 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "The target cutin pose is not on a valid lanelet. So no lane change!");
3337 break;
3338 }
3339 lanelet::Id target_lanelet_id = target_lanelets[0].second.id();
3340 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "target_lanelet_id: " << target_lanelet_id);
3341
3342 lanelet::ConstLanelet starting_lanelet = wm_->getMap()->laneletLayer.get(current_lanelet_id);
3343 lanelet::ConstLanelet ending_lanelet = wm_->getMap()->laneletLayer.get(target_lanelet_id);
3344
3345 bool lanechangePossible = is_lanechange_possible(current_lanelet_id, target_lanelet_id);
3346 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "lanechangePossible: " << lanechangePossible);
3347
3348 if (lanechangePossible)
3349 {
3350 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Lane change possible, planning it.. " );
3351 resp.new_plan.maneuvers.push_back(composeLaneChangeManeuverMessage(current_downtrack_, lc_end_dist,
3352 speed_progress, target_speed, current_lanelet_id, target_lanelet_id , time_progress));
3353
3354 }
3355 else
3356 {
3357 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Lane change impossible, planning lanefollow instead ... " );
3358 resp.new_plan.maneuvers.push_back(composeManeuverMessage(current_downtrack_, end_dist,
3359 speed_progress, target_speed, current_lanelet_id, time_progress));
3360 }
3361
3362
3363
3364 current_progress += dist_diff;
3365 // read lane change maneuver end time as time progress
3366 time_progress = resp.new_plan.maneuvers.back().lane_change_maneuver.end_time;
3367 speed_progress = target_speed;
3368 if(current_progress >= total_maneuver_length)
3369 {
3370 break;
3371 }
3372
3373 }
3374 }
3375
3376 // lane change finished, use lane following plan
3377 else
3378 {
3379 // send out lane following plan
3380 while (current_progress < total_maneuver_length)
3381 {
3382 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Same Lane Maneuver for platoon join ! ");
3383 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "current_progress: "<< current_progress);
3384 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "speed_progress: " << speed_progress);
3385 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "target_speed: " << target_speed);
3386 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "time_progress: " << rclcpp::Time(time_progress).seconds());
3387 double end_dist = total_maneuver_length;
3388 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "end_dist: " << end_dist);
3389 double dist_diff = end_dist - current_progress;
3390 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "dist_diff: " << dist_diff);
3391 if(end_dist < current_progress)
3392 {
3393 break;
3394 }
3395 // Note: The previous plan was generated at the beginning of the trip. It is necessary to update
3396 // it as the lane ID and lanelet Index are different.
3397 resp.new_plan.maneuvers.push_back(composeManeuverMessage(current_downtrack_, end_dist,
3398 speed_progress, target_speed, current_lanelet_id, time_progress));
3399
3400 current_progress += dist_diff;
3401 time_progress = resp.new_plan.maneuvers.back().lane_following_maneuver.end_time;
3402 speed_progress = target_speed;
3403 if(current_progress >= total_maneuver_length)
3404 {
3405 break;
3406 }
3407 }
3408 }
3409 }
3410
3411 // same-lane maneuver
3412 else
3413 {
3414 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Planning Same Lane Maneuver! ");
3415 while (current_progress < total_maneuver_length)
3416 {
3417 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Same Lane Maneuver for platoon join ! ");
3418 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "current_progress: "<< current_progress);
3419 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "speed_progress: " << speed_progress);
3420 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "target_speed: " << target_speed);
3421 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "time_progress: " << rclcpp::Time(time_progress).seconds());
3422 double end_dist = total_maneuver_length;
3423 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "end_dist: " << end_dist);
3424 double dist_diff = end_dist - current_progress;
3425 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "dist_diff: " << dist_diff);
3426 if (end_dist < current_progress)
3427 {
3428 break;
3429 }
3430
3431 resp.new_plan.maneuvers.push_back(composeManeuverMessage(current_progress, end_dist,
3432 speed_progress, target_speed,current_lanelet_id, time_progress));
3433
3434
3435 current_progress += dist_diff;
3436 time_progress = resp.new_plan.maneuvers.back().lane_following_maneuver.end_time;
3437 speed_progress = target_speed;
3438 if(current_progress >= total_maneuver_length)
3439 {
3440 break;
3441 }
3442 }
3443 }
3444
3445
3446 if(resp.new_plan.maneuvers.size() == 0)
3447 {
3448 RCLCPP_WARN_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Cannot plan maneuver because no route is found");
3449 }
3450
3451
3453 {
3454 resp.new_plan.maneuvers = {};
3455 RCLCPP_WARN_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Platoon size 1 so Empty maneuver sent");
3456 }
3457 else
3458 {
3459 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Planning maneuvers: ");
3460 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "safeToLaneChange_: " << safeToLaneChange_);
3461 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "pm_.getHostPlatoonSize(): " << pm_.getHostPlatoonSize());
3462 }
3463
3465 {
3467 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "change the state from standby to leader at start-up");
3468 }
3469
3470 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "current_downtrack: " << current_downtrack_);
3471
3472 return true;
3473 }
3474
3475 bool PlatoonStrategicIHPPlugin::is_lanechange_possible(lanelet::Id start_lanelet_id, lanelet::Id target_lanelet_id)
3476 {
3477 lanelet::ConstLanelet starting_lanelet = wm_->getMap()->laneletLayer.get(start_lanelet_id);
3478 lanelet::ConstLanelet ending_lanelet = wm_->getMap()->laneletLayer.get(target_lanelet_id);
3479 lanelet::ConstLanelet current_lanelet = starting_lanelet;
3480 bool shared_boundary_found = false;
3481
3482 while(!shared_boundary_found)
3483 {
3484 //Assumption- Adjacent lanelets share lane boundary
3485 if(current_lanelet.leftBound() == ending_lanelet.rightBound())
3486 {
3487 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Lanelet " << std::to_string(current_lanelet.id()) << " shares left boundary with " << std::to_string(ending_lanelet.id()));
3488 shared_boundary_found = true;
3489 }
3490 else if(current_lanelet.rightBound() == ending_lanelet.leftBound())
3491 {
3492 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("platoon_strategic_ihp"), "Lanelet " << std::to_string(current_lanelet.id()) << " shares right boundary with " << std::to_string(ending_lanelet.id()));
3493 shared_boundary_found = true;
3494 }
3495
3496 else
3497 {
3498 //If there are no following lanelets on route, lanechange should be completing before reaching it
3499 if(wm_->getMapRoutingGraph()->following(current_lanelet, false).empty())
3500 {
3501 //In this case we have reached a lanelet which does not have a routable lanelet ahead + isn't adjacent to the lanelet where lane change ends
3502 return false;
3503 }
3504
3505 current_lanelet = wm_->getMapRoutingGraph()->following(current_lanelet, false).front();
3506 if(current_lanelet.id() == starting_lanelet.id())
3507 {
3508 //Looped back to starting lanelet
3509 return false;
3510 }
3511 }
3512 }
3513
3514
3515 return true;
3516 }
3517
3518 //--------------------------------------------------------------------------//
3519
3521 {
3522 config_ = config;
3523 }
3524
3525}
Position in a track based coordinate system where the axis are downtrack and crosstrack....
Definition: TrackPos.hpp:35
Class containing the logic for platoon manager. It is responsible for keeping track of the platoon me...
int getNumberOfVehicleInFront()
Get number of vehicles in front of host vehicle inside platoon.
PlatoonMember getDynamicLeader()
Returns dynamic leader of the host vehicle.
bool removeMember(const size_t mem)
Removes a single member from the internal record of platoon members.
double getDistanceToPredVehicle()
Returns distance to the predessecor vehicle, in m.
std::vector< PlatoonMember > neighbor_platoon_
void updateHostSpeeds(const double cmdSpeed, const double actualSpeed)
Stores the latest info on host vehicle's command & actual speeds.
double getPlatoonRearDowntrackDistance()
Returns downtrack distance of the rear vehicle in platoon, in m.
int getClosestIndex(double joinerDtD)
UCLA: Return joiner's desired position in terms of target platoon index to cut into the platoon....
int getHostPlatoonSize()
Returns total size of the platoon , in number of vehicles.
double getPredecessorPosition()
UCLA: Return the position of the preceding vehicle, in m.
void hostMemberUpdates(const std::string &senderId, const std::string &platoonId, const std::string &params, const double &DtD, const double &CtD)
Update information for members of the host's platoon based on a mobility operation STATUS message.
void changeFromLeaderToFollower(std::string newPlatoonId, std::string newLeaderId)
Update status when state change from Leader to Follower.
double getPlatoonFrontDowntrackDistance()
UCLA: Return the platoon leader downtrack distance, in m.
void updateHostPose(const double downtrack, const double crosstrack)
Stores the latest info on location of the host vehicle.
void resetHostPlatoon()
Resets all variables that might indicate other members of the host's platoon; sets the host back to s...
void resetNeighborPlatoon()
Resets all variables that describe a neighbor platoon, so that no neighbor is known.
double getPredecessorSpeed()
UCLA: Return the speed of the preceding vehicle, in m/s.
double getPredecessorTimeHeadwaySum()
UCLA: Return the time headway summation of all predecessors, in s.
std::vector< PlatoonMember > host_platoon_
std::string getHostStaticID() const
Returns current host static ID as a string.
void neighborMemberUpdates(const std::string &senderId, const std::string &platoonId, const std::string &params, const double &DtD, const double &CtD)
Update information for members of a neighboring platoon based on a mobility operation STATUS message.
double getCurrentPlatoonLength()
Returns overall length of the platoon. in m.
void clearActionPlan()
Resets necessary variables to indicate that the current ActionPlan is dead.
void changeFromFollowerToLeader()
Update status when state change from Follower to Leader.
void mob_op_cb_leaderaborting(const carma_v2x_msgs::msg::MobilityOperation &msg)
Function to process mobility operation in leaderaborting state.
carma_v2x_msgs::msg::LocationECEF pose_to_ecef(geometry_msgs::msg::PoseStamped pose_msg)
Function to convert pose from map frame to ecef location.
bool isJoiningVehicleNearPlatoon(double joining_downtrack, double joining_crosstrack)
The method for platoon leader to determine if the joining vehicle is closeby. (Note: Used when host v...
void mob_resp_cb_leadwithoperation(const carma_v2x_msgs::msg::MobilityResponse &msg)
Function to process mobility response in leadwithoperation state (cut-in join)
PlatoonStrategicIHPPlugin(carma_wm::WorldModelConstPtr wm, PlatoonPluginConfig config, MobilityResponseCB mobility_response_publisher, MobilityRequestCB mobility_request_publisher, MobilityOperationCB mobility_operation_publisher, PlatooningInfoCB platooning_info_publisher, std::shared_ptr< carma_ros2_utils::timers::TimerFactory > timer_factory)
Constructor.
MobilityRequestResponse mob_req_cb_preparetojoin(const carma_v2x_msgs::msg::MobilityRequest &msg)
Function to process mobility request in preparetojoin state (cut-in join)
MobilityRequestResponse mob_req_cb_leadwithoperation(const carma_v2x_msgs::msg::MobilityRequest &msg)
Function to process mobility request in leadwithoperation state (cut-in join)
void georeference_cb(const std_msgs::msg::String::UniquePtr msg)
Callback for the georeference.
bool isVehicleRightInFront(double downtrack, double crosstrack)
Function to determine if a target vehicle is in the front of host vehicle note: This is only applicab...
carma_v2x_msgs::msg::MobilityOperation composeMobilityOperationLeaderAborting()
Function to compose mobility operation in LeaderAborting state.
void mob_op_cb_candidateleader(const carma_v2x_msgs::msg::MobilityOperation &msg)
Function to process mobility operation in candidateleader state.
MobilityRequestResponse mob_req_cb_follower(const carma_v2x_msgs::msg::MobilityRequest &msg)
Function to process mobility request in follower state.
std::shared_ptr< carma_ros2_utils::timers::TimerFactory > timer_factory_
carma_v2x_msgs::msg::MobilityOperation composeMobilityOperationLeadWithOperation(const std::string &type)
Function to compose mobility operation in LeadWithOperation (cut-in join)
void setPMState(PlatoonState desiredState)
UCLA Setter: function to set pm_.platoon_state.
carma_v2x_msgs::msg::LocationECEF mob_op_find_ecef_from_STATUS_params(std::string strategyParams)
Function to process mobility operation for STATUS params.
carma_v2x_msgs::msg::MobilityOperation composeMobilityOperationFollower()
Function to compose mobility operation in follower state.
double mob_op_find_platoon_length_from_INFO_params(std::string strategyParams)
Function to process mobility operation INFO params to find platoon length in m.
carma_v2x_msgs::msg::MobilityOperation composeMobilityOperationPrepareToJoin()
Function to compose mobility operation in PrepareToJoin (cut-in join)
MobilityRequestResponse handle_mob_req(const carma_v2x_msgs::msg::MobilityRequest &msg)
Function to the process and respond to the mobility request.
MobilityRequestResponse mob_req_cb_leader(const carma_v2x_msgs::msg::MobilityRequest &msg)
Function to process mobility request in leader state.
void mob_req_cb(const carma_v2x_msgs::msg::MobilityRequest::UniquePtr msg)
Callback function for Mobility Request Message.
void mob_op_cb_leaderwaiting(const carma_v2x_msgs::msg::MobilityOperation &msg)
Function to process mobility operation in leader waiting state.
carma_v2x_msgs::msg::MobilityOperation composeMobilityOperationCandidateLeader()
Function to compose mobility operation in CandidateLeader.
void updateCurrentStatus(carma_planning_msgs::msg::Maneuver maneuver, double &speed, double &current_progress, int &lane_id)
Update maneuver status based on prior plan.
carma_v2x_msgs::msg::MobilityOperation composeMobilityOperationCandidateFollower()
Function to compose mobility operation in candidate follower state.
void mob_op_cb_leadwithoperation(const carma_v2x_msgs::msg::MobilityOperation &msg)
Function to process mobility operation in LeadWithOperation state (cut-in join)
carma_planning_msgs::msg::Maneuver composeManeuverMessage(double current_dist, double end_dist, double current_speed, double target_speed, int lane_id, rclcpp::Time &current_time)
Find lanelet index from path.
std::shared_ptr< lanelet::projection::LocalFrameProjector > map_projector_
carma_planning_msgs::msg::Plugin plugin_discovery_msg_
void mob_resp_cb_candidateleader(const carma_v2x_msgs::msg::MobilityResponse &msg)
Function to process mobility response in candidateleader state.
MobilityRequestResponse mob_req_cb_candidatefollower(const carma_v2x_msgs::msg::MobilityRequest &msg)
Function to process mobility request in candidate follower state.
double findSpeedLimit(const lanelet::ConstLanelet &llt)
Function to find speed limit of a lanelet.
void mob_op_cb_candidatefollower(const carma_v2x_msgs::msg::MobilityOperation &msg)
Function to process mobility operation in candidate follower state.
MobilityRequestResponse mob_req_cb_leaderwaiting(const carma_v2x_msgs::msg::MobilityRequest &msg)
Function to process mobility request in leader waiting state.
void run_candidate_follower()
Run Candidate Follower State.
void mob_resp_cb_leader(const carma_v2x_msgs::msg::MobilityResponse &msg)
Function to process mobility response in leader state.
void mob_resp_cb_follower(const carma_v2x_msgs::msg::MobilityResponse &msg)
Function to process mobility response in follower state.
carma_planning_msgs::msg::Maneuver composeLaneChangeManeuverMessage(double current_dist, double end_dist, double current_speed, double target_speed, int starting_lane_id, int ending_lane_id, rclcpp::Time &current_time)
Find start(current) and target(end) lanelet index from path to generate lane change maneuver message.
void setConfig(const PlatoonPluginConfig &config)
Set the current config.
MobilityRequestResponse mob_req_cb_standby(const carma_v2x_msgs::msg::MobilityRequest &msg)
Function to process mobility request in standby state.
lanelet::BasicPoint2d ecef_to_map_point(carma_v2x_msgs::msg::LocationECEF ecef_point)
Function to convert ecef location to a 2d point in map frame.
bool is_lanechange_possible(lanelet::Id start_lanelet_id, lanelet::Id target_lanelet_id)
Function to check if lanechange is possible.
void run_prepare_to_join()
UCLA Run prepare to join State.
void mob_resp_cb_preparetojoin(const carma_v2x_msgs::msg::MobilityResponse &msg)
Function to process mobility response in preparetojoin state.
MobilityRequestResponse mob_req_cb_candidateleader(const carma_v2x_msgs::msg::MobilityRequest &msg)
Function to process mobility request in candidateleader state.
carma_v2x_msgs::msg::MobilityOperation composeMobilityOperationLeaderWaiting()
Function to compose mobility operation in leader waiting state.
PlatoonManager getHostPM()
UCLA Getter: for PlatoonManager class.
void mob_op_cb(const carma_v2x_msgs::msg::MobilityOperation::UniquePtr msg)
Callback function for Mobility Operation Message.
bool isVehicleNearTargetPlatoon(double rearVehicleDtd, double frontVehicleDtd, double frontVehicleCtd)
Function to determine if the host vehicle is close to the target platoon (used for cut-in join scenar...
void cmd_cb(const geometry_msgs::msg::TwistStamped::UniquePtr msg)
Callback for the control command.
void pose_cb(const geometry_msgs::msg::PoseStamped::UniquePtr msg)
Callback function for current pose.
carma_v2x_msgs::msg::MobilityOperation composeMobilityOperationSTATUS()
Function to compose mobility operation message with STATUS params.
void mob_resp_cb(const carma_v2x_msgs::msg::MobilityResponse::UniquePtr msg)
Callback function for Mobility Response Message.
void mob_resp_cb_standby(const carma_v2x_msgs::msg::MobilityResponse &msg)
Function to process mobility response in standby state.
carma_v2x_msgs::msg::LocationECEF mob_op_find_ecef_from_INFO_params(std::string strategyParams)
Function to process mobility operation INFO params to find platoon leader's ecef location.
void setHostECEF(carma_v2x_msgs::msg::LocationECEF pose_ecef_point)
UCLA Update the private variable pose_ecef_point_.
int findLaneletIndexFromPath(int target_id, lanelet::routing::LaneletPath &path)
Find lanelet index from path.
carma_v2x_msgs::msg::MobilityOperation composeMobilityOperationINFO()
Function to compose mobility operation message with INFO params.
void run_lead_with_operation()
UCLA Run lead with operation State.
carma_planning_msgs::msg::PlatooningInfo composePlatoonInfoMsg()
Compose Platoon information message.
void mob_resp_cb_candidatefollower(const carma_v2x_msgs::msg::MobilityResponse &msg)
Function to process mobility response in candidate follower state.
carma_v2x_msgs::msg::LocationECEF pose_ecef_point_
double findLaneWidth()
Find lanelet width from local position.
void twist_cb(const geometry_msgs::msg::TwistStamped::UniquePtr msg)
Callback for the twist subscriber, which will store latest twist locally.
void mob_op_cb_standby(const carma_v2x_msgs::msg::MobilityOperation &msg)
Function to process mobility operation in standby state.
MobilityRequestResponse mob_req_cb_leaderaborting(const carma_v2x_msgs::msg::MobilityRequest &msg)
Function to process mobility request in leaderaborting state.
void mob_op_cb_follower(const carma_v2x_msgs::msg::MobilityOperation &msg)
Function to process mobility operation in follower state.
void mob_resp_cb_leaderwaiting(const carma_v2x_msgs::msg::MobilityResponse &msg)
Function to process mobility response in leader waiting state.
void updatePlatoonList(std::vector< PlatoonMember > platoon_list)
UCLA Setter: Update platoon list (Unit Test).
void mob_op_cb_STATUS(const carma_v2x_msgs::msg::MobilityOperation &msg)
Function to process mobility operation message with STATUS params, read ecef location and update plat...
void mob_resp_cb_leaderaborting(const carma_v2x_msgs::msg::MobilityResponse &msg)
Function to process mobility response in leaderaborting state.
bool plan_maneuver_cb(carma_planning_msgs::srv::PlanManeuvers::Request &req, carma_planning_msgs::srv::PlanManeuvers::Response &resp)
Callback function to the maneuver request.
bool isVehicleRightBehind(double downtrack, double crosstrack)
Function to determine if the given downtrack distance (m) is behind the host vehicle.
carma_v2x_msgs::msg::MobilityOperation composeMobilityOperationLeader(const std::string &type)
Function to compose mobility operation in leader state.
void mob_op_cb_leader(const carma_v2x_msgs::msg::MobilityOperation &msg)
Function to process mobility operation in leader state.
void mob_op_cb_preparetojoin(const carma_v2x_msgs::msg::MobilityOperation &msg)
Function to process mobility operation in PrepareToJoin state (cut-in join)
auto to_string(const UtmZone &zone) -> std::string
Definition: utm_zone.cpp:21
std::shared_ptr< const WorldModel > WorldModelConstPtr
Definition: WorldModel.hpp:452
PlatoonState
Platoon States. UCLA: Added additional states (i.e., LEADERABORTING and CANDIDATELEADER) for same-lan...
std::function< void(const carma_v2x_msgs::msg::MobilityOperation &)> MobilityOperationCB
std::function< void(const carma_planning_msgs::msg::PlatooningInfo &)> PlatooningInfoCB
std::function< void(const carma_v2x_msgs::msg::MobilityResponse &)> MobilityResponseCB
std::function< void(const carma_v2x_msgs::msg::MobilityRequest &)> MobilityRequestCB
MobilityRequestResponse
A response to an MobilityRequest message. ACK - indicates that the plugin accepts the MobilityRequest...
Struct for an action plan, which describes a transient joining activity.
Stuct containing the algorithm configuration values for the yield_pluginConfig.