Skip to content

Commit

Permalink
Sort controllers while configuring instead of while activating (#1107)
Browse files Browse the repository at this point in the history
(cherry picked from commit 863f161)

# Conflicts:
#	controller_manager/src/controller_manager.cpp
#	controller_manager/test/test_controller_manager_srvs.cpp
  • Loading branch information
saikishor authored and mergify[bot] committed Nov 4, 2023
1 parent 553d0a5 commit 68da3b6
Show file tree
Hide file tree
Showing 6 changed files with 810 additions and 63 deletions.
239 changes: 238 additions & 1 deletion controller_manager/src/controller_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,121 @@ bool command_interface_is_reference_interface_of_controller(
return true;
}

<<<<<<< HEAD
=======
/**
* 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;
}

>>>>>>> 863f161 (Sort controllers while configuring instead of while activating (#1107))
} // namespace

namespace controller_manager
Expand Down Expand Up @@ -512,10 +627,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 +1076,10 @@ controller_interface::return_type ControllerManager::switch_controller(
controller.info.claimed_interfaces.clear();
}
}
<<<<<<< HEAD
=======

>>>>>>> 863f161 (Sort controllers while configuring instead of while activating (#1107))
// switch lists
rt_controllers_wrapper_.switch_updated_list(guard);
// clear unused list
Expand Down Expand Up @@ -2078,6 +2222,99 @@ controller_interface::return_type ControllerManager::check_preceeding_controller
}
}
return controller_interface::return_type::OK;
<<<<<<< HEAD
=======
}

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;
}
>>>>>>> 863f161 (Sort controllers while configuring instead of while activating (#1107))
};

} // 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
22 changes: 17 additions & 5 deletions controller_manager/test/test_controller_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,11 @@ TEST_P(TestControllerManagerWithStrictness, controller_lifecycle)
lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED, test_controller->get_state().id());

// configure controller
cm_->configure_controller(test_controller::TEST_CONTROLLER_NAME);
cm_->configure_controller(TEST_CONTROLLER2_NAME);
{
ControllerManagerRunner cm_runner(this);
cm_->configure_controller(test_controller::TEST_CONTROLLER_NAME);
cm_->configure_controller(TEST_CONTROLLER2_NAME);
}
EXPECT_EQ(
controller_interface::return_type::OK,
cm_->update(rclcpp::Time(0), rclcpp::Duration::from_seconds(0.01)));
Expand Down Expand Up @@ -217,7 +220,10 @@ TEST_P(TestControllerManagerWithStrictness, per_controller_update_rate)

test_controller->get_node()->set_parameter({"update_rate", 4});
// configure controller
cm_->configure_controller(test_controller::TEST_CONTROLLER_NAME);
{
ControllerManagerRunner cm_runner(this);
cm_->configure_controller(test_controller::TEST_CONTROLLER_NAME);
}
EXPECT_EQ(
controller_interface::return_type::OK,
cm_->update(rclcpp::Time(0), rclcpp::Duration::from_seconds(0.01)));
Expand Down Expand Up @@ -296,7 +302,10 @@ TEST_P(TestControllerManagerWithUpdateRates, per_controller_equal_and_higher_upd
rclcpp::Parameter update_rate_parameter("update_rate", static_cast<int>(ctrl_update_rate));
test_controller->get_node()->set_parameter(update_rate_parameter);
// configure controller
cm_->configure_controller(test_controller::TEST_CONTROLLER_NAME);
{
ControllerManagerRunner cm_runner(this);
cm_->configure_controller(test_controller::TEST_CONTROLLER_NAME);
}
EXPECT_EQ(
controller_interface::return_type::OK,
cm_->update(rclcpp::Time(0), rclcpp::Duration::from_seconds(0.01)));
Expand Down Expand Up @@ -390,7 +399,10 @@ TEST_P(TestControllerUpdateRates, check_the_controller_update_rate)

test_controller->get_node()->set_parameter({"update_rate", static_cast<int>(ctrl_update_rate)});
// configure controller
cm_->configure_controller(test_controller::TEST_CONTROLLER_NAME);
{
ControllerManagerRunner cm_runner(this);
cm_->configure_controller(test_controller::TEST_CONTROLLER_NAME);
}
EXPECT_EQ(
controller_interface::return_type::OK,
cm_->update(rclcpp::Time(0), rclcpp::Duration::from_seconds(0.01)));
Expand Down
Loading

0 comments on commit 68da3b6

Please sign in to comment.