Skip to content

Commit 4c58cda

Browse files
Fix spawner behaviour on failing controller activation or deactivation (#1941) (#1968)
--------- Co-authored-by: Sai Kishor Kothakota <sai.kishor@pal-robotics.com>
1 parent 5c5a171 commit 4c58cda

File tree

7 files changed

+200
-18
lines changed

7 files changed

+200
-18
lines changed

controller_manager/CMakeLists.txt

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -183,17 +183,13 @@ if(BUILD_TESTING)
183183
target_link_libraries(test_hardware_spawner ${PROJECT_NAME})
184184
ament_target_dependencies(test_hardware_spawner ros2_control_test_assets)
185185

186-
install(FILES test/test_controller_spawner_with_type.yaml
187-
DESTINATION test)
188-
189-
install(FILES test/test_controller_spawner_with_basic_controllers.yaml
190-
DESTINATION test)
191-
192-
install(FILES test/test_controller_overriding_parameters.yaml
193-
DESTINATION test)
194-
195-
install(FILES test/test_controller_spawner_wildcard_entries.yaml
196-
DESTINATION test)
186+
install(FILES
187+
test/test_controller_spawner_with_type.yaml
188+
test/test_controller_spawner_with_basic_controllers.yaml
189+
test/test_controller_overriding_parameters.yaml
190+
test/test_controller_spawner_wildcard_entries.yaml
191+
test/test_controller_spawner_with_interfaces.yaml
192+
DESTINATION test)
197193

198194
ament_add_gmock(
199195
test_hardware_management_srvs

controller_manager/controller_manager/spawner.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ def main(args=None):
265265
)
266266
if not ret.ok:
267267
node.get_logger().error(
268-
bcolors.FAIL + "Failed to activate controller" + bcolors.ENDC
268+
f"{bcolors.FAIL}Failed to activate controller : {controller_name}{bcolors.ENDC}"
269269
)
270270
return 1
271271

@@ -290,7 +290,7 @@ def main(args=None):
290290
)
291291
if not ret.ok:
292292
node.get_logger().error(
293-
bcolors.FAIL + "Failed to activate the parsed controllers list" + bcolors.ENDC
293+
f"{bcolors.FAIL}Failed to activate the parsed controllers list : {controller_names}{bcolors.ENDC}"
294294
)
295295
return 1
296296

controller_manager/src/controller_manager.cpp

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,6 +1264,7 @@ controller_interface::return_type ControllerManager::switch_controller(
12641264
to = controllers;
12651265

12661266
// update the claimed interface controller info
1267+
auto switch_result = controller_interface::return_type::OK;
12671268
for (auto & controller : to)
12681269
{
12691270
if (is_controller_active(controller.c))
@@ -1284,6 +1285,32 @@ controller_interface::return_type ControllerManager::switch_controller(
12841285
{
12851286
controller.info.claimed_interfaces.clear();
12861287
}
1288+
if (
1289+
std::find(activate_request_.begin(), activate_request_.end(), controller.info.name) !=
1290+
activate_request_.end())
1291+
{
1292+
if (!is_controller_active(controller.c))
1293+
{
1294+
RCLCPP_ERROR(
1295+
get_logger(), "Could not activate controller : '%s'", controller.info.name.c_str());
1296+
switch_result = controller_interface::return_type::ERROR;
1297+
}
1298+
}
1299+
/// @note The following is the case of the real controllers that are deactivated and doesn't
1300+
/// include the chained controllers that are deactivated and activated
1301+
if (
1302+
std::find(deactivate_request_.begin(), deactivate_request_.end(), controller.info.name) !=
1303+
deactivate_request_.end() &&
1304+
std::find(activate_request_.begin(), activate_request_.end(), controller.info.name) ==
1305+
activate_request_.end())
1306+
{
1307+
if (is_controller_active(controller.c))
1308+
{
1309+
RCLCPP_ERROR(
1310+
get_logger(), "Could not deactivate controller : '%s'", controller.info.name.c_str());
1311+
switch_result = controller_interface::return_type::ERROR;
1312+
}
1313+
}
12871314
}
12881315

12891316
// switch lists
@@ -1293,8 +1320,10 @@ controller_interface::return_type ControllerManager::switch_controller(
12931320

12941321
clear_requests();
12951322

1296-
RCLCPP_DEBUG(get_logger(), "Successfully switched controllers");
1297-
return controller_interface::return_type::OK;
1323+
RCLCPP_DEBUG_EXPRESSION(
1324+
get_logger(), switch_result == controller_interface::return_type::OK,
1325+
"Successfully switched controllers");
1326+
return switch_result;
12981327
}
12991328

13001329
controller_interface::ControllerInterfaceBaseSharedPtr ControllerManager::add_controller_impl(
@@ -1518,6 +1547,7 @@ void ControllerManager::activate_controllers()
15181547
get_logger(),
15191548
"Resource conflict for controller '%s'. Command interface '%s' is already claimed.",
15201549
controller_name.c_str(), command_interface.c_str());
1550+
command_loans.clear();
15211551
assignment_successful = false;
15221552
break;
15231553
}
@@ -1529,6 +1559,7 @@ void ControllerManager::activate_controllers()
15291559
{
15301560
RCLCPP_ERROR(
15311561
get_logger(), "Can't activate controller '%s': %s", controller_name.c_str(), e.what());
1562+
command_loans.clear();
15321563
assignment_successful = false;
15331564
break;
15341565
}

controller_manager/test/test_controller/test_controller.cpp

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ controller_interface::return_type TestController::update(
6464

6565
for (size_t i = 0; i < command_interfaces_.size(); ++i)
6666
{
67-
RCLCPP_INFO(
67+
RCLCPP_DEBUG(
6868
get_node()->get_logger(), "Setting value of command interface '%s' to %f",
6969
command_interfaces_[i].get_name().c_str(), external_commands_for_testing_[i]);
7070
command_interfaces_[i].set_value(external_commands_for_testing_[i]);
@@ -77,6 +77,36 @@ CallbackReturn TestController::on_init() { return CallbackReturn::SUCCESS; }
7777

7878
CallbackReturn TestController::on_configure(const rclcpp_lifecycle::State & /*previous_state*/)
7979
{
80+
auto ctrl_node = get_node();
81+
if (!ctrl_node->has_parameter("command_interfaces"))
82+
{
83+
ctrl_node->declare_parameter("command_interfaces", std::vector<std::string>({}));
84+
}
85+
if (!ctrl_node->has_parameter("state_interfaces"))
86+
{
87+
ctrl_node->declare_parameter("state_interfaces", std::vector<std::string>({}));
88+
}
89+
const std::vector<std::string> command_interfaces =
90+
ctrl_node->get_parameter("command_interfaces").as_string_array();
91+
const std::vector<std::string> state_interfaces =
92+
ctrl_node->get_parameter("state_interfaces").as_string_array();
93+
if (!command_interfaces.empty() || !state_interfaces.empty())
94+
{
95+
cmd_iface_cfg_.names.clear();
96+
state_iface_cfg_.names.clear();
97+
for (const auto & cmd_itf : command_interfaces)
98+
{
99+
cmd_iface_cfg_.names.push_back(cmd_itf);
100+
}
101+
cmd_iface_cfg_.type = controller_interface::interface_configuration_type::INDIVIDUAL;
102+
external_commands_for_testing_.resize(command_interfaces.size(), 0.0);
103+
for (const auto & state_itf : state_interfaces)
104+
{
105+
state_iface_cfg_.names.push_back(state_itf);
106+
}
107+
state_iface_cfg_.type = controller_interface::interface_configuration_type::INDIVIDUAL;
108+
}
109+
80110
return CallbackReturn::SUCCESS;
81111
}
82112

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
ctrl_with_joint2_command_interface:
2+
ros__parameters:
3+
type: "controller_manager/test_controller"
4+
command_interfaces:
5+
- "joint2/velocity"
6+
7+
ctrl_with_joint1_command_interface:
8+
ros__parameters:
9+
type: "controller_manager/test_controller"
10+
command_interfaces:
11+
- "joint1/position"
12+
13+
ctrl_with_joint1_and_joint2_command_interfaces:
14+
ros__parameters:
15+
type: "controller_manager/test_controller"
16+
command_interfaces:
17+
- "joint1/position"
18+
- "joint2/velocity"
19+
20+
ctrl_with_state_interfaces:
21+
ros__parameters:
22+
type: "controller_manager/test_controller"
23+
state_interfaces:
24+
- "joint1/position"
25+
- "joint2/position"

controller_manager/test/test_release_interfaces.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ TEST_F(TestReleaseInterfaces, switch_controllers_same_interface)
8686
ASSERT_EQ(std::future_status::timeout, switch_future.wait_for(std::chrono::milliseconds(100)))
8787
<< "switch_controller should be blocking until next update cycle";
8888
ControllerManagerRunner cm_runner(this);
89-
EXPECT_EQ(controller_interface::return_type::OK, switch_future.get());
89+
EXPECT_EQ(controller_interface::return_type::ERROR, switch_future.get());
9090
ASSERT_EQ(
9191
lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE,
9292
abstract_test_controller1.c->get_state().id());
@@ -190,7 +190,7 @@ TEST_F(TestReleaseInterfaces, switch_controllers_same_interface)
190190
ASSERT_EQ(std::future_status::timeout, switch_future.wait_for(std::chrono::milliseconds(100)))
191191
<< "switch_controller should be blocking until next update cycle";
192192
ControllerManagerRunner cm_runner(this);
193-
EXPECT_EQ(controller_interface::return_type::OK, switch_future.get());
193+
EXPECT_EQ(controller_interface::return_type::ERROR, switch_future.get());
194194
ASSERT_EQ(
195195
lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE,
196196
abstract_test_controller1.c->get_state().id());

controller_manager/test/test_spawner_unspawner.cpp

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,106 @@ TEST_F(TestLoadController, spawner_test_with_wildcard_entries_with_no_ctrl_name)
411411
verify_ctrl_parameter(wildcard_ctrl_3.c->get_node(), true);
412412
}
413413

414+
TEST_F(TestLoadController, spawner_test_failed_activation_of_controllers)
415+
{
416+
const std::string test_file_path = ament_index_cpp::get_package_prefix("controller_manager") +
417+
"/test/test_controller_spawner_with_interfaces.yaml";
418+
419+
ControllerManagerRunner cm_runner(this);
420+
// Provide controller type via the parsed file
421+
EXPECT_EQ(
422+
call_spawner(
423+
"ctrl_with_joint1_command_interface ctrl_with_joint2_command_interface -c "
424+
"test_controller_manager "
425+
"--controller-manager-timeout 1.0 "
426+
"-p " +
427+
test_file_path),
428+
0);
429+
430+
ASSERT_EQ(cm_->get_loaded_controllers().size(), 2ul);
431+
432+
auto ctrl_with_joint2_command_interface = cm_->get_loaded_controllers()[1];
433+
ASSERT_EQ(ctrl_with_joint2_command_interface.info.name, "ctrl_with_joint2_command_interface");
434+
ASSERT_EQ(
435+
ctrl_with_joint2_command_interface.info.type, test_controller::TEST_CONTROLLER_CLASS_NAME);
436+
EXPECT_EQ(
437+
ctrl_with_joint2_command_interface.c->get_state().id(),
438+
lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE);
439+
ASSERT_EQ(
440+
ctrl_with_joint2_command_interface.c->command_interface_configuration().names.size(), 1ul);
441+
ASSERT_THAT(
442+
ctrl_with_joint2_command_interface.c->command_interface_configuration().names,
443+
std::vector<std::string>({"joint2/velocity"}));
444+
445+
auto ctrl_with_joint1_command_interface = cm_->get_loaded_controllers()[0];
446+
ASSERT_EQ(ctrl_with_joint1_command_interface.info.name, "ctrl_with_joint1_command_interface");
447+
ASSERT_EQ(
448+
ctrl_with_joint1_command_interface.info.type, test_controller::TEST_CONTROLLER_CLASS_NAME);
449+
EXPECT_EQ(
450+
ctrl_with_joint1_command_interface.c->get_state().id(),
451+
lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE);
452+
ASSERT_EQ(
453+
ctrl_with_joint1_command_interface.c->command_interface_configuration().names.size(), 1ul);
454+
ASSERT_THAT(
455+
ctrl_with_joint1_command_interface.c->command_interface_configuration().names,
456+
std::vector<std::string>({"joint1/position"}));
457+
458+
EXPECT_EQ(
459+
call_spawner(
460+
"ctrl_with_joint1_and_joint2_command_interfaces -c test_controller_manager "
461+
"--controller-manager-timeout 1.0 "
462+
"-p " +
463+
test_file_path),
464+
256)
465+
<< "Should fail as the ctrl_with_joint1_command_interface and "
466+
"ctrl_with_joint2_command_interface are active";
467+
468+
ASSERT_EQ(cm_->get_loaded_controllers().size(), 3ul);
469+
470+
auto ctrl_with_joint1_and_joint2_command_interfaces = cm_->get_loaded_controllers()[2];
471+
ASSERT_EQ(
472+
ctrl_with_joint1_and_joint2_command_interfaces.info.name,
473+
"ctrl_with_joint1_and_joint2_command_interfaces");
474+
ASSERT_EQ(
475+
ctrl_with_joint1_and_joint2_command_interfaces.info.type,
476+
test_controller::TEST_CONTROLLER_CLASS_NAME);
477+
ASSERT_EQ(
478+
ctrl_with_joint1_and_joint2_command_interfaces.c->get_state().id(),
479+
lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE);
480+
ASSERT_EQ(
481+
ctrl_with_joint1_and_joint2_command_interfaces.c->command_interface_configuration()
482+
.names.size(),
483+
2ul);
484+
ASSERT_THAT(
485+
ctrl_with_joint1_and_joint2_command_interfaces.c->command_interface_configuration().names,
486+
std::vector<std::string>({"joint1/position", "joint2/velocity"}));
487+
488+
EXPECT_EQ(call_unspawner("ctrl_with_joint1_command_interface -c test_controller_manager"), 0);
489+
490+
ASSERT_EQ(cm_->get_loaded_controllers().size(), 2ul);
491+
EXPECT_EQ(
492+
call_spawner(
493+
"ctrl_with_joint1_and_joint2_command_interfaces -c test_controller_manager "
494+
"--controller-manager-timeout 1.0 "
495+
"-p " +
496+
test_file_path),
497+
256)
498+
<< "Should fail as the ctrl_with_joint2_command_interface is still active";
499+
500+
EXPECT_EQ(call_unspawner("ctrl_with_joint2_command_interface -c test_controller_manager"), 0);
501+
502+
ASSERT_EQ(cm_->get_loaded_controllers().size(), 1ul);
503+
EXPECT_EQ(
504+
call_spawner(
505+
"ctrl_with_joint1_and_joint2_command_interfaces -c test_controller_manager "
506+
"--controller-manager-timeout 1.0 "
507+
"-p " +
508+
test_file_path),
509+
0)
510+
<< "Should pass as the ctrl_with_joint1_command_interface and "
511+
"ctrl_with_joint2_command_interface are inactive";
512+
}
513+
414514
TEST_F(TestLoadController, unload_on_kill)
415515
{
416516
// Launch spawner with unload on kill

0 commit comments

Comments
 (0)