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.
plugin_manager.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2019-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
17#include <boost/algorithm/string.hpp>
18#include <lifecycle_msgs/msg/state.hpp>
19#include <rclcpp/logger.hpp>
20#include <rclcpp/logging.hpp>
22
23using std_msec = std::chrono::milliseconds;
24
26{
27
28 PluginManager::PluginManager(const std::vector<std::string>& required_plugins,
29 const std::vector<std::string>& auto_activated_plugins,
30 std::shared_ptr<ros2_lifecycle_manager::LifecycleManagerInterface> plugin_lifecycle_mgr,
31 GetParentNodeStateFunc get_parent_state_func,
32 ServiceNamesAndTypesFunc get_service_names_and_types_func,
33 std::chrono::nanoseconds service_timeout, std::chrono::nanoseconds call_timeout)
34 : required_plugins_(required_plugins.begin(), required_plugins.end()),
35 auto_activated_plugins_(auto_activated_plugins.begin(), auto_activated_plugins.end()),
36 plugin_lifecycle_mgr_(plugin_lifecycle_mgr), get_parent_state_func_(get_parent_state_func),
37 get_service_names_and_types_func_(get_service_names_and_types_func),
38 service_timeout_(service_timeout), call_timeout_(call_timeout)
39 {
40 if (!plugin_lifecycle_mgr)
41 throw std::invalid_argument("Input plugin_lifecycle_mgr to PluginManager constructor cannot be null");
42
43 // For all required and auto activated plugins add unknown entries but with
44 // user_requested_activation set to true.
45 // This will be used later to determine how to transition the plugin specified by that entry
46
47 for (const auto& p : required_plugins_) {
48 RCLCPP_INFO_STREAM(rclcpp::get_logger("subsystem_controllers"), "Added a required plugin: " << p);
49
50 Entry e(false, false, p, carma_planning_msgs::msg::Plugin::UNKNOWN, "", true);
52 plugin_lifecycle_mgr_->add_managed_node(p);
53 }
54
55 for (const auto& p : auto_activated_plugins_) {
56 RCLCPP_INFO_STREAM(rclcpp::get_logger("subsystem_controllers"), "Added an auto activated plugin: " << p);
57
58 Entry e(false, false, p, carma_planning_msgs::msg::Plugin::UNKNOWN, "", true);
60 plugin_lifecycle_mgr_->add_managed_node(p);
61 }
62 }
63
64 void PluginManager::add_plugin(const Entry& plugin)
65 {
66 plugin_lifecycle_mgr_->add_managed_node(plugin.name_);
67
68 em_.update_entry(plugin);
69
70 Entry deactivated_entry = plugin;
71
72 auto parent_node_state = get_parent_state_func_();
73
74 // If this node is not in the active or inactive states then move the plugin to unconfigured
75 if (parent_node_state != lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE
76 && parent_node_state != lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE)
77 {
78
79 deactivated_entry.active_ = false;
80
81 // Move plugin to match this nodes state
82 auto result_state = plugin_lifecycle_mgr_->transition_node_to_state(parent_node_state, plugin.name_, service_timeout_, call_timeout_);
83
84 if(result_state != parent_node_state)
85 {
86
87 // If this plugin was required then trigger exception
88 if (required_plugins_.find(plugin.name_) != required_plugins_.end())
89 {
90 throw std::runtime_error("Newly discovered required plugin " + plugin.name_ + " could not be brought to controller node state.");
91 }
92
93 // If this plugin was not required log an error and mark it is unavailable and deactivated
94 RCLCPP_ERROR_STREAM(rclcpp::get_logger("subsystem_controllers"), "Failed to transition newly discovered non-required plugin: "
95 << plugin.name_ << " Marking as deactivated and unavailable!");
96
97 deactivated_entry.available_ = false;
98
99 }
100
101 em_.update_entry(deactivated_entry);
102 return;
103 }
104
105
106 // If the node is active or inactive then,
107 // when adding a plugin, it should be brought to the inactive state
108 // We do not need transition it beyond inactive in this function as that will be managed by the plugin activation process via UI or parameters
109 auto result_state = plugin_lifecycle_mgr_->transition_node_to_state(lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE, plugin.name_, service_timeout_, call_timeout_);
110
111 if(result_state != lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE)
112 {
113 // If this plugin was required then trigger exception
114 if (required_plugins_.find(plugin.name_) != required_plugins_.end())
115 {
116 throw std::runtime_error("Newly discovered required plugin " + plugin.name_ + " could not be brought to inactive state.");
117 }
118
119 // If this plugin was not required log an error and mark it is unavailable and deactivated
120 RCLCPP_ERROR_STREAM(rclcpp::get_logger("subsystem_controllers"), "Failed to configure newly discovered non-required plugin: "
121 << plugin.name_ << " Marking as deactivated and unavailable!");
122
123 deactivated_entry.available_ = false;
124 }
125
126 deactivated_entry.active_ = false;
127 em_.update_entry(deactivated_entry);
128
129 }
130
132 {
133 bool full_success = true;
134 // Bring all known plugins to the inactive state
135 for (auto plugin : em_.get_entries())
136 {
137 auto result_state = plugin_lifecycle_mgr_->transition_node_to_state(lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE, plugin.name_, service_timeout_, call_timeout_);
138
139 if(result_state != lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE)
140 {
141 // If this plugin was required then trigger exception
142 if (required_plugins_.find(plugin.name_) != required_plugins_.end())
143 {
144 throw std::runtime_error("Required plugin " + plugin.name_ + " could not be configured.");
145 }
146
147 // If this plugin was not required log an error and mark it is unavailable and deactivated
148 RCLCPP_ERROR_STREAM(rclcpp::get_logger("subsystem_controllers"), "Failed to configure newly discovered non-required plugin: "
149 << plugin.name_ << " Marking as deactivated and unavailable!");
150
151 Entry deactivated_entry = plugin;
152 deactivated_entry.active_ = false;
153 deactivated_entry.available_ = false;
154 deactivated_entry.user_requested_activation_ = false;
155 em_.update_entry(deactivated_entry);
156
157 full_success = false;
158 }
159
160 }
161
162 return full_success;
163
164 }
165
167 {
168 bool full_success = true;
169 // Bring all required or auto activated plugins to the active state
170 for (auto plugin : em_.get_entries())
171 {
172 // If this is not a plugin slated for activation then continue and leave up to user to activate manually later
173 if (!plugin.user_requested_activation_)
174 continue;
175
176 auto result_state = plugin_lifecycle_mgr_->transition_node_to_state(lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE, plugin.name_, service_timeout_, call_timeout_);
177
178 if(result_state != lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE)
179 {
180 // If this plugin was required then trigger exception
181 if (required_plugins_.find(plugin.name_) != required_plugins_.end())
182 {
183 throw std::runtime_error("Required plugin " + plugin.name_ + " could not be activated.");
184 }
185
186 // If this plugin was not required log an error and mark it is unavailable and deactivated
187 RCLCPP_ERROR_STREAM(rclcpp::get_logger("subsystem_controllers"), "Failed to activate newly discovered non-required plugin: "
188 << plugin.name_ << " Marking as deactivated and unavailable!");
189
190 Entry deactivated_entry = plugin;
191 deactivated_entry.active_ = false;
192 deactivated_entry.available_ = false;
193 deactivated_entry.user_requested_activation_ = false;
194 em_.update_entry(deactivated_entry);
195
196 full_success = false;
197 }
198
199 // If this was an auto activated plugin and not required then we only activate is once
200 if (auto_activated_plugins_.find(plugin.name_) != auto_activated_plugins_.end())
201 {
202 plugin.user_requested_activation_ = false;
203 }
204
205 plugin.active_ = true; // Mark plugin as active
206
207 em_.update_entry(plugin);
208
209 }
210
211 return full_success;
212 }
213
215 {
216 bool full_success = true;
217 // Bring all required or auto activated plugins to the active state
218 for (auto plugin : em_.get_entries())
219 {
220 auto result_state = plugin_lifecycle_mgr_->transition_node_to_state(lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE, plugin.name_, service_timeout_, call_timeout_);
221
222 if(result_state != lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE)
223 {
224 // If this plugin was required then trigger exception
225 if (required_plugins_.find(plugin.name_) != required_plugins_.end())
226 {
227 throw std::runtime_error("Required plugin " + plugin.name_ + " could not be deactivated.");
228 }
229
230 // If this plugin was not required log an error and mark it is unavailable and deactivated
231 RCLCPP_ERROR_STREAM(rclcpp::get_logger("subsystem_controllers"), "Failed to deactivate non-required plugin: "
232 << plugin.name_ << " Marking as deactivated and unavailable!");
233
234 Entry deactivated_entry = plugin;
235 deactivated_entry.active_ = false;
236 deactivated_entry.available_ = false;
237 deactivated_entry.user_requested_activation_ = false;
238 em_.update_entry(deactivated_entry);
239
240 full_success = false;
241 }
242
243 }
244
245 return full_success;
246 }
247
249 {
250 bool full_success = true;
251 // Bring all required or auto activated plugins to the unconfigured state
252 for (auto plugin : em_.get_entries())
253 {
254 auto result_state = plugin_lifecycle_mgr_->transition_node_to_state(lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED, plugin.name_, service_timeout_, call_timeout_);
255
256 if(result_state != lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED)
257 {
258 // If this plugin was required then trigger exception
259 if (required_plugins_.find(plugin.name_) != required_plugins_.end())
260 {
261 throw std::runtime_error("Required plugin " + plugin.name_ + " could not be cleaned up.");
262 }
263
264 // If this plugin was not required log an error and mark it is unavailable and deactivated
265 RCLCPP_ERROR_STREAM(rclcpp::get_logger("subsystem_controllers"), "Failed to cleanup non-required plugin: "
266 << plugin.name_ << " Marking as deactivated and unavailable!");
267
268 Entry deactivated_entry = plugin;
269 deactivated_entry.active_ = false;
270 deactivated_entry.available_ = false;
271 deactivated_entry.user_requested_activation_ = false;
272 em_.update_entry(deactivated_entry);
273
274 full_success = false;
275 }
276
277 }
278
279 return full_success;
280 }
281
283 {
284 return plugin_lifecycle_mgr_->shutdown(service_timeout_, call_timeout_, false, em_.get_entry_names()).empty();
285 }
286
287 void PluginManager::get_registered_plugins(SrvHeader, carma_planning_msgs::srv::PluginList::Request::SharedPtr, carma_planning_msgs::srv::PluginList::Response::SharedPtr res)
288 {
289 // convert to plugin list
290 for(const auto& plugin : em_.get_entries())
291 {
292 carma_planning_msgs::msg::Plugin msg;
293 msg.activated = plugin.active_;
294 msg.available = plugin.available_;
295 msg.name = plugin.name_;
296 msg.type = plugin.type_;
297 msg.capability = plugin.capability_;
298 res->plugins.push_back(msg);
299 }
300 }
301
302 void PluginManager::get_active_plugins(SrvHeader, carma_planning_msgs::srv::PluginList::Request::SharedPtr, carma_planning_msgs::srv::PluginList::Response::SharedPtr res)
303 {
304 // convert to plugin list
305 for(const auto& plugin : em_.get_entries())
306 {
307 if(plugin.active_)
308 {
309 carma_planning_msgs::msg::Plugin msg;
310 msg.activated = plugin.active_;
311 msg.available = plugin.available_;
312 msg.name = plugin.name_;
313 msg.type = plugin.type_;
314 msg.capability = plugin.capability_;
315 res->plugins.push_back(msg);
316 }
317 }
318 }
319
320 void PluginManager::activate_plugin(SrvHeader, carma_planning_msgs::srv::PluginActivation::Request::SharedPtr req, carma_planning_msgs::srv::PluginActivation::Response::SharedPtr res)
321 {
322 boost::optional<Entry> requested_plugin = em_.get_entry_by_name(req->plugin_name);
323
324 if(!requested_plugin) // If not a known plugin then obviously not activated. Though really it would be better to have an indication of name failure in the message
325 {
326 res->newstate = false;
327 return;
328 }
329
330 bool activated = false;
331 if (req->activated)
332 {
333 auto result_state = plugin_lifecycle_mgr_->transition_node_to_state(lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE, requested_plugin->name_, service_timeout_, call_timeout_);
334
335 activated = (result_state == lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE);
336 } else {
337 auto result_state = plugin_lifecycle_mgr_->transition_node_to_state(lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE, requested_plugin->name_, service_timeout_, call_timeout_);
338
339 activated = (result_state == lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE);
340 }
341
342 Entry updated_entry(requested_plugin->available_, activated, requested_plugin->name_, requested_plugin->type_, requested_plugin->capability_, true); // Mark as user activated
343 em_.update_entry(updated_entry);
344
345 res->newstate = activated;
346 }
347
348 void PluginManager::update_plugin_status(carma_planning_msgs::msg::Plugin::UniquePtr msg)
349 {
350 boost::optional<Entry> requested_plugin = em_.get_entry_by_name(msg->name);
351
352 if (!requested_plugin) // This is a new plugin so we need to add it
353 {
354 Entry plugin(msg->available, msg->activated, msg->name, msg->type, msg->capability, false);
355 add_plugin(plugin);
356 return;
357 }
358
359 Entry plugin(msg->available, msg->activated, msg->name, msg->type, msg->capability, requested_plugin->user_requested_activation_);
360
361 em_.update_entry(plugin);
362 }
363
364 bool PluginManager::matching_capability(const std::vector<std::string>& base_capability_levels, const std::vector<std::string>& compared_capability_levels)
365 {
366 for (size_t i=0; i < base_capability_levels.size() && i < compared_capability_levels.size(); i++)
367 {
368 if (compared_capability_levels[i].compare(base_capability_levels[i]) != 0)
369 return false;
370 }
371
372 return true;
373 }
374
375 void PluginManager::get_control_plugins_by_capability(SrvHeader, carma_planning_msgs::srv::GetPluginApi::Request::SharedPtr req, carma_planning_msgs::srv::GetPluginApi::Response::SharedPtr res)
376 {
377 std::vector<std::string> req_capability_levels;
378 boost::split(req_capability_levels, req->capability, boost::is_any_of("/"));
379
380 for(const auto& plugin : em_.get_entries())
381 {
382
383 std::vector<std::string> plugin_capability_levels;
384 boost::split(plugin_capability_levels, plugin.capability_, boost::is_any_of("/"));
385
386 if(plugin.type_ == carma_planning_msgs::msg::Plugin::CONTROL &&
387 (req->capability.size() == 0 || (matching_capability(plugin_capability_levels, req_capability_levels) && plugin.active_ && plugin.available_)))
388 {
389 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("guidance_controller"), "discovered control plugin: " << plugin.name_);
390 res->plan_service.push_back(plugin.name_ + control_trajectory_suffix_);
391 }
392 else
393 {
394 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("guidance_controller"), "not valid control plugin: " << plugin.name_);
395 }
396 }
397 }
398
399 void PluginManager::get_tactical_plugins_by_capability(SrvHeader, carma_planning_msgs::srv::GetPluginApi::Request::SharedPtr req, carma_planning_msgs::srv::GetPluginApi::Response::SharedPtr res)
400 {
401 std::vector<std::string> req_capability_levels;
402 boost::split(req_capability_levels, req->capability, boost::is_any_of("/"));
403
404 for(const auto& plugin : em_.get_entries())
405 {
406
407 std::vector<std::string> plugin_capability_levels;
408 boost::split(plugin_capability_levels, plugin.capability_, boost::is_any_of("/"));
409
410 if(plugin.type_ == carma_planning_msgs::msg::Plugin::TACTICAL &&
411 (req->capability.size() == 0 || (matching_capability(plugin_capability_levels, req_capability_levels) && plugin.active_ && plugin.available_)))
412 {
413 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("guidance_controller"), "discovered tactical plugin: " << plugin.name_);
414 res->plan_service.push_back(plugin.name_ + plan_trajectory_suffix_);
415 }
416 else
417 {
418 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("guidance_controller"), "not valid tactical plugin: " << plugin.name_);
419 }
420 }
421 }
422
423 void PluginManager::get_strategic_plugins_by_capability(SrvHeader, carma_planning_msgs::srv::GetPluginApi::Request::SharedPtr req, carma_planning_msgs::srv::GetPluginApi::Response::SharedPtr res)
424 {
425 std::vector<std::string> req_capability_levels;
426 boost::split(req_capability_levels, req->capability, boost::is_any_of("/"));
427
428 for(const auto& plugin : em_.get_entries())
429 {
430
431 std::vector<std::string> plugin_capability_levels;
432 boost::split(plugin_capability_levels, plugin.capability_, boost::is_any_of("/"));
433
434 if(plugin.type_ == carma_planning_msgs::msg::Plugin::STRATEGIC &&
435 (req->capability.size() == 0 || (matching_capability(plugin_capability_levels, req_capability_levels) && plugin.active_ && plugin.available_)))
436 {
437 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("guidance_controller"), "discovered strategic plugin: " << plugin.name_);
438 res->plan_service.push_back(plugin.name_ + plan_maneuvers_suffix_);
439 }
440 else
441 {
442 RCLCPP_DEBUG_STREAM(rclcpp::get_logger("guidance_controller"), "not valid strategic plugin: " << plugin.name_);
443 }
444 }
445 }
446
447}
std::chrono::milliseconds std_msec
std::vector< std::string > get_entry_names() const
Get all entry names as a list.
boost::optional< Entry > get_entry_by_name(const std::string &name) const
Get a entry using name as the key.
void update_entry(const Entry &entry)
Add a new entry if the given name does not exist. Update an existing entry if the given name exists.
std::vector< Entry > get_entries() const
Get all registed entries as a list.
std::shared_ptr< ros2_lifecycle_manager::LifecycleManagerInterface > plugin_lifecycle_mgr_
Lifecycle Manager which will track the plugin nodes and call their lifecycle services on request.
void add_plugin(const Entry &plugin)
Add the specified entry to our plugin management This function will attempt to move the newly detecte...
void get_control_plugins_by_capability(SrvHeader, carma_planning_msgs::srv::GetPluginApi::Request::SharedPtr req, carma_planning_msgs::srv::GetPluginApi::Response::SharedPtr res)
Get control plugins by capability.
PluginManager(const std::vector< std::string > &required_plugins, const std::vector< std::string > &auto_activated_plugins, std::shared_ptr< ros2_lifecycle_manager::LifecycleManagerInterface > plugin_lifecycle_mgr, GetParentNodeStateFunc get_parent_state_func, ServiceNamesAndTypesFunc get_service_names_and_types_func, std::chrono::nanoseconds service_timeout, std::chrono::nanoseconds call_timeout)
Constructor for PluginManager.
std::unordered_set< std::string > auto_activated_plugins_
Set of use specified auto activated plugins which will automatically started without need for user in...
const std::string plan_trajectory_suffix_
Base service name of plan_trajectory service.
std::unordered_set< std::string > required_plugins_
Set of required plugins a failure of which necessitates system shutdown.
const std::string plan_maneuvers_suffix_
Base service name of plan_trajectory service.
bool matching_capability(const std::vector< std::string > &base_capability_levels, const std::vector< std::string > &compared_capability_levels)
Returns true if the provided base capability hierarchy can achieve the requested capability hierarchy...
std::chrono::nanoseconds call_timeout_
The timeout for service calls to return.
void get_registered_plugins(SrvHeader, carma_planning_msgs::srv::PluginList::Request::SharedPtr req, carma_planning_msgs::srv::PluginList::Response::SharedPtr res)
Returns the list of known plugins.
GetParentNodeStateFunc get_parent_state_func_
Callback to retrieve the lifecycle state of the parent process.
void update_plugin_status(carma_planning_msgs::msg::Plugin::UniquePtr msg)
Update the status of a certain plugin.
void get_active_plugins(SrvHeader, carma_planning_msgs::srv::PluginList::Request::SharedPtr req, carma_planning_msgs::srv::PluginList::Response::SharedPtr res)
Get the list of currently active plugins.
std::chrono::nanoseconds service_timeout_
The timeout for services to be available.
void get_strategic_plugins_by_capability(SrvHeader, carma_planning_msgs::srv::GetPluginApi::Request::SharedPtr req, carma_planning_msgs::srv::GetPluginApi::Response::SharedPtr res)
Get strategic plugins by capability.
void get_tactical_plugins_by_capability(SrvHeader, carma_planning_msgs::srv::GetPluginApi::Request::SharedPtr req, carma_planning_msgs::srv::GetPluginApi::Response::SharedPtr res)
Get tactical plugins by capability.
void activate_plugin(SrvHeader, carma_planning_msgs::srv::PluginActivation::Request::SharedPtr req, carma_planning_msgs::srv::PluginActivation::Response::SharedPtr res)
Activate the specified plugin.
EntryManager em_
Entry manager to keep track of detected plugins.
const std::string control_trajectory_suffix_
Base topic name of control plugin trajectory input topic.
std::function< uint8_t()> GetParentNodeStateFunc
std::function< std::map< std::string, std::vector< std::string, std::allocator< std::string > > >(const std::string &, const std::string &)> ServiceNamesAndTypesFunc
Function which will return a map of service names and their message types based on the provided base ...
const std::shared_ptr< rmw_request_id_t > SrvHeader
An entry represents a driver details for the purposes of tracking.
Definition: entry.hpp:27
bool available_
Availability flag of a driver.
Definition: entry.hpp:29
std::string name_
Fully specified node name of a driver.
Definition: entry.hpp:31
bool user_requested_activation_
Flag indicating if the user requested this plugin be activated.
Definition: entry.h:39
bool active_
Activation flag of a plugin.
Definition: entry.h:31