Skip to content

Commit 675a7a9

Browse files
[CM] Fix the controller deactivation on the control cycles returning ERROR (#1756)
* Added a method to get the failed controller command interfaces from the list * prepare and perform mode switch in the read cycle on returning error on failed controllers * Add test_actuator_exclusive_interfaces to avoid locking of resources if they interface stay claimed --------- Co-authored-by: Christoph Fröhlich <[email protected]>
1 parent 465e5d0 commit 675a7a9

File tree

7 files changed

+439
-23
lines changed

7 files changed

+439
-23
lines changed

controller_manager/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ if(BUILD_TESTING)
187187
)
188188
target_link_libraries(test_release_interfaces
189189
controller_manager
190+
test_controller
190191
test_controller_with_interfaces
191192
ros2_control_test_assets::ros2_control_test_assets
192193
)

controller_manager/src/controller_manager.cpp

Lines changed: 62 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,44 @@ void get_active_controllers_using_command_interfaces_of_controller(
214214
}
215215
}
216216

217+
void extract_command_interfaces_for_controller(
218+
const controller_manager::ControllerSpec & ctrl,
219+
const hardware_interface::ResourceManager & resource_manager,
220+
std::vector<std::string> & request_interface_list)
221+
{
222+
auto command_interface_config = ctrl.c->command_interface_configuration();
223+
std::vector<std::string> command_interface_names = {};
224+
if (command_interface_config.type == controller_interface::interface_configuration_type::ALL)
225+
{
226+
command_interface_names = resource_manager.available_command_interfaces();
227+
}
228+
if (
229+
command_interface_config.type == controller_interface::interface_configuration_type::INDIVIDUAL)
230+
{
231+
command_interface_names = command_interface_config.names;
232+
}
233+
request_interface_list.insert(
234+
request_interface_list.end(), command_interface_names.begin(), command_interface_names.end());
235+
}
236+
237+
void get_controller_list_command_interfaces(
238+
const std::vector<std::string> & controllers_list,
239+
const std::vector<controller_manager::ControllerSpec> & controllers_spec,
240+
const hardware_interface::ResourceManager & resource_manager,
241+
std::vector<std::string> & request_interface_list)
242+
{
243+
for (const auto & controller_name : controllers_list)
244+
{
245+
auto found_it = std::find_if(
246+
controllers_spec.begin(), controllers_spec.end(),
247+
std::bind(controller_name_compare, std::placeholders::_1, controller_name));
248+
if (found_it != controllers_spec.end())
249+
{
250+
extract_command_interfaces_for_controller(
251+
*found_it, resource_manager, request_interface_list);
252+
}
253+
}
254+
}
217255
} // namespace
218256

219257
namespace controller_manager
@@ -1411,33 +1449,15 @@ controller_interface::return_type ControllerManager::switch_controller(
14111449
activate_request_.erase(activate_list_it);
14121450
}
14131451

1414-
const auto extract_interfaces_for_controller =
1415-
[this](const ControllerSpec ctrl, std::vector<std::string> & request_interface_list)
1416-
{
1417-
auto command_interface_config = ctrl.c->command_interface_configuration();
1418-
std::vector<std::string> command_interface_names = {};
1419-
if (command_interface_config.type == controller_interface::interface_configuration_type::ALL)
1420-
{
1421-
command_interface_names = resource_manager_->available_command_interfaces();
1422-
}
1423-
if (
1424-
command_interface_config.type ==
1425-
controller_interface::interface_configuration_type::INDIVIDUAL)
1426-
{
1427-
command_interface_names = command_interface_config.names;
1428-
}
1429-
request_interface_list.insert(
1430-
request_interface_list.end(), command_interface_names.begin(),
1431-
command_interface_names.end());
1432-
};
1433-
14341452
if (in_activate_list)
14351453
{
1436-
extract_interfaces_for_controller(controller, activate_command_interface_request_);
1454+
extract_command_interfaces_for_controller(
1455+
controller, *resource_manager_, activate_command_interface_request_);
14371456
}
14381457
if (in_deactivate_list)
14391458
{
1440-
extract_interfaces_for_controller(controller, deactivate_command_interface_request_);
1459+
extract_command_interfaces_for_controller(
1460+
controller, *resource_manager_, deactivate_command_interface_request_);
14411461
}
14421462

14431463
// cache mapping between hardware and controllers for stopping when read/write error happens
@@ -2668,6 +2688,26 @@ controller_interface::return_type ControllerManager::update(
26682688
RCLCPP_ERROR(
26692689
get_logger(), "Activating fallback controllers : [ %s]", controllers_string.c_str());
26702690
}
2691+
std::vector<std::string> failed_controller_interfaces, fallback_controller_interfaces;
2692+
failed_controller_interfaces.reserve(500);
2693+
get_controller_list_command_interfaces(
2694+
failed_controllers_list, rt_controller_list, *resource_manager_,
2695+
failed_controller_interfaces);
2696+
get_controller_list_command_interfaces(
2697+
cumulative_fallback_controllers, rt_controller_list, *resource_manager_,
2698+
fallback_controller_interfaces);
2699+
if (!failed_controller_interfaces.empty())
2700+
{
2701+
if (!(resource_manager_->prepare_command_mode_switch(
2702+
fallback_controller_interfaces, failed_controller_interfaces) &&
2703+
resource_manager_->perform_command_mode_switch(
2704+
fallback_controller_interfaces, failed_controller_interfaces)))
2705+
{
2706+
RCLCPP_ERROR(
2707+
get_logger(),
2708+
"Error while attempting mode switch when deactivating controllers in update cycle!");
2709+
}
2710+
}
26712711
deactivate_controllers(rt_controller_list, active_controllers_using_interfaces);
26722712
if (!cumulative_fallback_controllers.empty())
26732713
{

controller_manager/test/test_release_interfaces.cpp

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "controller_manager/controller_manager.hpp"
2121
#include "controller_manager_test_common.hpp"
2222
#include "lifecycle_msgs/msg/state.hpp"
23+
#include "test_controller/test_controller.hpp"
2324
#include "test_controller_with_interfaces/test_controller_with_interfaces.hpp"
2425

2526
using ::testing::_;
@@ -197,3 +198,137 @@ TEST_F(TestReleaseInterfaces, switch_controllers_same_interface)
197198
abstract_test_controller2.c->get_lifecycle_state().id());
198199
}
199200
}
201+
202+
class TestReleaseExclusiveInterfaces
203+
: public ControllerManagerFixture<controller_manager::ControllerManager>
204+
{
205+
public:
206+
TestReleaseExclusiveInterfaces()
207+
: ControllerManagerFixture<controller_manager::ControllerManager>(
208+
std::string(ros2_control_test_assets::urdf_head) +
209+
std::string(ros2_control_test_assets::hardware_resources_with_exclusive_interface) +
210+
std::string(ros2_control_test_assets::urdf_tail))
211+
{
212+
}
213+
};
214+
215+
TEST_F(TestReleaseExclusiveInterfaces, test_exclusive_interface_deactivation_on_update_error)
216+
{
217+
const std::string controller_type = test_controller::TEST_CONTROLLER_CLASS_NAME;
218+
219+
// Load two controllers of different names
220+
const std::string controller_name1 = "test_controller1";
221+
const std::string controller_name2 = "test_controller2";
222+
223+
auto test_controller1 = std::make_shared<test_controller::TestController>();
224+
controller_interface::InterfaceConfiguration cmd_cfg = {
225+
controller_interface::interface_configuration_type::INDIVIDUAL, {"joint1/position"}};
226+
controller_interface::InterfaceConfiguration state_cfg = {
227+
controller_interface::interface_configuration_type::INDIVIDUAL,
228+
{"joint1/position", "joint1/velocity"}};
229+
test_controller1->set_command_interface_configuration(cmd_cfg);
230+
test_controller1->set_state_interface_configuration(state_cfg);
231+
cm_->add_controller(test_controller1, controller_name1, controller_type);
232+
233+
auto test_controller2 = std::make_shared<test_controller::TestController>();
234+
cmd_cfg = {controller_interface::interface_configuration_type::INDIVIDUAL, {"joint2/velocity"}};
235+
state_cfg = {
236+
controller_interface::interface_configuration_type::INDIVIDUAL,
237+
{"joint2/position", "joint2/velocity"}};
238+
test_controller2->set_command_interface_configuration(cmd_cfg);
239+
test_controller2->set_state_interface_configuration(state_cfg);
240+
cm_->add_controller(test_controller2, controller_name2, controller_type);
241+
ASSERT_EQ(2u, cm_->get_loaded_controllers().size());
242+
controller_manager::ControllerSpec abstract_test_controller1 = cm_->get_loaded_controllers()[0];
243+
controller_manager::ControllerSpec abstract_test_controller2 = cm_->get_loaded_controllers()[1];
244+
245+
// Configure controllers
246+
ASSERT_EQ(controller_interface::return_type::OK, cm_->configure_controller(controller_name1));
247+
ASSERT_EQ(controller_interface::return_type::OK, cm_->configure_controller(controller_name2));
248+
249+
ASSERT_EQ(
250+
lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE,
251+
abstract_test_controller1.c->get_lifecycle_state().id());
252+
ASSERT_EQ(
253+
lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE,
254+
abstract_test_controller2.c->get_lifecycle_state().id());
255+
256+
{ // Test starting the first and second controller
257+
RCLCPP_INFO(cm_->get_logger(), "Starting controller #1 and #2");
258+
std::vector<std::string> start_controllers = {controller_name1, controller_name2};
259+
std::vector<std::string> stop_controllers = {};
260+
auto switch_future = std::async(
261+
std::launch::async, &controller_manager::ControllerManager::switch_controller, cm_,
262+
start_controllers, stop_controllers, STRICT, true, rclcpp::Duration(0, 0));
263+
ASSERT_EQ(std::future_status::timeout, switch_future.wait_for(std::chrono::milliseconds(100)))
264+
<< "switch_controller should be blocking until next update cycle";
265+
ControllerManagerRunner cm_runner(this);
266+
EXPECT_EQ(controller_interface::return_type::OK, switch_future.get());
267+
ASSERT_EQ(
268+
lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE,
269+
abstract_test_controller1.c->get_lifecycle_state().id());
270+
ASSERT_EQ(
271+
lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE,
272+
abstract_test_controller2.c->get_lifecycle_state().id());
273+
}
274+
275+
// As the external command is Nan, the controller update cycle should return an error and
276+
// deactivate the controllers
277+
{
278+
test_controller1->set_external_commands_for_testing({std::numeric_limits<double>::quiet_NaN()});
279+
test_controller2->set_external_commands_for_testing({std::numeric_limits<double>::quiet_NaN()});
280+
ControllerManagerRunner cm_runner(this);
281+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
282+
ASSERT_EQ(
283+
lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE,
284+
abstract_test_controller1.c->get_lifecycle_state().id());
285+
ASSERT_EQ(
286+
lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE,
287+
abstract_test_controller2.c->get_lifecycle_state().id());
288+
}
289+
290+
{ // Test starting both controllers but only the second one will pass as the first one has the
291+
// same external command
292+
test_controller2->set_external_commands_for_testing({0.0});
293+
RCLCPP_INFO(cm_->get_logger(), "Starting controller #1 and #2 again");
294+
std::vector<std::string> start_controllers = {controller_name1, controller_name2};
295+
std::vector<std::string> stop_controllers = {};
296+
auto switch_future = std::async(
297+
std::launch::async, &controller_manager::ControllerManager::switch_controller, cm_,
298+
start_controllers, stop_controllers, STRICT, true, rclcpp::Duration(0, 0));
299+
ASSERT_EQ(std::future_status::timeout, switch_future.wait_for(std::chrono::milliseconds(100)))
300+
<< "switch_controller should be blocking until next update cycle";
301+
ControllerManagerRunner cm_runner(this);
302+
EXPECT_EQ(controller_interface::return_type::OK, switch_future.get());
303+
std::this_thread::sleep_for(std::chrono::milliseconds(50));
304+
ASSERT_EQ(
305+
lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE,
306+
abstract_test_controller1.c->get_lifecycle_state().id())
307+
<< "Controller 1 current state is "
308+
<< abstract_test_controller1.c->get_lifecycle_state().label();
309+
ASSERT_EQ(
310+
lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE,
311+
abstract_test_controller2.c->get_lifecycle_state().id());
312+
}
313+
314+
{ // Test starting controller 1 and it should work as external command is valid now
315+
test_controller1->set_external_commands_for_testing({0.0});
316+
RCLCPP_INFO(cm_->get_logger(), "Starting controller #1");
317+
std::vector<std::string> start_controllers = {controller_name1};
318+
std::vector<std::string> stop_controllers = {};
319+
auto switch_future = std::async(
320+
std::launch::async, &controller_manager::ControllerManager::switch_controller, cm_,
321+
start_controllers, stop_controllers, STRICT, true, rclcpp::Duration(0, 0));
322+
ASSERT_EQ(std::future_status::timeout, switch_future.wait_for(std::chrono::milliseconds(100)))
323+
<< "switch_controller should be blocking until next update cycle";
324+
ControllerManagerRunner cm_runner(this);
325+
EXPECT_EQ(controller_interface::return_type::OK, switch_future.get());
326+
std::this_thread::sleep_for(std::chrono::milliseconds(50));
327+
ASSERT_EQ(
328+
lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE,
329+
abstract_test_controller1.c->get_lifecycle_state().id());
330+
ASSERT_EQ(
331+
lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE,
332+
abstract_test_controller2.c->get_lifecycle_state().id());
333+
}
334+
}

hardware_interface_testing/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ endforeach()
2323
add_library(test_components SHARED
2424
test/test_components/test_actuator.cpp
2525
test/test_components/test_sensor.cpp
26-
test/test_components/test_system.cpp)
26+
test/test_components/test_system.cpp
27+
test/test_components/test_actuator_exclusive_interfaces.cpp)
2728
ament_target_dependencies(test_components hardware_interface pluginlib ros2_control_test_assets)
2829
install(TARGETS test_components
2930
DESTINATION lib

0 commit comments

Comments
 (0)