Skip to content

Commit 2f777ff

Browse files
authored
Deactivate the whole controller chain if one of the update results in ERROR (ros-controls#2681) (ros-controls#2743)
1 parent d533db3 commit 2f777ff

File tree

8 files changed

+748
-31
lines changed

8 files changed

+748
-31
lines changed

controller_manager/doc/userdoc.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -416,13 +416,14 @@ Hardware and Controller Errors
416416
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
417417

418418
If the hardware during it's ``read`` or ``write`` method returns ``return_type::ERROR``, the controller manager will stop all controllers that are using the hardware's command and state interfaces.
419-
Likewise, if a controller returns ``return_type::ERROR`` from its ``update`` method, the controller manager will deactivate the respective controller. In future, the controller manager will try to start any fallback controllers if available.
419+
Likewise, if a controller returns ``return_type::ERROR`` from its ``update`` method, the controller manager will deactivate the respective controller (or) the entire controller chain it is part of, then the controller manager will try to start any available fallback controllers.
420420

421421
Factors that affect Determinism
422422
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
423423
When run under the conditions determined in the above section, the determinism is assured up to the limitations of the hardware and the real-time kernel. However, there are some situations that can affect determinism:
424424

425-
* When a controller fails to activate, the controller_manager will call the methods ``prepare_command_mode_switch`` and ``perform_command_mode_switch`` to stop the started interfaces. These calls can cause jitter in the main control loop.
425+
* When a controller fails to activate in the realtime loop, the controller_manager will call the methods ``prepare_command_mode_switch`` and ``perform_command_mode_switch`` to stop the started interfaces. These calls can cause jitter in the main control loop.
426+
* If a controller does not complete a successful update cycle in the realtime loop (for example, returns ``return_type::ERROR``), the controller manager will deactivate that controller (or) the entire controller chain it is part of. It will then invoke ``prepare_command_mode_switch`` and ``perform_command_mode_switch`` to stop the interfaces used by the affected controller(s). These actions can introduce jitter into the main control loop.
426427

427428
Support for Asynchronous Updates
428429
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

controller_manager/include/controller_manager/controller_manager.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,14 @@ class ControllerManager : public rclcpp::Node
488488
const std::string & ctrl_name, std::vector<std::string>::iterator controller_iterator,
489489
bool append_to_controller);
490490

491+
/**
492+
* @brief Build the controller chain topology information based on the provided controllers.
493+
* This method constructs a directed graph representing the dependencies between controllers.
494+
* It analyzes the relationships between controllers, such as which controllers depend on others,
495+
* and builds a directed graph to represent these dependencies.
496+
*/
497+
void build_controllers_topology_info(const std::vector<ControllerSpec> & controllers);
498+
491499
/**
492500
* @brief Method to publish the state of the controller manager.
493501
* The state includes the list of controllers and the list of hardware interfaces along with

controller_manager/include/controller_manager/controller_spec.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ struct ControllerSpec
4848
hardware_interface::ControllerInfo info;
4949
controller_interface::ControllerInterfaceBaseSharedPtr c;
5050
std::shared_ptr<rclcpp::Time> last_update_cycle_time;
51+
std::vector<std::string> controllers_chain_group = {};
5152
std::shared_ptr<MovingAverageStatistics> execution_time_statistics;
5253
std::shared_ptr<MovingAverageStatistics> periodicity_statistics;
5354
};

controller_manager/src/controller_manager.cpp

Lines changed: 153 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,52 @@ void get_controller_list_command_interfaces(
357357
}
358358
}
359359

360+
void get_full_chain_spec(
361+
const std::string & controller_name,
362+
const std::unordered_map<std::string, controller_manager::ControllerChainSpec> & chain_spec,
363+
std::vector<std::string> & full_chain_list)
364+
{
365+
auto it = chain_spec.find(controller_name);
366+
if (it == chain_spec.end())
367+
{
368+
RCLCPP_WARN(
369+
rclcpp::get_logger("ControllerManager::utils"),
370+
"Controller '%s' not found in the controller chain specification.", controller_name.c_str());
371+
return;
372+
}
373+
ros2_control::add_item(full_chain_list, controller_name);
374+
for (const auto & following_controller : it->second.following_controllers)
375+
{
376+
if (!ros2_control::has_item(full_chain_list, following_controller))
377+
{
378+
get_full_chain_spec(following_controller, chain_spec, full_chain_list);
379+
}
380+
}
381+
for (const auto & preceding_controller : it->second.preceding_controllers)
382+
{
383+
if (!ros2_control::has_item(full_chain_list, preceding_controller))
384+
{
385+
get_full_chain_spec(preceding_controller, chain_spec, full_chain_list);
386+
}
387+
}
388+
}
389+
390+
void build_controller_full_chain_map_cache(
391+
const std::unordered_map<std::string, controller_manager::ControllerChainSpec> &
392+
controller_chain_spec,
393+
std::unordered_map<std::string, std::vector<std::string>> & controller_full_chain_info_cache)
394+
{
395+
controller_full_chain_info_cache.clear();
396+
for (const auto & [controller_name, chain_spec] : controller_chain_spec)
397+
{
398+
controller_full_chain_info_cache[controller_name] = {};
399+
// add the controller itself to the list
400+
ros2_control::add_item(controller_full_chain_info_cache[controller_name], controller_name);
401+
get_full_chain_spec(
402+
controller_name, controller_chain_spec, controller_full_chain_info_cache[controller_name]);
403+
}
404+
}
405+
360406
void register_controller_manager_statistics(
361407
const std::string & name,
362408
const libstatistics_collector::moving_average_statistics::StatisticData * variable)
@@ -1346,33 +1392,10 @@ controller_interface::return_type ControllerManager::configure_controller(
13461392
return controller_interface::return_type::ERROR;
13471393
}
13481394

1349-
for (const auto & cmd_itf : cmd_itfs)
1350-
{
1351-
controller_manager::ControllersListIterator ctrl_it;
1352-
if (is_interface_a_chained_interface(cmd_itf, controllers, ctrl_it))
1353-
{
1354-
ros2_control::add_item(
1355-
controller_chain_spec_[controller_name].following_controllers, ctrl_it->info.name);
1356-
ros2_control::add_item(
1357-
controller_chain_spec_[ctrl_it->info.name].preceding_controllers, controller_name);
1358-
ros2_control::add_item(
1359-
controller_chained_reference_interfaces_cache_[ctrl_it->info.name], controller_name);
1360-
}
1361-
}
1362-
// This is needed when we start exporting the state interfaces from the controllers
1363-
for (const auto & state_itf : state_itfs)
1364-
{
1365-
controller_manager::ControllersListIterator ctrl_it;
1366-
if (is_interface_a_chained_interface(state_itf, controllers, ctrl_it))
1367-
{
1368-
ros2_control::add_item(
1369-
controller_chain_spec_[controller_name].preceding_controllers, ctrl_it->info.name);
1370-
ros2_control::add_item(
1371-
controller_chain_spec_[ctrl_it->info.name].following_controllers, controller_name);
1372-
ros2_control::add_item(
1373-
controller_chained_state_interfaces_cache_[ctrl_it->info.name], controller_name);
1374-
}
1375-
}
1395+
build_controllers_topology_info(controllers);
1396+
1397+
std::unordered_map<std::string, std::vector<std::string>> controller_full_chain_info_cache;
1398+
build_controller_full_chain_map_cache(controller_chain_spec_, controller_full_chain_info_cache);
13761399

13771400
// Now let's reorder the controllers
13781401
// lock controllers
@@ -1407,6 +1430,23 @@ controller_interface::return_type ControllerManager::configure_controller(
14071430
}
14081431
}
14091432

1433+
// Update the controllers chain groups in the ControllerSpec
1434+
for (const auto & [ctrl_name, full_chain_info] : controller_full_chain_info_cache)
1435+
{
1436+
RCLCPP_DEBUG(
1437+
get_logger(), "%s",
1438+
fmt::format(
1439+
"The controller '{}' is in chain with: [{}]", ctrl_name, fmt::join(full_chain_info, ", "))
1440+
.c_str());
1441+
auto controller_it = std::find_if(
1442+
new_list.begin(), new_list.end(),
1443+
std::bind(controller_name_compare, std::placeholders::_1, ctrl_name));
1444+
if (controller_it != new_list.end())
1445+
{
1446+
controller_it->controllers_chain_group = full_chain_info;
1447+
}
1448+
}
1449+
14101450
to = new_list;
14111451
RCLCPP_DEBUG(get_logger(), "Reordered controllers list is:");
14121452
for (const auto & ctrl : to)
@@ -2154,6 +2194,7 @@ void ControllerManager::deactivate_controllers(
21542194
// deactivation
21552195
if (controller->is_chainable())
21562196
{
2197+
controller->set_chained_mode(false);
21572198
resource_manager_->make_controller_exported_state_interfaces_unavailable(controller_name);
21582199
resource_manager_->make_controller_reference_interfaces_unavailable(controller_name);
21592200
}
@@ -3096,7 +3137,16 @@ controller_interface::return_type ControllerManager::update(
30963137

30973138
if (controller_ret != controller_interface::return_type::OK)
30983139
{
3099-
rt_buffer_.deactivate_controllers_list.push_back(loaded_controller.info.name);
3140+
const std::vector<std::string> & controller_chain =
3141+
loaded_controller.controllers_chain_group;
3142+
RCLCPP_INFO_EXPRESSION(
3143+
get_logger(), controller_chain.size() > 1,
3144+
"Controller '%s' is part of a chain of %lu controllers that will be deactivated.",
3145+
loaded_controller.info.name.c_str(), controller_chain.size());
3146+
for (const auto & chained_controller : controller_chain)
3147+
{
3148+
ros2_control::add_item(rt_buffer_.deactivate_controllers_list, chained_controller);
3149+
}
31003150
ret = controller_ret;
31013151
}
31023152
}
@@ -4398,6 +4448,81 @@ void ControllerManager::update_list_with_controller_chain(
43984448
}
43994449
}
44004450

4451+
void ControllerManager::build_controllers_topology_info(
4452+
const std::vector<ControllerSpec> & controllers)
4453+
{
4454+
std::for_each(
4455+
controller_chain_spec_.begin(), controller_chain_spec_.end(),
4456+
[](auto & pair)
4457+
{
4458+
pair.second.following_controllers.clear();
4459+
pair.second.preceding_controllers.clear();
4460+
});
4461+
std::for_each(
4462+
controller_chained_reference_interfaces_cache_.begin(),
4463+
controller_chained_reference_interfaces_cache_.end(), [](auto & pair) { pair.second.clear(); });
4464+
std::for_each(
4465+
controller_chained_state_interfaces_cache_.begin(),
4466+
controller_chained_state_interfaces_cache_.end(), [](auto & pair) { pair.second.clear(); });
4467+
for (const auto & controller : controllers)
4468+
{
4469+
if (is_controller_unconfigured(*controller.c))
4470+
{
4471+
RCLCPP_DEBUG(
4472+
get_logger(), "Controller '%s' is unconfigured, skipping chain building.",
4473+
controller.info.name.c_str());
4474+
continue;
4475+
}
4476+
const auto cmd_itfs = controller.c->command_interface_configuration().names;
4477+
const auto state_itfs = controller.c->state_interface_configuration().names;
4478+
4479+
for (const auto & cmd_itf : cmd_itfs)
4480+
{
4481+
controller_manager::ControllersListIterator ctrl_it;
4482+
if (is_interface_a_chained_interface(cmd_itf, controllers, ctrl_it))
4483+
{
4484+
ros2_control::add_item(
4485+
controller_chain_spec_[controller.info.name].following_controllers, ctrl_it->info.name);
4486+
ros2_control::add_item(
4487+
controller_chain_spec_[ctrl_it->info.name].preceding_controllers, controller.info.name);
4488+
ros2_control::add_item(
4489+
controller_chained_reference_interfaces_cache_[ctrl_it->info.name], controller.info.name);
4490+
}
4491+
}
4492+
// This is needed when we start exporting the state interfaces from the controllers
4493+
for (const auto & state_itf : state_itfs)
4494+
{
4495+
controller_manager::ControllersListIterator ctrl_it;
4496+
if (is_interface_a_chained_interface(state_itf, controllers, ctrl_it))
4497+
{
4498+
ros2_control::add_item(
4499+
controller_chain_spec_[controller.info.name].preceding_controllers, ctrl_it->info.name);
4500+
ros2_control::add_item(
4501+
controller_chain_spec_[ctrl_it->info.name].following_controllers, controller.info.name);
4502+
ros2_control::add_item(
4503+
controller_chained_state_interfaces_cache_[ctrl_it->info.name], controller.info.name);
4504+
}
4505+
}
4506+
}
4507+
for (const auto & [controller_name, controller_chain] : controller_chain_spec_)
4508+
{
4509+
RCLCPP_DEBUG(
4510+
get_logger(), "Controller '%s' has %ld following controllers and %ld preceding controllers.",
4511+
controller_name.c_str(), controller_chain.following_controllers.size(),
4512+
controller_chain.preceding_controllers.size());
4513+
RCLCPP_DEBUG_EXPRESSION(
4514+
get_logger(), !controller_chain.following_controllers.empty(), "%s",
4515+
fmt::format(
4516+
"\tFollowing controllers are : {}", fmt::join(controller_chain.following_controllers, ", "))
4517+
.c_str());
4518+
RCLCPP_DEBUG_EXPRESSION(
4519+
get_logger(), !controller_chain.preceding_controllers.empty(), "%s",
4520+
fmt::format(
4521+
"\tPreceding controllers are : {}", fmt::join(controller_chain.preceding_controllers, ", "))
4522+
.c_str());
4523+
}
4524+
}
4525+
44014526
rclcpp::NodeOptions ControllerManager::determine_controller_node_options(
44024527
const ControllerSpec & controller) const
44034528
{

controller_manager/test/test_chainable_controller/test_chainable_controller.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ controller_interface::return_type TestChainableController::update_and_write_comm
129129
state_interfaces_values_[i] = state_interfaces_[i].get_optional().value();
130130
}
131131

132-
return controller_interface::return_type::OK;
132+
return update_return_value;
133133
}
134134

135135
CallbackReturn TestChainableController::on_init() { return CallbackReturn::SUCCESS; }

controller_manager/test/test_chainable_controller/test_chainable_controller.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class TestChainableController : public controller_interface::ChainableController
8181

8282
size_t internal_counter;
8383
bool fail_on_activate = false;
84+
controller_interface::return_type update_return_value = controller_interface::return_type::OK;
8485
controller_interface::InterfaceConfiguration cmd_iface_cfg_;
8586
controller_interface::InterfaceConfiguration state_iface_cfg_;
8687
std::vector<std::string> reference_interface_names_;

0 commit comments

Comments
 (0)