Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Humble] Controller sorting #1157

Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,29 @@ class ControllerManager : public rclcpp::Node
const std::vector<ControllerSpec> & controllers, int strictness,
const ControllersListIterator controller_it);

/// A method to be used in the std::sort method to sort the controllers to be able to
/// execute them in a proper order
/**
* Compares the controllers ctrl_a and ctrl_b and then returns which comes first in the sequence
*
* @note The following conditions needs to be handled while ordering the controller list
* 1. The controllers that do not use any state or command interfaces are updated first
* 2. The controllers that use only the state system interfaces only are updated next
* 3. The controllers that use any of an another controller's reference interface are updated
* before the preceding controller
* 4. The controllers that use the controller's estimated interfaces are updated after the
* preceding controller
* 5. The controllers that only use the hardware command interfaces are updated last
* 6. All inactive controllers go at the end of the list
*
* \param[in] controllers list of controllers to compare their names to interface's prefix.
*
* @return true, if ctrl_a needs to execute first, else false
*/
bool controller_sorting(
const ControllerSpec & ctrl_a, const ControllerSpec & ctrl_b,
const std::vector<controller_manager::ControllerSpec> & controllers);

std::shared_ptr<rclcpp::Executor> executor_;

std::shared_ptr<pluginlib::ClassLoader<controller_interface::ControllerInterface>> loader_;
Expand Down
230 changes: 229 additions & 1 deletion controller_manager/src/controller_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,118 @@ bool command_interface_is_reference_interface_of_controller(
return true;
}

/**
* A method to retrieve the names of all it's following controllers given a controller name
* For instance, for the following case
* A -> B -> C -> D
* When called with B, returns C and D
* NOTE: A -> B signifies that the controller A is utilizing the reference interfaces exported from
* the controller B (or) the controller B is utilizing the expected interfaces exported from the
* controller A
*
* @param controller_name - Name of the controller for checking the tree
* \param[in] controllers list of controllers to compare their names to interface's prefix.
* @return list of controllers that are following the given controller in a chain. If none, return
* empty.
*/
std::vector<std::string> get_following_controller_names(
const std::string controller_name,
const std::vector<controller_manager::ControllerSpec> & controllers)
{
std::vector<std::string> following_controllers;
auto controller_it = std::find_if(
controllers.begin(), controllers.end(),
std::bind(controller_name_compare, std::placeholders::_1, controller_name));
if (controller_it == controllers.end())
{
RCLCPP_DEBUG(
rclcpp::get_logger("ControllerManager::utils"),
"Required controller : '%s' is not found in the controller list ", controller_name.c_str());

return following_controllers;
}
const auto cmd_itfs = controller_it->c->command_interface_configuration().names;
for (const auto & itf : cmd_itfs)
{
controller_manager::ControllersListIterator ctrl_it;
if (command_interface_is_reference_interface_of_controller(itf, controllers, ctrl_it))
{
RCLCPP_DEBUG(
rclcpp::get_logger("ControllerManager::utils"),
"The interface is a reference interface of controller : %s", ctrl_it->info.name.c_str());
following_controllers.push_back(ctrl_it->info.name);
const std::vector<std::string> ctrl_names =
get_following_controller_names(ctrl_it->info.name, controllers);
for (const std::string & controller : ctrl_names)
{
if (
std::find(following_controllers.begin(), following_controllers.end(), controller) ==
following_controllers.end())
{
// Only add to the list if it doesn't exist
following_controllers.push_back(controller);
}
}
}
}
return following_controllers;
}

/**
* A method to retrieve the names of all it's preceding controllers given a controller name
* For instance, for the following case
* A -> B -> C -> D
* When called with C, returns A and B
* NOTE: A -> B signifies that the controller A is utilizing the reference interfaces exported from
* the controller B (or) the controller B is utilizing the expected interfaces exported from the
* controller A
*
* @param controller_name - Name of the controller for checking the tree
* \param[in] controllers list of controllers to compare their names to interface's prefix.
* @return list of controllers that are preceding the given controller in a chain. If none, return
* empty.
*/
std::vector<std::string> get_preceding_controller_names(
const std::string controller_name,
const std::vector<controller_manager::ControllerSpec> & controllers)
{
std::vector<std::string> preceding_controllers;
auto controller_it = std::find_if(
controllers.begin(), controllers.end(),
std::bind(controller_name_compare, std::placeholders::_1, controller_name));
if (controller_it == controllers.end())
{
RCLCPP_DEBUG(
rclcpp::get_logger("ControllerManager::utils"),
"Required controller : '%s' is not found in the controller list ", controller_name.c_str());
return preceding_controllers;
}
for (const auto & ctrl : controllers)
{
auto cmd_itfs = ctrl.c->command_interface_configuration().names;
for (const auto & itf : cmd_itfs)
{
auto split_pos = itf.find_first_of('/');
if ((split_pos != std::string::npos) && (itf.substr(0, split_pos) == controller_name))
{
preceding_controllers.push_back(ctrl.info.name);
auto ctrl_names = get_preceding_controller_names(ctrl.info.name, controllers);
for (const std::string & controller : ctrl_names)
{
if (
std::find(preceding_controllers.begin(), preceding_controllers.end(), controller) ==
preceding_controllers.end())
{
// Only add to the list if it doesn't exist
preceding_controllers.push_back(controller);
}
}
}
}
}
return preceding_controllers;
}

} // namespace

namespace controller_manager
Expand Down Expand Up @@ -512,10 +624,35 @@ controller_interface::return_type ControllerManager::configure_controller(
return controller_interface::return_type::ERROR;
}
resource_manager_->import_controller_reference_interfaces(controller_name, interfaces);
}

// Now let's reorder the controllers
// lock controllers
std::lock_guard<std::recursive_mutex> guard(rt_controllers_wrapper_.controllers_lock_);
std::vector<ControllerSpec> & to = rt_controllers_wrapper_.get_unused_list(guard);
const std::vector<ControllerSpec> & from = rt_controllers_wrapper_.get_updated_list(guard);

// TODO(destogl): check and resort controllers in the vector
// Copy all controllers from the 'from' list to the 'to' list
to = from;

// Reordering the controllers
std::sort(
to.begin(), to.end(),
std::bind(
&ControllerManager::controller_sorting, this, std::placeholders::_1, std::placeholders::_2,
to));

RCLCPP_DEBUG(get_logger(), "Reordered controllers list is:");
for (const auto & ctrl : to)
{
RCLCPP_DEBUG(this->get_logger(), "\t%s", ctrl.info.name.c_str());
}

// switch lists
rt_controllers_wrapper_.switch_updated_list(guard);
// clear unused list
rt_controllers_wrapper_.get_unused_list(guard).clear();

return controller_interface::return_type::OK;
}

Expand Down Expand Up @@ -936,6 +1073,7 @@ controller_interface::return_type ControllerManager::switch_controller(
controller.info.claimed_interfaces.clear();
}
}

// switch lists
rt_controllers_wrapper_.switch_updated_list(guard);
// clear unused list
Expand Down Expand Up @@ -2078,6 +2216,96 @@ controller_interface::return_type ControllerManager::check_preceeding_controller
}
}
return controller_interface::return_type::OK;
}

bool ControllerManager::controller_sorting(
const ControllerSpec & ctrl_a, const ControllerSpec & ctrl_b,
const std::vector<controller_manager::ControllerSpec> & controllers)
{
const std::vector<std::string> cmd_itfs = ctrl_a.c->command_interface_configuration().names;
const std::vector<std::string> state_itfs = ctrl_a.c->state_interface_configuration().names;
if (cmd_itfs.empty() || !ctrl_a.c->is_chainable())
{
// The case of the controllers that don't have any command interfaces. For instance,
// joint_state_broadcaster
return true;
}
else
{
auto following_ctrls = get_following_controller_names(ctrl_a.info.name, controllers);
if (following_ctrls.empty()) return false;
// If the ctrl_b is any of the following controllers of ctrl_a, then place ctrl_a before ctrl_b
if (
std::find(following_ctrls.begin(), following_ctrls.end(), ctrl_b.info.name) !=
following_ctrls.end())
return true;
else
{
auto ctrl_a_preceding_ctrls = get_preceding_controller_names(ctrl_a.info.name, controllers);
// This is to check that the ctrl_b is in the preceding controllers list of ctrl_a - This
// check is useful when there is a chained controller branching, but they belong to same
// branch
if (
std::find(ctrl_a_preceding_ctrls.begin(), ctrl_a_preceding_ctrls.end(), ctrl_b.info.name) !=
ctrl_a_preceding_ctrls.end())
{
return false;
}

// This is to handle the cases where, the parsed ctrl_a and ctrl_b are not directly related
// but might have a common parent - happens in branched chained controller
auto ctrl_b_preceding_ctrls = get_preceding_controller_names(ctrl_b.info.name, controllers);
std::sort(ctrl_a_preceding_ctrls.begin(), ctrl_a_preceding_ctrls.end());
std::sort(ctrl_b_preceding_ctrls.begin(), ctrl_b_preceding_ctrls.end());
std::list<std::string> intersection;
std::set_intersection(
ctrl_a_preceding_ctrls.begin(), ctrl_a_preceding_ctrls.end(),
ctrl_b_preceding_ctrls.begin(), ctrl_b_preceding_ctrls.end(),
std::back_inserter(intersection));
if (!intersection.empty())
{
// If there is an intersection, then there is a common parent controller for both ctrl_a and
// ctrl_b
return true;
}

// If there is no common parent, then they belong to 2 different sets
auto following_ctrls_b = get_following_controller_names(ctrl_b.info.name, controllers);
if (following_ctrls_b.empty()) return true;
auto find_first_element = [&](const auto & controllers_list)
{
auto it = std::find_if(
controllers.begin(), controllers.end(),
std::bind(controller_name_compare, std::placeholders::_1, controllers_list.back()));
if (it != controllers.end())
{
int dist = std::distance(controllers.begin(), it);
return dist;
}
};
const int ctrl_a_chain_first_controller = find_first_element(following_ctrls);
const int ctrl_b_chain_first_controller = find_first_element(following_ctrls_b);
if (ctrl_a_chain_first_controller < ctrl_b_chain_first_controller) return true;
}

// If the ctrl_a's state interface is the one exported by the ctrl_b then ctrl_b should be
// infront of ctrl_a
// TODO(saikishor): deal with the state interface chaining in the sorting algorithm
auto state_it = std::find_if(
state_itfs.begin(), state_itfs.end(),
[ctrl_b](auto itf)
{
auto index = itf.find_first_of('/');
return ((index != std::string::npos) && (itf.substr(0, index) == ctrl_b.info.name));
});
if (state_it != state_itfs.end())
{
return false;
}

// The rest of the cases, basically end up at the end of the list
return false;
}
};

} // namespace controller_manager
Original file line number Diff line number Diff line change
Expand Up @@ -32,33 +32,13 @@ TestChainableController::TestChainableController()
controller_interface::InterfaceConfiguration
TestChainableController::command_interface_configuration() const
{
if (
get_state().id() == lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE ||
get_state().id() == lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE)
{
return cmd_iface_cfg_;
}
else
{
throw std::runtime_error(
"Can not get command interface configuration until the controller is configured.");
}
return cmd_iface_cfg_;
}

controller_interface::InterfaceConfiguration
TestChainableController::state_interface_configuration() const
{
if (
get_state().id() == lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE ||
get_state().id() == lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE)
{
return state_iface_cfg_;
}
else
{
throw std::runtime_error(
"Can not get state interface configuration until the controller is configured.");
}
return state_iface_cfg_;
}

controller_interface::return_type TestChainableController::update_reference_from_subscribers()
Expand Down
24 changes: 2 additions & 22 deletions controller_manager/test/test_controller/test_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,32 +29,12 @@ TestController::TestController()

controller_interface::InterfaceConfiguration TestController::command_interface_configuration() const
{
if (
get_state().id() == lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE ||
get_state().id() == lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE)
{
return cmd_iface_cfg_;
}
else
{
throw std::runtime_error(
"Can not get command interface configuration until the controller is configured.");
}
return cmd_iface_cfg_;
}

controller_interface::InterfaceConfiguration TestController::state_interface_configuration() const
{
if (
get_state().id() == lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE ||
get_state().id() == lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE)
{
return state_iface_cfg_;
}
else
{
throw std::runtime_error(
"Can not get state interface configuration until the controller is configured.");
}
return state_iface_cfg_;
}

controller_interface::return_type TestController::update(
Expand Down
Loading
Loading