Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions controller_manager/doc/userdoc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -409,13 +409,14 @@ Hardware and Controller Errors
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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.
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.
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.

Factors that affect Determinism
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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:

* 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.
* 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.
* 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.

Support for Asynchronous Updates
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,14 @@ class ControllerManager : public rclcpp::Node
const std::string & ctrl_name, std::vector<std::string>::iterator controller_iterator,
bool append_to_controller);

/**
* @brief Build the controller chain topology information based on the provided controllers.
* This method constructs a directed graph representing the dependencies between controllers.
* It analyzes the relationships between controllers, such as which controllers depend on others,
* and builds a directed graph to represent these dependencies.
*/
void build_controllers_topology_info(const std::vector<ControllerSpec> & controllers);

/**
* @brief Method to publish the state of the controller manager.
* The state includes the list of controllers and the list of hardware interfaces along with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ struct ControllerSpec
hardware_interface::ControllerInfo info;
controller_interface::ControllerInterfaceBaseSharedPtr c;
std::shared_ptr<rclcpp::Time> last_update_cycle_time;
std::vector<std::string> controllers_chain_group = {};
std::shared_ptr<MovingAverageStatistics> execution_time_statistics;
std::shared_ptr<MovingAverageStatistics> periodicity_statistics;
};
Expand Down
181 changes: 153 additions & 28 deletions controller_manager/src/controller_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,52 @@ void get_controller_list_command_interfaces(
}
}

void get_full_chain_spec(
const std::string & controller_name,
const std::unordered_map<std::string, controller_manager::ControllerChainSpec> & chain_spec,
std::vector<std::string> & full_chain_list)
{
auto it = chain_spec.find(controller_name);
if (it == chain_spec.end())
{
RCLCPP_WARN(
rclcpp::get_logger("ControllerManager::utils"),
"Controller '%s' not found in the controller chain specification.", controller_name.c_str());
return;
}
ros2_control::add_item(full_chain_list, controller_name);
for (const auto & following_controller : it->second.following_controllers)
{
if (!ros2_control::has_item(full_chain_list, following_controller))
{
get_full_chain_spec(following_controller, chain_spec, full_chain_list);
}
}
for (const auto & preceding_controller : it->second.preceding_controllers)
{
if (!ros2_control::has_item(full_chain_list, preceding_controller))
{
get_full_chain_spec(preceding_controller, chain_spec, full_chain_list);
}
}
}

void build_controller_full_chain_map_cache(
const std::unordered_map<std::string, controller_manager::ControllerChainSpec> &
controller_chain_spec,
std::unordered_map<std::string, std::vector<std::string>> & controller_full_chain_info_cache)
{
controller_full_chain_info_cache.clear();
for (const auto & [controller_name, chain_spec] : controller_chain_spec)
{
controller_full_chain_info_cache[controller_name] = {};
// add the controller itself to the list
ros2_control::add_item(controller_full_chain_info_cache[controller_name], controller_name);
get_full_chain_spec(
controller_name, controller_chain_spec, controller_full_chain_info_cache[controller_name]);
}
}

void register_controller_manager_statistics(
const std::string & name,
const libstatistics_collector::moving_average_statistics::StatisticData * variable)
Expand Down Expand Up @@ -1346,33 +1392,10 @@ controller_interface::return_type ControllerManager::configure_controller(
return controller_interface::return_type::ERROR;
}

for (const auto & cmd_itf : cmd_itfs)
{
controller_manager::ControllersListIterator ctrl_it;
if (is_interface_a_chained_interface(cmd_itf, controllers, ctrl_it))
{
ros2_control::add_item(
controller_chain_spec_[controller_name].following_controllers, ctrl_it->info.name);
ros2_control::add_item(
controller_chain_spec_[ctrl_it->info.name].preceding_controllers, controller_name);
ros2_control::add_item(
controller_chained_reference_interfaces_cache_[ctrl_it->info.name], controller_name);
}
}
// This is needed when we start exporting the state interfaces from the controllers
for (const auto & state_itf : state_itfs)
{
controller_manager::ControllersListIterator ctrl_it;
if (is_interface_a_chained_interface(state_itf, controllers, ctrl_it))
{
ros2_control::add_item(
controller_chain_spec_[controller_name].preceding_controllers, ctrl_it->info.name);
ros2_control::add_item(
controller_chain_spec_[ctrl_it->info.name].following_controllers, controller_name);
ros2_control::add_item(
controller_chained_state_interfaces_cache_[ctrl_it->info.name], controller_name);
}
}
build_controllers_topology_info(controllers);

std::unordered_map<std::string, std::vector<std::string>> controller_full_chain_info_cache;
build_controller_full_chain_map_cache(controller_chain_spec_, controller_full_chain_info_cache);

// Now let's reorder the controllers
// lock controllers
Expand Down Expand Up @@ -1407,6 +1430,23 @@ controller_interface::return_type ControllerManager::configure_controller(
}
}

// Update the controllers chain groups in the ControllerSpec
for (const auto & [ctrl_name, full_chain_info] : controller_full_chain_info_cache)
{
RCLCPP_DEBUG(
get_logger(), "%s",
fmt::format(
"The controller '{}' is in chain with: [{}]", ctrl_name, fmt::join(full_chain_info, ", "))
.c_str());
auto controller_it = std::find_if(
new_list.begin(), new_list.end(),
std::bind(controller_name_compare, std::placeholders::_1, ctrl_name));
if (controller_it != new_list.end())
{
controller_it->controllers_chain_group = full_chain_info;
}
}

to = new_list;
RCLCPP_DEBUG(get_logger(), "Reordered controllers list is:");
for (const auto & ctrl : to)
Expand Down Expand Up @@ -2154,6 +2194,7 @@ void ControllerManager::deactivate_controllers(
// deactivation
if (controller->is_chainable())
{
controller->set_chained_mode(false);
resource_manager_->make_controller_exported_state_interfaces_unavailable(controller_name);
resource_manager_->make_controller_reference_interfaces_unavailable(controller_name);
}
Expand Down Expand Up @@ -3096,7 +3137,16 @@ controller_interface::return_type ControllerManager::update(

if (controller_ret != controller_interface::return_type::OK)
{
rt_buffer_.deactivate_controllers_list.push_back(loaded_controller.info.name);
const std::vector<std::string> & controller_chain =
loaded_controller.controllers_chain_group;
RCLCPP_INFO_EXPRESSION(
get_logger(), controller_chain.size() > 1,
"Controller '%s' is part of a chain of %lu controllers that will be deactivated.",
loaded_controller.info.name.c_str(), controller_chain.size());
for (const auto & chained_controller : controller_chain)
{
ros2_control::add_item(rt_buffer_.deactivate_controllers_list, chained_controller);
}
ret = controller_ret;
}
}
Expand Down Expand Up @@ -4398,6 +4448,81 @@ void ControllerManager::update_list_with_controller_chain(
}
}

void ControllerManager::build_controllers_topology_info(
const std::vector<ControllerSpec> & controllers)
{
std::for_each(
controller_chain_spec_.begin(), controller_chain_spec_.end(),
[](auto & pair)
{
pair.second.following_controllers.clear();
pair.second.preceding_controllers.clear();
});
std::for_each(
controller_chained_reference_interfaces_cache_.begin(),
controller_chained_reference_interfaces_cache_.end(), [](auto & pair) { pair.second.clear(); });
std::for_each(
controller_chained_state_interfaces_cache_.begin(),
controller_chained_state_interfaces_cache_.end(), [](auto & pair) { pair.second.clear(); });
for (const auto & controller : controllers)
{
if (is_controller_unconfigured(*controller.c))
{
RCLCPP_DEBUG(
get_logger(), "Controller '%s' is unconfigured, skipping chain building.",
controller.info.name.c_str());
continue;
}
const auto cmd_itfs = controller.c->command_interface_configuration().names;
const auto state_itfs = controller.c->state_interface_configuration().names;

for (const auto & cmd_itf : cmd_itfs)
{
controller_manager::ControllersListIterator ctrl_it;
if (is_interface_a_chained_interface(cmd_itf, controllers, ctrl_it))
{
ros2_control::add_item(
controller_chain_spec_[controller.info.name].following_controllers, ctrl_it->info.name);
ros2_control::add_item(
controller_chain_spec_[ctrl_it->info.name].preceding_controllers, controller.info.name);
ros2_control::add_item(
controller_chained_reference_interfaces_cache_[ctrl_it->info.name], controller.info.name);
}
}
// This is needed when we start exporting the state interfaces from the controllers
for (const auto & state_itf : state_itfs)
{
controller_manager::ControllersListIterator ctrl_it;
if (is_interface_a_chained_interface(state_itf, controllers, ctrl_it))
{
ros2_control::add_item(
controller_chain_spec_[controller.info.name].preceding_controllers, ctrl_it->info.name);
ros2_control::add_item(
controller_chain_spec_[ctrl_it->info.name].following_controllers, controller.info.name);
ros2_control::add_item(
controller_chained_state_interfaces_cache_[ctrl_it->info.name], controller.info.name);
}
}
}
for (const auto & [controller_name, controller_chain] : controller_chain_spec_)
{
RCLCPP_DEBUG(
get_logger(), "Controller '%s' has %ld following controllers and %ld preceding controllers.",
controller_name.c_str(), controller_chain.following_controllers.size(),
controller_chain.preceding_controllers.size());
RCLCPP_DEBUG_EXPRESSION(
get_logger(), !controller_chain.following_controllers.empty(), "%s",
fmt::format(
"\tFollowing controllers are : {}", fmt::join(controller_chain.following_controllers, ", "))
.c_str());
RCLCPP_DEBUG_EXPRESSION(
get_logger(), !controller_chain.preceding_controllers.empty(), "%s",
fmt::format(
"\tPreceding controllers are : {}", fmt::join(controller_chain.preceding_controllers, ", "))
.c_str());
}
}

rclcpp::NodeOptions ControllerManager::determine_controller_node_options(
const ControllerSpec & controller) const
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ controller_interface::return_type TestChainableController::update_and_write_comm
state_interfaces_values_[i] = state_interfaces_[i].get_optional().value();
}

return controller_interface::return_type::OK;
return update_return_value;
}

CallbackReturn TestChainableController::on_init() { return CallbackReturn::SUCCESS; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class TestChainableController : public controller_interface::ChainableController

size_t internal_counter;
bool fail_on_activate = false;
controller_interface::return_type update_return_value = controller_interface::return_type::OK;
controller_interface::InterfaceConfiguration cmd_iface_cfg_;
controller_interface::InterfaceConfiguration state_iface_cfg_;
std::vector<std::string> reference_interface_names_;
Expand Down
Loading