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.
port_drayage_worker.cpp
Go to the documentation of this file.
1 /*
2 * Copyright (C) 2020-2022 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
18
19namespace port_drayage_plugin
20{
22 return operation_enum_;
23 }
24
25 std::string OperationID::operationToString() const {
26
27 // Convert operation enum into a human-readable string
28 switch(operation_enum_) {
29 case Operation::PICKUP: return "PICKUP";
30 case Operation::DROPOFF: return "DROPOFF";
31 case Operation::ENTER_STAGING_AREA: return "ENTER_STAGING_AREA";
32 case Operation::EXIT_STAGING_AREA: return "EXIT_STAGING_AREA";
33 case Operation::ENTER_PORT: return "ENTER_PORT";
34 case Operation::EXIT_PORT: return "EXIT_PORT";
35 case Operation::PORT_CHECKPOINT: return "PORT_CHECKPOINT";
36 case Operation::HOLDING_AREA: return "HOLDING_AREA";
37 default:
38 RCLCPP_WARN_STREAM(rclcpp::get_logger("OperationID"), "Conversion of an unsupported operation enum value to a string.");
39 return "UNSUPPORTED_OPERATION_ID";
40 }
41 }
42
43 PortDrayageWorker::PortDrayageWorker(rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr logger,
44 rclcpp::Clock::SharedPtr clock,
45 std::function<void(carma_v2x_msgs::msg::MobilityOperation)> mobility_operations_publisher,
46 std::function<void(carma_msgs::msg::UIInstructions)> ui_instructions_publisher,
47 std::function<bool(std::shared_ptr<carma_planning_msgs::srv::SetActiveRoute::Request>)> call_set_active_route)
48 : logger_(logger), clock_(clock),
49 publish_mobility_operation_(mobility_operations_publisher),
50 publish_ui_instructions_(ui_instructions_publisher),
51 call_set_active_route_service_(call_set_active_route),
52 pdsm_(logger)
53 {
56 }
57
58 void PortDrayageWorker::setVehicleID(const std::string& cmv_id) {
59 cmv_id_ = cmv_id;
60 }
61
62 void PortDrayageWorker::setCargoID(const std::string& cargo_id) {
63 cargo_id_ = cargo_id;
64 }
65
66 void PortDrayageWorker::setEnablePortDrayageFlag(bool enable_port_drayage) {
67 enable_port_drayage_ = enable_port_drayage;
68 }
69
70 void PortDrayageWorker::setStartingAtStagingAreaFlag(bool starting_at_staging_area) {
71 starting_at_staging_area_ = starting_at_staging_area;
72 }
73
75 carma_v2x_msgs::msg::MobilityOperation msg = composeArrivalMessage();
77 }
78
80 // Populate the service request with the destination coordinates from the last received port drayage mobility operation message
82
83 // Call service client to set the new active route
84 bool is_route_generation_successful = call_set_active_route_service_(route_req);
85
86 if (is_route_generation_successful) {
87 // Publish UI Instructions to trigger a pop-up on the Web UI for the user to engage on the newly received route if desired
88 carma_msgs::msg::UIInstructions ui_instructions_msg = composeUIInstructions(latest_mobility_operation_msg_.operation, previously_completed_operation_);
89 publish_ui_instructions_(ui_instructions_msg);
90 }
91 else {
92 // Throw exception if route generation was not successful
93 RCLCPP_DEBUG_STREAM(logger_->get_logger(), "Route generation failed. Routing could not be completed.");
94 throw std::invalid_argument("Route generation failed. Routing could not be completed.");
95 }
96 }
97
98 std::shared_ptr<carma_planning_msgs::srv::SetActiveRoute::Request> PortDrayageWorker::composeSetActiveRouteRequest(boost::optional<double> dest_latitude, boost::optional<double> dest_longitude) const {
99 auto route_req = std::make_shared<carma_planning_msgs::srv::SetActiveRoute::Request>();
100 if (dest_latitude && dest_longitude) {
101 route_req->choice = carma_planning_msgs::srv::SetActiveRoute::Request::DESTINATION_POINTS_ARRAY;
102 route_req->route_id = latest_mobility_operation_msg_.operation;
103
104 carma_v2x_msgs::msg::Position3D destination_point;
105 destination_point.latitude = *latest_mobility_operation_msg_.dest_latitude;
106 destination_point.longitude = *latest_mobility_operation_msg_.dest_longitude;
107 destination_point.elevation_exists = false;
108
109 route_req->destination_points.push_back(destination_point);
110 }
111 else {
112 RCLCPP_DEBUG_STREAM(logger_->get_logger(), "No destination points were received. Routing could not be completed.");
113 throw std::invalid_argument("No destination points were received. Routing could not be completed");
114 }
115
116 return route_req;
117 }
118
119 carma_msgs::msg::UIInstructions PortDrayageWorker::composeUIInstructions(const std::string& current_operation, const std::string& previous_operation) {
120 // Create the text that will be displayed in the Web UI popup
121 std::string popup_text = "";
122
123 // Add text that indicates the previous action was completed (if it was a pickup or dropoff action)
124 if (previous_operation == OperationID::PICKUP) {
125 popup_text += "The pickup action was completed successfully. ";
126 }
127 else if (previous_operation == OperationID::DROPOFF) {
128 popup_text += "The dropoff action was completed successfully. ";
129 }
130 else if (previous_operation == OperationID::HOLDING_AREA) {
131 popup_text += "The inspection was completed successfully. ";
132 }
133
134 // Add text to notify the user that the system can be engaged on the newly received route
135 popup_text += "A new Port Drayage route with operation type '" + current_operation + "' has been received. "
136 "Select YES to engage the system on the route, or select NO to remain "
137 "disengaged.";
138
139 // Create and populate the UI Instructions message
140 carma_msgs::msg::UIInstructions ui_instructions_msg;
141 ui_instructions_msg.stamp = clock_->now();
142 ui_instructions_msg.msg = popup_text;
143 ui_instructions_msg.type = carma_msgs::msg::UIInstructions::ACK_REQUIRED; // The popup will be displayed until the user interacts with it
144 ui_instructions_msg.response_service = SET_GUIDANCE_ACTIVE_SERVICE_ID;
145
146 return ui_instructions_msg;
147 }
148
149 carma_v2x_msgs::msg::MobilityOperation PortDrayageWorker::composeArrivalMessage() const {
150 carma_v2x_msgs::msg::MobilityOperation msg;
151
152 msg.m_header.plan_id = "";
153 msg.m_header.sender_id = cmv_id_;
154 msg.m_header.recipient_id = "";
155 msg.m_header.timestamp = clock_->now().nanoseconds()/1000000;
156
157 msg.strategy = PORT_DRAYAGE_STRATEGY_ID;
158
159 // Encode JSON with Boost Property Tree
160 using boost::property_tree::ptree;
161 ptree pt;
162 pt.put("cmv_id", cmv_id_);
163
164 // Add current vehicle location (latitude and longitude)
165 ptree location;
166 location.put("latitude", current_gps_position_.latitude);
167 location.put("longitude", current_gps_position_.longitude);
168 pt.put_child("location", location);
169
170 // Add flag to indicate whether CMV is carring cargo
171 pt.put("cargo", cargo_id_ != "");
172
173 // If CMV has arrived at its initial destination, assign 'operation' field for its initial arrival message
175 // The CMV's initial destination is either the Staging Area Entrance or the Port Entrance
178 pt.put("operation", pickup_operation);
179 }
180 else {
181 OperationID enter_port_operation = OperationID::ENTER_PORT;
182 pt.put("operation", enter_port_operation);
183 }
184
185 // Add cargo_id if CMV is carrying cargo
186 if (cargo_id_ != "") {
187 pt.put("cargo_id", cargo_id_);
188 }
189 }
190 // If CMV has arrived at a received destination, add necessary fields based on the destination type that it has arrived at
192 // Assign the 'operation' using the 'operation' from the last received port drayage message
193 pt.put("operation", latest_mobility_operation_msg_.operation);
194
195 // Assign the 'action_id' using the 'action_id' from the last received port drayage message
198 }
199 else {
200 RCLCPP_WARN_STREAM(logger_->get_logger(), "CMV has arrived at a received destination, but does not have an action_id to broadcast.");
201 }
202
203 // Assign specific fields for arrival at a Pickup location
205 // Assign 'cargo_id' using the value received in the previous port drayage message
207 pt.put("cargo_id", *latest_mobility_operation_msg_.cargo_id);
208 }
209 else {
210 RCLCPP_WARN_STREAM(logger_->get_logger(), "CMV has arrived at loading area, but does not have a cargo_id to broadcast.");
211 }
212
213 if (cargo_id_ != "") {
214 RCLCPP_WARN_STREAM(logger_->get_logger(), "CMV has arrived at a loading area, but it is already carrying cargo.");
215 }
216 }
217 // Assign specific fields for arrival at a Dropoff location
219 // Assign 'cargo_id' using the ID of the cargo currently being carried
220 if (cargo_id_ != "") {
221 pt.put("cargo_id", cargo_id_);
222 }
223 else {
224 RCLCPP_WARN_STREAM(logger_->get_logger(), "CMV has arrived at a dropoff area, but does not have a cargo_id to broadcast.");
225 }
226 }
227 // Assign 'cargo_id' using the ID of the cargo currently being carried if the CMV is not arriving at a Pickup location
228 else {
229 if (cargo_id_ != "") {
230 pt.put("cargo_id", cargo_id_);
231 }
232 }
233 }
234
235 std::stringstream body_stream;
236 boost::property_tree::json_parser::write_json(body_stream, pt);
237 msg.strategy_params = body_stream.str();
238
239 return msg;
240 }
241
242 void PortDrayageWorker::onInboundMobilityOperation(carma_v2x_msgs::msg::MobilityOperation::UniquePtr msg) {
243 // Check if the received message is a new message for port drayage
244 if((msg->strategy == PORT_DRAYAGE_STRATEGY_ID) && (msg->strategy_params != previous_strategy_params_)) {
245 // Use Boost Property Tree to parse JSON-encoded strategy_params field in MobilityOperations message
246 using boost::property_tree::ptree;
247 ptree pt;
248 std::istringstream strategy_params_ss(msg->strategy_params);
249 boost::property_tree::json_parser::read_json(strategy_params_ss, pt);
250
251 std::string mobility_operation_cmv_id = pt.get<std::string>("cmv_id");
252
253 // Check if the received MobilityOperation message is intended for this vehicle's cmv_id
254 if(mobility_operation_cmv_id == cmv_id_) {
255 // Since a new message indicates the previous action was completed, update all cargo-related data members based on the previous action that was completed
257
258 // Store the previously received operation
260
261 RCLCPP_DEBUG_STREAM(logger_->get_logger(), "Processing new port drayage MobilityOperation message for cmv_id " << mobility_operation_cmv_id);
262 mobilityOperationMessageParser(msg->strategy_params);
263 previous_strategy_params_ = msg->strategy_params;
264
266 }
267 else {
268 RCLCPP_DEBUG_STREAM(logger_->get_logger(), "Ignoring received port drayage MobilityOperation message intended for cmv_id " << mobility_operation_cmv_id);
269 }
270 }
271 }
272
274 // If the previously received message was for 'Pickup' or 'Dropoff', update this object's cargo_id_ member accordingly
275 // Note: This assumes the previous 'Pickup' or 'Dropoff' action was successful
276 if (previous_port_drayage_msg.operation == OperationID::PICKUP) {
277
278 if (previous_port_drayage_msg.cargo_id) {
279 cargo_id_ = *previous_port_drayage_msg.cargo_id;
280 RCLCPP_DEBUG_STREAM(logger_->get_logger(), "CMV completed pickup action. CMV is now carrying cargo ID " << cargo_id_);
281 }
282 else {
283 RCLCPP_DEBUG_STREAM(logger_->get_logger(), "CMV has completed pickup, but there is no Cargo ID associated with the picked up cargo.");
284 }
285 }
286 else if (previous_port_drayage_msg.operation == OperationID::DROPOFF) {
287 RCLCPP_DEBUG_STREAM(logger_->get_logger(), "CMV completed dropoff action. CMV is no longer carrying cargo ID " << cargo_id_);
288 cargo_id_ = ""; // Empty string is used when no cargo is being carried
289 }
290 }
291
292 void PortDrayageWorker::mobilityOperationMessageParser(std::string mobility_operation_strategy_params) {
293 // Use Boost Property Tree to parse JSON-encoded strategy_params field in MobilityOperations message
294 using boost::property_tree::ptree;
295 ptree pt;
296 std::istringstream mobility_operation_strategy_params_ss(mobility_operation_strategy_params);
297 boost::property_tree::json_parser::read_json(mobility_operation_strategy_params_ss, pt);
298
299 // Parse 'operation' field and assign the PortDrayageEvent type for this message accordingly
300 latest_mobility_operation_msg_.operation = pt.get<std::string>("operation");
301 RCLCPP_DEBUG_STREAM(logger_->get_logger(), "operation: " << latest_mobility_operation_msg_.operation);
302
303 // If this CMV is commanded to pickup new cargo, check that it isn't already carrying cargo
305 RCLCPP_WARN_STREAM(logger_->get_logger(), "Received 'PICKUP' operation, but CMV is already carrying cargo.");
306 }
307
308 // If this CMV is commanded to dropoff cargo, check that it is actually carrying cargo
310 RCLCPP_WARN_STREAM(logger_->get_logger(), "Received 'DROPOFF' operation, but CMV isn't currently carrying cargo.");
311 }
312
313 // Parse 'cargo_id' field if it exists in strategy_params
314 if (pt.count("cargo_id") != 0){
315 latest_mobility_operation_msg_.cargo_id = pt.get<std::string>("cargo_id");
316 RCLCPP_DEBUG_STREAM(logger_->get_logger(), "cargo id: " << *latest_mobility_operation_msg_.cargo_id);
317
318 // Log message if the cargo ID being dropped off does not match the cargo ID currently being carried
319 if (latest_mobility_operation_msg_.operation == OperationID::DROPOFF && cargo_id_ != pt.get<std::string>("cargo_id")) {
320 RCLCPP_WARN_STREAM(logger_->get_logger(), "CMV commanded to dropoff an invalid Cargo ID. Currently carrying " << cargo_id_ << ", commanded to dropoff " << pt.get<std::string>("cargo_id"));
321 }
322 }
323 else{
324 // If this message is for 'PICKUP', then the 'cargo_id' field is required
326 RCLCPP_WARN_STREAM(logger_->get_logger(), "Received 'PICKUP' operation, but no cargo_id was included.");
327 }
329 RCLCPP_WARN_STREAM(logger_->get_logger(), "Received 'DROPOFF' operation, but no cargo_id was included.");
330 }
331
332 latest_mobility_operation_msg_.cargo_id = boost::optional<std::string>();
333 }
334
335 // Parse 'action_id' field if it exists in strategy_params
336 if (pt.count("action_id") != 0){
337 latest_mobility_operation_msg_.current_action_id = pt.get<std::string>("action_id");
338 RCLCPP_DEBUG_STREAM(logger_->get_logger(), "action id: " << *latest_mobility_operation_msg_.current_action_id);
339 }
340 else{
341 latest_mobility_operation_msg_.current_action_id = boost::optional<std::string>();
342 }
343
344 // Parse starting longitude/latitude fields if 'location' field exists in strategy_params:
345 if (pt.count("location") != 0){
346 latest_mobility_operation_msg_.start_longitude = pt.get<double>("location.longitude");
347 latest_mobility_operation_msg_.start_latitude = pt.get<double>("location.latitude");
348 RCLCPP_DEBUG_STREAM(logger_->get_logger(), "start long: " << *latest_mobility_operation_msg_.start_longitude);
349 RCLCPP_DEBUG_STREAM(logger_->get_logger(), "start lat: " << *latest_mobility_operation_msg_.start_latitude);
350 }
351 else {
352 latest_mobility_operation_msg_.start_longitude = boost::optional<double>();
353 latest_mobility_operation_msg_.start_latitude = boost::optional<double>();
354 }
355
356 // Parse destination longitude/latitude fields if 'destination' field exists in strategy_params:
357 if (pt.count("destination") != 0) {
358 latest_mobility_operation_msg_.dest_longitude = pt.get<double>("destination.longitude");
359 latest_mobility_operation_msg_.dest_latitude = pt.get<double>("destination.latitude");
360 RCLCPP_DEBUG_STREAM(logger_->get_logger(), "dest long: " << *latest_mobility_operation_msg_.dest_longitude);
361 RCLCPP_DEBUG_STREAM(logger_->get_logger(), "dest lat: " << *latest_mobility_operation_msg_.dest_latitude);
362 }
363 else {
364 latest_mobility_operation_msg_.dest_longitude = boost::optional<double>();
365 latest_mobility_operation_msg_.dest_latitude = boost::optional<double>();
366 }
367 }
368
369 void PortDrayageWorker::onGuidanceState(carma_planning_msgs::msg::GuidanceState::UniquePtr msg) {
370 // Drayage operations have started when the CMV has been engaged for the first time
372 RCLCPP_DEBUG_STREAM(logger_->get_logger(), "CMV has been engaged for the first time. Processing DRAYAGE_START event.");
374 }
375 }
376
377 void PortDrayageWorker::onRouteEvent(carma_planning_msgs::msg::RouteEvent::UniquePtr msg) {
378 // CMV has officially arrived at its destination if the previous route was completed and is no longer active
379 if (latest_route_event_ != nullptr) {
380 if (latest_route_event_->event == carma_planning_msgs::msg::RouteEvent::ROUTE_COMPLETED && msg->event == carma_planning_msgs::msg::RouteEvent::ROUTE_LOADED) {
382 RCLCPP_DEBUG_STREAM(logger_->get_logger(), "CMV completed its previous route, and the previous route is no longer active.");
383 RCLCPP_DEBUG_STREAM(logger_->get_logger(), "Processing ARRIVED_AT_DESTINATION event.");
385 }
386 }
387 }
388
389 // Update the latest received route event data member
390 latest_route_event_ = std::move(msg);
391 }
392
394 return pdsm_.getState();
395 }
396
397 void PortDrayageWorker::onNewPose(geometry_msgs::msg::PoseStamped::UniquePtr msg) {
398 if (!map_projector_) {
399 RCLCPP_DEBUG_STREAM(logger_->get_logger(), "Ignoring pose message as projection string has not been defined");
400 return;
401 }
402
403 // Convert pose message contents to a GPS coordinate
404 lanelet::GPSPoint coord = map_projector_->reverse( { msg->pose.position.x, msg->pose.position.y, msg->pose.position.z } );
405
406 // Update the locally-stored GPS position of the CMV
409 }
410
411 void PortDrayageWorker::onNewGeoreference(std_msgs::msg::String::UniquePtr msg) {
412 // Build projector from proj string
413 if (georeference_ != msg->data)
414 {
415 georeference_ = msg->data;
416 map_projector_ = std::make_shared<lanelet::projection::LocalFrameProjector>(msg->data.c_str());
417 }
418 }
419
420
421} // namespace port_drayage_plugin
Helper class containing an enum for valid port drayage MobilityOperation message operation IDs and a ...
OperationID::Operation getOperationID() const
Getter function to obtain the Operation enum associated with this object.
Operation
Enum containing possible operation IDs used to define destinations for port drayage.
std::string operationToString() const
Function to convert this object's 'operation_enum_' to a human-readable string.
void setOnArrivedAtDestinationCallback(const std::function< void()> &cb)
Set the callback to be invoked upon transitioning into the AWAITING_DESTINATION state.
void setOnReceivedNewDestinationCallback(const std::function< void()> &cb)
Set the callback to be invoked upon transitioning into the EN_ROUTE state.
PortDrayageState getState() const
Get the current state of the state machine.
void setVehicleID(const std::string &cmv_id)
Setter function to set this object's cmv_id_ string.
carma_msgs::msg::UIInstructions composeUIInstructions(const std::string &current_operation, const std::string &previous_operation)
Creates a UIInstructions message that can be used to create a pop-up on the Web UI to notify a user t...
PortDrayageWorker(rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr logger, rclcpp::Clock::SharedPtr clock, std::function< void(carma_v2x_msgs::msg::MobilityOperation)> mobility_operations_publisher, std::function< void(carma_msgs::msg::UIInstructions)> ui_instructions_publisher, std::function< bool(std::shared_ptr< carma_planning_msgs::srv::SetActiveRoute::Request >)> call_set_active_route)
Standard constructor for the PortDrayageWorker.
void setStartingAtStagingAreaFlag(bool starting_at_staging_area)
Setter function to set this object's starting_at_staging_area flag.
PortDrayageState getPortDrayageState()
Get the current state of the port drayage state machine.
void onRouteEvent(const carma_planning_msgs::msg::RouteEvent::UniquePtr msg)
Callback to process each Route Event.
PortDrayageMobilityOperationMsg latest_mobility_operation_msg_
void updateCargoInformationAfterActionCompletion(const PortDrayageMobilityOperationMsg &previous_port_drayage_msg)
Method to update worker's cargo-related data members depending on whether the previously completed ac...
void onArrivedAtDestination()
Callback for usage by the PortDrayageStateMachine when the vehicle has arrived at a destination.
void setEnablePortDrayageFlag(bool enable_port_drayage)
Setter function to set this object's enable_port_drayage flag.
std::function< void(carma_v2x_msgs::msg::MobilityOperation)> publish_mobility_operation_
void mobilityOperationMessageParser(std::string mobility_operation_strategy_params)
Function to help parse the text included in an inbound MobilityOperation message's strategy_params fi...
void onNewPose(geometry_msgs::msg::PoseStamped::UniquePtr msg)
Callback for the pose subscriber. The pose will be converted into lat/lon and stored locally.
void onReceivedNewDestination()
Callback for usage by the PortDrayageStateMachine when the vehicle has received a new destination.
std::shared_ptr< carma_planning_msgs::srv::SetActiveRoute::Request > composeSetActiveRouteRequest(boost::optional< double > dest_latitude, boost::optional< double > dest_longitude) const
Create a SetActiveRoute service request to set a new active route for the system based on the destina...
std::shared_ptr< lanelet::projection::LocalFrameProjector > map_projector_
std::function< void(carma_msgs::msg::UIInstructions)> publish_ui_instructions_
rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr logger_
void onGuidanceState(const carma_planning_msgs::msg::GuidanceState::UniquePtr msg)
Callback to process the current status of the guidance state machine.
void onInboundMobilityOperation(carma_v2x_msgs::msg::MobilityOperation::UniquePtr msg)
Callback to process a received MobilityOperation message.
void setCargoID(const std::string &cargo_id)
Setter function to set this object's cargo_id_ string.
std::shared_ptr< carma_planning_msgs::msg::RouteEvent > latest_route_event_
void onNewGeoreference(std_msgs::msg::String::UniquePtr msg)
Callback for map projection string to define lat/lon <--> map conversion.
std::function< bool(std::shared_ptr< carma_planning_msgs::srv::SetActiveRoute::Request >)> call_set_active_route_service_
carma_v2x_msgs::msg::MobilityOperation composeArrivalMessage() const
Assemble the current dataset into a MobilityOperations message with a JSON formatted body containing ...
PortDrayageState
Enum containing the possible state values for the PortDrayagePlugin.
Convenience struct for storing all data contained in a received MobilityOperation message's strategy_...