Skip to content

Commit 4eabd25

Browse files
saikishorbmagyar
andauthored
[Feature] Fallback controllers (#1789)
Co-authored-by: Bence Magyar <[email protected]>
1 parent 1d2d617 commit 4eabd25

File tree

8 files changed

+1221
-6
lines changed

8 files changed

+1221
-6
lines changed

controller_manager/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ if(BUILD_TESTING)
9191
target_link_libraries(test_controller_manager
9292
controller_manager
9393
test_controller
94+
test_chainable_controller
9495
ros2_control_test_assets::ros2_control_test_assets
9596
)
9697

controller_manager/doc/userdoc.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,18 @@ update_rate (mandatory; integer)
8787
Name of a plugin exported using ``pluginlib`` for a controller.
8888
This is a class from which controller's instance with name "``controller_name``" is created.
8989

90+
<controller_name>.params_file
91+
The absolute path to the YAML file with parameters for the controller.
92+
The file should contain the parameters for the controller in the standard ROS 2 YAML format.
93+
94+
<controller_name>.fallback_controllers
95+
List of controllers that are activated as a fallback strategy, when the spawned controllers fail by returning ``return_type::ERROR`` during the ``update`` cycle.
96+
It is recommended to add all the controllers needed for the fallback strategy to the list, including the chainable controllers whose interfaces are used by the main fallback controllers.
97+
98+
.. warning::
99+
The fallback controllers activation is subject to the availability of the state and command interfaces at the time of activation.
100+
It is recommended to test the fallback strategy in simulation before deploying it on the real robot.
101+
90102
Handling Multiple Controller Managers
91103
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
92104

controller_manager/include/controller_manager/controller_manager.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ class ControllerManager : public rclcpp::Node
128128
return add_controller_impl(controller_spec);
129129
}
130130

131+
controller_interface::ControllerInterfaceBaseSharedPtr add_controller(
132+
const ControllerSpec & controller_spec)
133+
{
134+
return add_controller_impl(controller_spec);
135+
}
136+
131137
/// configure_controller Configure controller by name calling their "configure" method.
132138
/**
133139
* \param[in] controller_name as a string.
@@ -418,6 +424,18 @@ class ControllerManager : public rclcpp::Node
418424
const std::vector<ControllerSpec> & controllers, int strictness,
419425
const ControllersListIterator controller_it);
420426

427+
/// Checks if the fallback controllers of the given controllers are in the right
428+
/// state, so they can be activated immediately
429+
/**
430+
* \param[in] controllers is a list of controllers to activate.
431+
* \param[in] controller_it is the iterator pointing to the controller to be activated.
432+
* \return return_type::OK if all fallback controllers are in the right state, otherwise
433+
* return_type::ERROR.
434+
*/
435+
CONTROLLER_MANAGER_PUBLIC
436+
controller_interface::return_type check_fallback_controllers_state_pre_activation(
437+
const std::vector<ControllerSpec> & controllers, const ControllersListIterator controller_it);
438+
421439
/**
422440
* @brief Inserts a controller into an ordered list based on dependencies to compute the
423441
* controller chain.

controller_manager/src/controller_manager.cpp

Lines changed: 263 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,41 @@ void controller_chain_spec_cleanup(
170170
}
171171
ctrl_chain_spec.erase(controller);
172172
}
173+
174+
// Gets the list of active controllers that use the command interface of the given controller
175+
void get_active_controllers_using_command_interfaces_of_controller(
176+
const std::string & controller_name,
177+
const std::vector<controller_manager::ControllerSpec> & controllers,
178+
std::vector<std::string> & controllers_using_command_interfaces)
179+
{
180+
auto it = std::find_if(
181+
controllers.begin(), controllers.end(),
182+
std::bind(controller_name_compare, std::placeholders::_1, controller_name));
183+
if (it == controllers.end())
184+
{
185+
RCLCPP_ERROR(
186+
rclcpp::get_logger("ControllerManager::utils"),
187+
"Controller '%s' not found in the list of controllers.", controller_name.c_str());
188+
return;
189+
}
190+
const auto cmd_itfs = it->c->command_interface_configuration().names;
191+
for (const auto & cmd_itf : cmd_itfs)
192+
{
193+
for (const auto & controller : controllers)
194+
{
195+
const auto ctrl_cmd_itfs = controller.c->command_interface_configuration().names;
196+
// check if the controller is active and has the command interface and make sure that it
197+
// doesn't exist in the list already
198+
if (
199+
is_controller_active(controller.c) &&
200+
std::find(ctrl_cmd_itfs.begin(), ctrl_cmd_itfs.end(), cmd_itf) != ctrl_cmd_itfs.end())
201+
{
202+
add_element_to_list(controllers_using_command_interfaces, controller.info.name);
203+
}
204+
}
205+
}
206+
}
207+
173208
} // namespace
174209

175210
namespace controller_manager
@@ -536,6 +571,15 @@ controller_interface::ControllerInterfaceBaseSharedPtr ControllerManager::load_c
536571
}
537572
if (get_parameter(fallback_ctrl_param, fallback_controllers) && !fallback_controllers.empty())
538573
{
574+
if (
575+
std::find(fallback_controllers.begin(), fallback_controllers.end(), controller_name) !=
576+
fallback_controllers.end())
577+
{
578+
RCLCPP_ERROR(
579+
get_logger(), "Controller '%s' cannot be a fallback controller for itself.",
580+
controller_name.c_str());
581+
return nullptr;
582+
}
539583
controller_spec.info.fallback_controllers_names = fallback_controllers;
540584
}
541585

@@ -1082,6 +1126,11 @@ controller_interface::return_type ControllerManager::switch_controller(
10821126
status = check_following_controllers_for_activate(controllers, strictness, controller_it);
10831127
}
10841128

1129+
if (status == controller_interface::return_type::OK)
1130+
{
1131+
status = check_fallback_controllers_state_pre_activation(controllers, controller_it);
1132+
}
1133+
10851134
if (status != controller_interface::return_type::OK)
10861135
{
10871136
RCLCPP_WARN(
@@ -2360,16 +2409,68 @@ controller_interface::return_type ControllerManager::update(
23602409
}
23612410
if (!failed_controllers_list.empty())
23622411
{
2363-
std::string failed_controllers;
2412+
const auto FALLBACK_STACK_MAX_SIZE = 500;
2413+
std::vector<std::string> active_controllers_using_interfaces(failed_controllers_list);
2414+
active_controllers_using_interfaces.reserve(FALLBACK_STACK_MAX_SIZE);
2415+
std::vector<std::string> cumulative_fallback_controllers;
2416+
cumulative_fallback_controllers.reserve(FALLBACK_STACK_MAX_SIZE);
2417+
2418+
for (const auto & failed_ctrl : failed_controllers_list)
2419+
{
2420+
auto ctrl_it = std::find_if(
2421+
rt_controller_list.begin(), rt_controller_list.end(),
2422+
std::bind(controller_name_compare, std::placeholders::_1, failed_ctrl));
2423+
if (ctrl_it != rt_controller_list.end())
2424+
{
2425+
for (const auto & fallback_controller : ctrl_it->info.fallback_controllers_names)
2426+
{
2427+
cumulative_fallback_controllers.push_back(fallback_controller);
2428+
get_active_controllers_using_command_interfaces_of_controller(
2429+
fallback_controller, rt_controller_list, active_controllers_using_interfaces);
2430+
}
2431+
}
2432+
}
2433+
std::string controllers_string;
2434+
controllers_string.reserve(500);
23642435
for (const auto & controller : failed_controllers_list)
23652436
{
2366-
failed_controllers += "\n\t- " + controller;
2437+
controllers_string.append(controller);
2438+
controllers_string.append(" ");
23672439
}
23682440
RCLCPP_ERROR(
2369-
get_logger(), "Deactivating following controllers as their update resulted in an error :%s",
2370-
failed_controllers.c_str());
2371-
2372-
deactivate_controllers(rt_controller_list, failed_controllers_list);
2441+
get_logger(), "Deactivating controllers : [ %s] as their update resulted in an error!",
2442+
controllers_string.c_str());
2443+
if (active_controllers_using_interfaces.size() > failed_controllers_list.size())
2444+
{
2445+
controllers_string.clear();
2446+
for (size_t i = failed_controllers_list.size();
2447+
i < active_controllers_using_interfaces.size(); i++)
2448+
{
2449+
controllers_string.append(active_controllers_using_interfaces[i]);
2450+
controllers_string.append(" ");
2451+
}
2452+
RCLCPP_ERROR_EXPRESSION(
2453+
get_logger(), !controllers_string.empty(),
2454+
"Deactivating controllers : [ %s] using the command interfaces needed for the fallback "
2455+
"controllers to activate.",
2456+
controllers_string.c_str());
2457+
}
2458+
if (!cumulative_fallback_controllers.empty())
2459+
{
2460+
controllers_string.clear();
2461+
for (const auto & controller : cumulative_fallback_controllers)
2462+
{
2463+
controllers_string.append(controller);
2464+
controllers_string.append(" ");
2465+
}
2466+
RCLCPP_ERROR(
2467+
get_logger(), "Activating fallback controllers : [ %s]", controllers_string.c_str());
2468+
}
2469+
deactivate_controllers(rt_controller_list, active_controllers_using_interfaces);
2470+
if (!cumulative_fallback_controllers.empty())
2471+
{
2472+
activate_controllers(rt_controller_list, cumulative_fallback_controllers);
2473+
}
23732474
}
23742475

23752476
// there are controllers to (de)activate
@@ -2765,6 +2866,162 @@ controller_interface::return_type ControllerManager::check_preceeding_controller
27652866
return controller_interface::return_type::OK;
27662867
}
27672868

2869+
controller_interface::return_type
2870+
ControllerManager::check_fallback_controllers_state_pre_activation(
2871+
const std::vector<ControllerSpec> & controllers, const ControllersListIterator controller_it)
2872+
{
2873+
for (const auto & fb_ctrl : controller_it->info.fallback_controllers_names)
2874+
{
2875+
auto fb_ctrl_it = std::find_if(
2876+
controllers.begin(), controllers.end(),
2877+
std::bind(controller_name_compare, std::placeholders::_1, fb_ctrl));
2878+
if (fb_ctrl_it == controllers.end())
2879+
{
2880+
RCLCPP_ERROR(
2881+
get_logger(),
2882+
"Unable to find the fallback controller : '%s' of the controller : '%s' "
2883+
"within the controller list",
2884+
fb_ctrl.c_str(), controller_it->info.name.c_str());
2885+
return controller_interface::return_type::ERROR;
2886+
}
2887+
else
2888+
{
2889+
if (!(is_controller_inactive(fb_ctrl_it->c) || is_controller_active(fb_ctrl_it->c)))
2890+
{
2891+
RCLCPP_ERROR(
2892+
get_logger(),
2893+
"Controller with name '%s' cannot be activated, as it's fallback controller : '%s'"
2894+
" need to be configured and be in inactive/active state!",
2895+
controller_it->info.name.c_str(), fb_ctrl.c_str());
2896+
return controller_interface::return_type::ERROR;
2897+
}
2898+
for (const auto & fb_cmd_itf : fb_ctrl_it->c->command_interface_configuration().names)
2899+
{
2900+
if (!resource_manager_->command_interface_is_available(fb_cmd_itf))
2901+
{
2902+
ControllersListIterator following_ctrl_it;
2903+
if (is_interface_a_chained_interface(fb_cmd_itf, controllers, following_ctrl_it))
2904+
{
2905+
// if following_ctrl_it is inactive and it is in the fallback list of the
2906+
// controller_it and then check it it's exported reference interface names list if
2907+
// it's available
2908+
if (is_controller_inactive(following_ctrl_it->c))
2909+
{
2910+
if (
2911+
std::find(
2912+
controller_it->info.fallback_controllers_names.begin(),
2913+
controller_it->info.fallback_controllers_names.end(),
2914+
following_ctrl_it->info.name) !=
2915+
controller_it->info.fallback_controllers_names.end())
2916+
{
2917+
const auto exported_ref_itfs =
2918+
resource_manager_->get_controller_reference_interface_names(
2919+
following_ctrl_it->info.name);
2920+
if (
2921+
std::find(exported_ref_itfs.begin(), exported_ref_itfs.end(), fb_cmd_itf) ==
2922+
exported_ref_itfs.end())
2923+
{
2924+
RCLCPP_ERROR(
2925+
get_logger(),
2926+
"Controller with name '%s' cannot be activated, as the command interface : "
2927+
"'%s' required by its fallback controller : '%s' is not exported by the "
2928+
"controller : '%s' in the current fallback list!",
2929+
controller_it->info.name.c_str(), fb_cmd_itf.c_str(), fb_ctrl.c_str(),
2930+
following_ctrl_it->info.name.c_str());
2931+
return controller_interface::return_type::ERROR;
2932+
}
2933+
}
2934+
else
2935+
{
2936+
RCLCPP_ERROR(
2937+
get_logger(),
2938+
"Controller with name '%s' cannot be activated, as the command interface : "
2939+
"'%s' required by its fallback controller : '%s' is not available as the "
2940+
"controller is not in active state!. May be consider adding this controller to "
2941+
"the fallback list of the controller : '%s' or already have it activated.",
2942+
controller_it->info.name.c_str(), fb_cmd_itf.c_str(), fb_ctrl.c_str(),
2943+
following_ctrl_it->info.name.c_str());
2944+
return controller_interface::return_type::ERROR;
2945+
}
2946+
}
2947+
}
2948+
else
2949+
{
2950+
RCLCPP_ERROR(
2951+
get_logger(),
2952+
"Controller with name '%s' cannot be activated, as not all of its fallback "
2953+
"controller's : '%s' command interfaces are currently available!",
2954+
controller_it->info.name.c_str(), fb_ctrl.c_str());
2955+
return controller_interface::return_type::ERROR;
2956+
}
2957+
}
2958+
}
2959+
for (const auto & fb_state_itf : fb_ctrl_it->c->state_interface_configuration().names)
2960+
{
2961+
if (!resource_manager_->state_interface_is_available(fb_state_itf))
2962+
{
2963+
ControllersListIterator following_ctrl_it;
2964+
if (is_interface_a_chained_interface(fb_state_itf, controllers, following_ctrl_it))
2965+
{
2966+
// if following_ctrl_it is inactive and it is in the fallback list of the
2967+
// controller_it and then check it it's exported reference interface names list if
2968+
// it's available
2969+
if (is_controller_inactive(following_ctrl_it->c))
2970+
{
2971+
if (
2972+
std::find(
2973+
controller_it->info.fallback_controllers_names.begin(),
2974+
controller_it->info.fallback_controllers_names.end(),
2975+
following_ctrl_it->info.name) !=
2976+
controller_it->info.fallback_controllers_names.end())
2977+
{
2978+
const auto exported_state_itfs =
2979+
resource_manager_->get_controller_exported_state_interface_names(
2980+
following_ctrl_it->info.name);
2981+
if (
2982+
std::find(exported_state_itfs.begin(), exported_state_itfs.end(), fb_state_itf) ==
2983+
exported_state_itfs.end())
2984+
{
2985+
RCLCPP_ERROR(
2986+
get_logger(),
2987+
"Controller with name '%s' cannot be activated, as the state interface : "
2988+
"'%s' required by its fallback controller : '%s' is not exported by the "
2989+
"controller : '%s' in the current fallback list!",
2990+
controller_it->info.name.c_str(), fb_state_itf.c_str(), fb_ctrl.c_str(),
2991+
following_ctrl_it->info.name.c_str());
2992+
return controller_interface::return_type::ERROR;
2993+
}
2994+
}
2995+
else
2996+
{
2997+
RCLCPP_ERROR(
2998+
get_logger(),
2999+
"Controller with name '%s' cannot be activated, as the state interface : "
3000+
"'%s' required by its fallback controller : '%s' is not available as the "
3001+
"controller is not in active state!. May be consider adding this controller to "
3002+
"the fallback list of the controller : '%s' or already have it activated.",
3003+
controller_it->info.name.c_str(), fb_state_itf.c_str(), fb_ctrl.c_str(),
3004+
following_ctrl_it->info.name.c_str());
3005+
return controller_interface::return_type::ERROR;
3006+
}
3007+
}
3008+
}
3009+
else
3010+
{
3011+
RCLCPP_ERROR(
3012+
get_logger(),
3013+
"Controller with name '%s' cannot be activated, as not all of its fallback "
3014+
"controller's : '%s' state interfaces are currently available!",
3015+
controller_it->info.name.c_str(), fb_ctrl.c_str());
3016+
return controller_interface::return_type::ERROR;
3017+
}
3018+
}
3019+
}
3020+
}
3021+
}
3022+
return controller_interface::return_type::OK;
3023+
}
3024+
27683025
void ControllerManager::controller_activity_diagnostic_callback(
27693026
diagnostic_updater::DiagnosticStatusWrapper & stat)
27703027
{

controller_manager/test/test_controller/test_controller.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@ std::vector<double> TestController::get_state_interface_data() const
137137
return state_intr_data;
138138
}
139139

140+
void TestController::set_external_commands_for_testing(const std::vector<double> & commands)
141+
{
142+
external_commands_for_testing_ = commands;
143+
}
144+
140145
} // namespace test_controller
141146

142147
#include "pluginlib/class_list_macros.hpp"

controller_manager/test/test_controller/test_controller.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ class TestController : public controller_interface::ControllerInterface
7070

7171
const std::string & getRobotDescription() const;
7272

73+
void set_external_commands_for_testing(const std::vector<double> & commands);
74+
7375
unsigned int internal_counter = 0;
7476
bool simulate_cleanup_failure = false;
7577
// Variable where we store when cleanup was called, pointer because the controller

0 commit comments

Comments
 (0)