Carma-platform v4.11.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.
stop_and_wait_plugin.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2019-2020 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#include <rclcpp/rclcpp.hpp>
18#include <string>
19#include <algorithm>
20#include <memory>
21#include <boost/uuid/uuid_generators.hpp>
22#include <boost/uuid/uuid_io.hpp>
23#include <lanelet2_core/geometry/Point.h>
24#include <trajectory_utils/trajectory_utils.hpp>
25#include <trajectory_utils/conversions/conversions.hpp>
26#include <sstream>
27#include <carma_ros2_utils/carma_lifecycle_node.hpp>
28#include <Eigen/Core>
29#include <Eigen/Geometry>
30#include <Eigen/LU>
31#include <Eigen/SVD>
32#include <unordered_set>
34#include <vector>
35#include <carma_planning_msgs/msg/stop_and_wait_maneuver.hpp>
36#include <carma_wm/Geometry.hpp>
37#include <carma_planning_msgs/msg/trajectory_plan_point.hpp>
38#include <carma_planning_msgs/msg/trajectory_plan.hpp>
39#include <lanelet2_core/primitives/Lanelet.h>
40#include <lanelet2_core/geometry/LineString.h>
42#include <math.h>
43#include <std_msgs/msg/float64.hpp>
45#include <math.h>
46
47using oss = std::ostringstream;
48
50{
51
52StopandWait::StopandWait(std::shared_ptr<carma_ros2_utils::CarmaLifecycleNode> nh,
54 const StopandWaitConfig& config,
55 const std::string& plugin_name,
56 const std::string& version_id)
57 : version_id_ (version_id),plugin_name_(plugin_name),config_(config),nh_(nh), wm_(wm)
58{
59 basic_autonomy::set_logger(nh_->get_logger().get_child("basic_autonomy"));
60};
61
62bool StopandWait::plan_trajectory_cb(carma_planning_msgs::srv::PlanTrajectory::Request::SharedPtr req, carma_planning_msgs::srv::PlanTrajectory::Response::SharedPtr resp)
63{
64 std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now(); // Start timing the execution time for planning so it can be logged
65 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("stop_and_wait_plugin"),"Starting stop&wait planning");
66
67 if (req->maneuver_index_to_plan >= req->maneuver_plan.maneuvers.size())
68 {
69 throw std::invalid_argument(
70 "StopAndWait plugin asked to plan invalid maneuver index: " + std::to_string(req->maneuver_index_to_plan) +
71 " for plan of size: " + std::to_string(req->maneuver_plan.maneuvers.size()));
72 }
73
74 if (req->maneuver_plan.maneuvers[req->maneuver_index_to_plan].type != carma_planning_msgs::msg::Maneuver::STOP_AND_WAIT)
75 {
76 throw std::invalid_argument("StopAndWait plugin asked to plan non STOP_AND_WAIT maneuver");
77 }
78
79 lanelet::BasicPoint2d veh_pos(req->vehicle_state.x_pos_global, req->vehicle_state.y_pos_global);
80
81 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("stop_and_wait_plugin"),"planning state x:" << req->vehicle_state.x_pos_global << ", y: " << req->vehicle_state.y_pos_global << ", speed: " << req->vehicle_state.longitudinal_vel);
82
83 if (req->vehicle_state.longitudinal_vel < epsilon_)
84 {
85 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("stop_and_wait_plugin"),"Detected that car is already stopped! Ignoring the request to plan Stop&Wait");
86
87 return true;
88 }
89
90 double current_downtrack = wm_->routeTrackPos(veh_pos).downtrack;
91
92 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("stop_and_wait_plugin"),"Current_downtrack" << current_downtrack);
93
94 if (req->maneuver_plan.maneuvers[req->maneuver_index_to_plan].stop_and_wait_maneuver.end_dist < current_downtrack)
95 {
96 throw std::invalid_argument("StopAndWait plugin asked to plan maneuver that ends earlier than the current state.");
97 }
98
99 resp->related_maneuvers.push_back(req->maneuver_index_to_plan);
100 resp->maneuver_status.push_back(carma_planning_msgs::srv::PlanTrajectory::Response::MANEUVER_IN_PROGRESS);
101
102 std::string maneuver_id = req->maneuver_plan.maneuvers[req->maneuver_index_to_plan].stop_and_wait_maneuver.parameters.maneuver_id;
103
104 RCLCPP_INFO_STREAM(rclcpp::get_logger("stop_and_wait_plugin"),"Maneuver not yet planned, planning new trajectory");
105
106 // Maneuver input is valid so continue with execution
107 std::vector<carma_planning_msgs::msg::Maneuver> maneuver_plan = { req->maneuver_plan.maneuvers[req->maneuver_index_to_plan] };
108
109 std::vector<PointSpeedPair> points_and_target_speeds = maneuvers_to_points(
110 maneuver_plan, wm_, req->vehicle_state); // Now have 1m downsampled points from cur to endpoint
111
112 // Trajectory plan
113 carma_planning_msgs::msg::TrajectoryPlan trajectory;
114 trajectory.header.frame_id = "map";
115 trajectory.header.stamp = req->header.stamp;
116 trajectory.trajectory_id = boost::uuids::to_string(boost::uuids::random_generator()());
117
118 // Extract the stopping buffer used to consider a stopping behavior complete
119 double stop_location_buffer = config_.default_stopping_buffer; // If no maneuver meta data is provided we will use the default buffer
120
121 double stopping_accel = 0.0;
122 if (maneuver_plan[0].stop_and_wait_maneuver.parameters.presence_vector &
123 carma_planning_msgs::msg::ManeuverParameters::HAS_FLOAT_META_DATA)
124 {
125 if(maneuver_plan[0].stop_and_wait_maneuver.parameters.float_valued_meta_data.size() < 2){
126 throw std::invalid_argument("stop and wait maneuver message missing required meta data");
127 }
128 stop_location_buffer = maneuver_plan[0].stop_and_wait_maneuver.parameters.float_valued_meta_data[0];
129 stopping_accel = maneuver_plan[0].stop_and_wait_maneuver.parameters.float_valued_meta_data[1];
130
131 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("stop_and_wait_plugin"),"Using stop buffer from meta data: " << stop_location_buffer);
132 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("stop_and_wait_plugin"),"Using stopping acceleration from meta data: "<< stopping_accel);
133 }
134 else{
135 throw std::invalid_argument("stop and wait maneuver message missing required float meta data");
136 }
137
138 double initial_speed = req->vehicle_state.longitudinal_vel; //will be modified after compose_trajectory_from_centerline
139
140 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("stop_and_wait_plugin"),"Original size: " << points_and_target_speeds.size());
141
142 trajectory.trajectory_points = compose_trajectory_from_centerline(
143 points_and_target_speeds, current_downtrack, req->vehicle_state.longitudinal_vel,
144 maneuver_plan[0].stop_and_wait_maneuver.end_dist, stop_location_buffer, req->header.stamp, stopping_accel, initial_speed);
145
146 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("stop_and_wait_plugin"),"Trajectory points size:" << trajectory.trajectory_points.size());
147
148 trajectory.initial_longitudinal_velocity = initial_speed;
149
150 resp->trajectory_plan = trajectory;
151
152 std::chrono::system_clock::time_point end_time = std::chrono::system_clock::now(); // Planning complete
153
154 auto duration = end_time - start_time;
155 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("stop_and_wait_plugin"), "ExecutionTime Stop&Wait Trajectory: " << std::chrono::duration<double>(duration).count());
156
157 return true;
158}
159
160// Returns the centerline points and speed limits for the provided maneuver
161std::vector<PointSpeedPair> StopandWait::maneuvers_to_points(const std::vector<carma_planning_msgs::msg::Maneuver>& maneuvers,
163 const carma_planning_msgs::msg::VehicleState& state)
164{
165 std::vector<PointSpeedPair> points_and_target_speeds;
166 std::unordered_set<lanelet::Id> visited_lanelets;
167
168 carma_planning_msgs::msg::StopAndWaitManeuver stop_and_wait_maneuver = maneuvers[0].stop_and_wait_maneuver;
169
170 lanelet::BasicPoint2d veh_pos(state.x_pos_global, state.y_pos_global);
171 double starting_downtrack = wm_->routeTrackPos(veh_pos).downtrack; // The vehicle position
172 double starting_speed = state.longitudinal_vel;
173
174 // Sample the lanelet centerline at fixed increments.
175 // std::min call here is a guard against starting_downtrack being within 1m of the maneuver end_dist
176 // in this case the sampleRoutePoints method will return a single point allowing execution to continue
177 std::vector<lanelet::BasicPoint2d> route_points = wm->sampleRoutePoints(
178 std::min(starting_downtrack + config_.centerline_sampling_spacing, stop_and_wait_maneuver.end_dist),
179 stop_and_wait_maneuver.end_dist, config_.centerline_sampling_spacing);
180
181
182 route_points.insert(route_points.begin(), veh_pos);
183
184
185 for (const auto& p : route_points)
186 {
187 PointSpeedPair pair;
188 pair.point = p;
189 pair.speed = starting_speed; // NOTE: Since the vehicle is trying to stop the assumption made is that the speed limit is irrelevant.
190 points_and_target_speeds.push_back(pair);
191 }
192
193 return points_and_target_speeds;
194}
195
196std::vector<carma_planning_msgs::msg::TrajectoryPlanPoint> StopandWait::trajectory_from_points_times_orientations(
197 const std::vector<lanelet::BasicPoint2d>& points, const std::vector<double>& times, const std::vector<double>& yaws,
198 rclcpp::Time startTime)
199{
200 if (points.size() != times.size() || points.size() != yaws.size())
201 {
202 throw std::invalid_argument("All input vectors must have the same size");
203 }
204
205 std::vector<carma_planning_msgs::msg::TrajectoryPlanPoint> traj;
206 traj.reserve(points.size());
207
208 for (size_t i = 0; i < points.size(); i++)
209 {
210 carma_planning_msgs::msg::TrajectoryPlanPoint tpp;
211 rclcpp::Duration relative_time = rclcpp::Duration::from_nanoseconds(times[i] * 1e9);
212 tpp.target_time = startTime + relative_time;
213 tpp.x = points[i].x();
214 tpp.y = points[i].y();
215 tpp.yaw = yaws[i];
216 tpp.planner_plugin_name = plugin_name_;
217 tpp.controller_plugin_name = "default";
218
219 traj.push_back(tpp);
220 }
221
222 return traj;
223}
224
225std::vector<carma_planning_msgs::msg::TrajectoryPlanPoint> StopandWait::compose_trajectory_from_centerline(
226 const std::vector<PointSpeedPair>& points, double starting_downtrack, double starting_speed, double stop_location,
227 double stop_location_buffer, rclcpp::Time start_time, double stopping_acceleration, double& initial_speed)
228{
229 std::vector<carma_planning_msgs::msg::TrajectoryPlanPoint> plan;
230 if (points.size() == 0)
231 {
232 RCLCPP_WARN_STREAM(rclcpp::get_logger("stop_and_wait_plugin"),"No points to use as trajectory in stop and wait plugin");
233 return plan;
234 }
235
236 std::vector<PointSpeedPair> final_points;
237
238 double half_stopping_buffer = stop_location_buffer * 0.5;
239 double remaining_distance = stop_location - half_stopping_buffer - starting_downtrack; // Target to stop in the middle of the buffer
240
241 double target_accel = stopping_acceleration * config_.accel_limit_multiplier;
242 double req_dist = (starting_speed * starting_speed) /
243 (2.0 * target_accel); // Distance needed to go from current speed to 0 at target accel
244
245 // In cases where the vehicle is not able to stop before the half_stopping_buffer the remaining distance becomes negative
246 // which will cause the target accel in this loop to be negative and make the vehicle speed up
247 while (remaining_distance <= 0.0)
248 {
249 if(remaining_distance <= (stop_location - starting_downtrack))
250 {
251 //Add additional distance to remaining to allow vehicle to stop within buffer
252 remaining_distance += 0.2;
253 }
254 else{
255 break;
256 }
257 }
258
259 if (req_dist > remaining_distance)
260 {
261
262 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("stop_and_wait_plugin"),"Target Accel Update From: " << target_accel);
263 target_accel =
264 (starting_speed * starting_speed) / (2.0 * remaining_distance); // If we cannot reach the end point it the
265 // required distance update the accel target
266 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("stop_and_wait_plugin"),"Target Accel Update To: " << target_accel);
267 }
268
269
270 std::vector<double> inverse_downtracks; // Store downtracks in reverse
271 inverse_downtracks.reserve(points.size());
272 final_points.reserve(points.size());
273
274 PointSpeedPair prev_pair = points.back();
275 prev_pair.speed = 0.0;
276 final_points.push_back(prev_pair); // Store the points in reverse
277 inverse_downtracks.push_back(0);
278
279 bool reached_end = false;
280 for (int i = points.size() - 2; i >= 0; i--)
281 { // NOTE: Do not use size_t for i type here as -- with > 0 will result in overflow
282
283 double v_i = prev_pair.speed;
284 double dx = lanelet::geometry::distance2d(prev_pair.point, points[i].point);
285 double new_downtrack = inverse_downtracks.back() + dx;
286
287 if (new_downtrack < half_stopping_buffer) { // Points after (when viewed not in reverse) the midpoint of the buffer should be 0 speed
288 PointSpeedPair pair = points[i];
289 pair.speed = 0;
290 final_points.push_back(pair); // Store the points in reverse
291 inverse_downtracks.push_back(new_downtrack);
292 prev_pair = pair;
293 continue;
294 }
295
296 if (reached_end || v_i >= starting_speed)
297 { // We are walking backward, so if the prev speed is greater than or equal to the starting speed then we are done
298 // backtracking
299 reached_end = true;
300 PointSpeedPair pair = points[i];
301 pair.speed = starting_speed;
302 final_points.push_back(pair); // Store the points in reverse
303 inverse_downtracks.push_back(new_downtrack);
304 prev_pair = pair;
305 continue; // continue until loop end
306 }
307
308 double v_f = sqrt(v_i * v_i + 2 * target_accel * dx);
309
310 PointSpeedPair pair = points[i];
311 pair.speed = std::min(v_f, starting_speed);
312 final_points.push_back(pair); // Store the points in reverse
313 inverse_downtracks.push_back(new_downtrack);
314
315 prev_pair = pair;
316 }
317
318 // Now we have a trajectory that decelerates from our end point to somewhere in the maneuver
319 std::reverse(final_points.begin(),
320 final_points.end());
321
322 std::vector<double> speeds;
323 std::vector<lanelet::BasicPoint2d> raw_points;
324 splitPointSpeedPairs(final_points, &raw_points, &speeds);
325
326 // Convert the inverted downtracks back to regular downtracks
327 double max_downtrack = inverse_downtracks.back();
328 std::vector<double> downtracks = lanelet::utils::transform(inverse_downtracks, [max_downtrack](const auto& d) { return max_downtrack - d; });
329 std::reverse(downtracks.begin(),
330 downtracks.end());
331
332 bool in_range = false;
333 double stopped_downtrack = 0;
334 lanelet::BasicPoint2d stopped_point;
335
336 bool vehicle_in_buffer = downtracks.back() < stop_location_buffer;
337
338 std::vector<double> filtered_speeds = basic_autonomy::smoothing::moving_average_filter(speeds, config_.moving_average_window_size);
339
340 for (size_t i = 0; i < filtered_speeds.size(); i++)
341 { // Apply minimum speed constraint
342 double downtrack = downtracks[i];
343
344 constexpr double one_mph_in_mps = 0.44704;
345
346 if (downtracks.back() - downtrack < stop_location_buffer && filtered_speeds[i] < config_.crawl_speed + one_mph_in_mps)
347 { // if we are within the stopping buffer and going at near crawl speed then command stop
348
349 // To avoid any issues in control plugin behavior we only command 0 if the vehicle is inside the buffer
350 if (vehicle_in_buffer || (i == filtered_speeds.size() - 1)) { // Vehicle is in the buffer
351 filtered_speeds[i] = 0.0;
352 } else { // Vehicle is not in the buffer so fill buffer with crawl speed
353 filtered_speeds[i] = std::max(filtered_speeds[i], config_.crawl_speed);
354 }
355
356 }
357 else
358 {
359 filtered_speeds[i] = std::max(filtered_speeds[i], config_.crawl_speed);
360 }
361 }
362
363 std::vector<double> times;
364 trajectory_utils::conversions::speed_to_time(downtracks, filtered_speeds, &times);
365
366 for (size_t i = 0; i < times.size(); i++)
367 {
368 if (times[i] != 0 && !std::isnormal(times[i]) && i != 0)
369 { // If the time
370 RCLCPP_WARN_STREAM_THROTTLE(rclcpp::get_logger("stop_and_wait_plugin"),
371 *nh_->get_clock(), 1000,
372 "(Throttled Log 1s) Detected non-normal (nan, inf, etc.) time. Making it same as before: "
373 << times[i-1]);
374 // NOTE: overriding the timestamps in assumption that pure_pursuit_wrapper will detect it as stopping case
375 times[i] = times[i - 1];
376 }
377 }
378
379 std::vector<double> yaws = carma_wm::geometry::compute_tangent_orientations(raw_points);
380
381 for (size_t i = 0; i < points.size(); i++)
382 {
383 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("stop_and_wait_plugin"),"1d: " << downtracks[i] << " t: " << times[i] << " v: " << filtered_speeds[i]);
384 }
385
386 auto traj = trajectory_from_points_times_orientations(raw_points, times, yaws, start_time);
387
388 while (rclcpp::Time(traj.back().target_time) - rclcpp::Time(traj.front().target_time) < rclcpp::Duration::from_nanoseconds(config_.minimal_trajectory_duration * 1e9))
389 {
390 carma_planning_msgs::msg::TrajectoryPlanPoint new_point = traj.back();
391 new_point.target_time = rclcpp::Time(new_point.target_time) + rclcpp::Duration::from_nanoseconds(config_.stop_timestep * 1e9);
392 new_point.planner_plugin_name = plugin_name_;
393 traj.push_back(new_point);
394 }
395
396 if (!filtered_speeds.empty())
397 initial_speed = filtered_speeds.front(); //modify initial_speed variable passed by reference
398
399 return traj;
400}
401
402void StopandWait::splitPointSpeedPairs(const std::vector<PointSpeedPair>& points,
403 std::vector<lanelet::BasicPoint2d>* basic_points,
404 std::vector<double>* speeds) const
405{
406 basic_points->reserve(points.size());
407 speeds->reserve(points.size());
408
409 for (const auto& p : points)
410 {
411 basic_points->push_back(p.point);
412 speeds->push_back(p.speed);
413 }
414}
415
416} // namespace stop_and_wait_plugin
carma_wm::WorldModelConstPtr wm_
void splitPointSpeedPairs(const std::vector< PointSpeedPair > &points, std::vector< lanelet::BasicPoint2d > *basic_points, std::vector< double > *speeds) const
Helper method to split a list of PointSpeedPair into separate point and speed lists.
std::vector< PointSpeedPair > maneuvers_to_points(const std::vector< carma_planning_msgs::msg::Maneuver > &maneuvers, const carma_wm::WorldModelConstPtr &wm, const carma_planning_msgs::msg::VehicleState &state)
Converts a set of requested STOP_AND_WAIT maneuvers to point speed limit pairs.
bool plan_trajectory_cb(carma_planning_msgs::srv::PlanTrajectory::Request::SharedPtr req, carma_planning_msgs::srv::PlanTrajectory::Response::SharedPtr resp)
Service callback for trajectory planning.
std::vector< carma_planning_msgs::msg::TrajectoryPlanPoint > compose_trajectory_from_centerline(const std::vector< PointSpeedPair > &points, double starting_downtrack, double starting_speed, double stop_location, double stop_location_buffer, rclcpp::Time start_time, double stopping_acceleration, double &initial_speed)
Method converts a list of lanelet centerline points and current vehicle state into a usable list of t...
StopandWait(std::shared_ptr< carma_ros2_utils::CarmaLifecycleNode > nh, carma_wm::WorldModelConstPtr wm, const StopandWaitConfig &config, const std::string &plugin_name, const std::string &version_id)
Constructor.
std::shared_ptr< carma_ros2_utils::CarmaLifecycleNode > nh_
std::vector< carma_planning_msgs::msg::TrajectoryPlanPoint > trajectory_from_points_times_orientations(const std::vector< lanelet::BasicPoint2d > &points, const std::vector< double > &times, const std::vector< double > &yaws, rclcpp::Time startTime)
std::ostringstream oss
std::vector< double > moving_average_filter(const std::vector< double > input, int window_size, bool ignore_first_point=true)
Extremely simplie moving average filter.
Definition: filters.cpp:24
void set_logger(rclcpp::Logger logger)
Replace the module-level logger used by all basic_autonomy functions.
Definition: log.cpp:33
rclcpp::Logger get_logger()
Return the module-level logger used by all basic_autonomy functions.
Definition: log.cpp:32
auto to_string(const UtmZone &zone) -> std::string
Definition: utm_zone.cpp:21
std::vector< double > compute_tangent_orientations(const lanelet::BasicLineString2d &centerline)
Compute an approximate orientation for the vehicle at each point along the provided centerline.
Definition: Geometry.cpp:565
std::shared_ptr< const WorldModel > WorldModelConstPtr
Definition: WorldModel.hpp:454
string version_id
Stuct containing the algorithm configuration values for the stop_and_wait_plugin.
Convenience class for pairing 2d points with speeds.