Skip to content

Commit 189f705

Browse files
VitezGabrielaNibanovicdestogl
authored
Controller Manager recovery from invalid URDF errors (#2775)
Co-authored-by: nibanovic <nikola.banovic@b-robotized.com> Co-authored-by: Dr. Denis <denis@stoglrobotics.de> Co-authored-by: Dr. Denis <denis.stogl@b-robotized.com>
1 parent 2844ba5 commit 189f705

15 files changed

Lines changed: 454 additions & 98 deletions

controller_manager/CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,15 @@ if(BUILD_TESTING)
268268
${controller_manager_msgs_TARGETS}
269269
)
270270

271+
ament_add_gmock(test_controller_manager_with_resource_manager
272+
test/test_controller_manager_with_resource_manager.cpp
273+
)
274+
target_link_libraries(test_controller_manager_with_resource_manager
275+
controller_manager
276+
hardware_interface::hardware_interface
277+
ros2_control_test_assets::ros2_control_test_assets
278+
)
279+
271280
find_package(ament_cmake_pytest REQUIRED)
272281
install(FILES test/test_ros2_control_node.yaml
273282
DESTINATION test)

controller_manager/include/controller_manager/controller_manager.hpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,10 +348,32 @@ class ControllerManager : public rclcpp::Node
348348
std::vector<std::string> get_controller_names();
349349
std::pair<std::string, std::string> split_command_interface(
350350
const std::string & command_interface);
351+
352+
/// Initialize controller manager publishers, diagnostics, introspection, and shutdown handling.
351353
void init_controller_manager();
352354

355+
/// Initialize controller manager parameters.
356+
/**
357+
* Declares controller manager parameters, reads them from the generated parameter listener, and
358+
* caches values used by the real-time update loop.
359+
*/
353360
void initialize_parameters();
354361

362+
/// Initialize the robot description subscription and wait notification timer.
363+
/**
364+
* Used when the controller manager starts without a valid robot description, or when a robot
365+
* description failed to initialize hardware and the manager should keep waiting for a
366+
* replacement.
367+
*/
368+
void init_robot_description_callback();
369+
370+
/// Set the initial lifecycle state of hardware components.
371+
/**
372+
* Applies the hardware_components_initial_state parameters after the resource manager has loaded
373+
* and initialized components from a valid robot description.
374+
*/
375+
void set_initial_hardware_components_state();
376+
355377
/**
356378
* Call cleanup to change the given controller lifecycle node to the unconfigured state.
357379
*
@@ -683,6 +705,8 @@ class ControllerManager : public rclcpp::Node
683705
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr robot_description_subscription_;
684706
rclcpp::TimerBase::SharedPtr robot_description_notification_timer_;
685707

708+
bool activate_all_hw_components_ = false;
709+
686710
struct ControllerManagerExecutionTime
687711
{
688712
double read_time = 0.0;

controller_manager/src/controller_manager.cpp

Lines changed: 128 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -548,25 +548,17 @@ ControllerManager::ControllerManager(
548548
chainable_loader_(
549549
std::make_shared<pluginlib::ClassLoader<controller_interface::ChainableControllerInterface>>(
550550
kControllerInterfaceNamespace, kChainableControllerInterfaceClassName)),
551-
robot_description_(urdf)
551+
robot_description_(urdf),
552+
activate_all_hw_components_(activate_all_hw_components)
552553
{
553554
initialize_parameters();
554-
hardware_interface::ResourceManagerParams params;
555-
params.robot_description = robot_description_;
556-
params.clock = trigger_clock_;
557-
params.logger = this->get_logger();
558-
params.activate_all = activate_all_hw_components;
559-
params.update_rate = static_cast<unsigned int>(params_->update_rate);
560-
params.executor = executor_;
561-
params.node_namespace = node_namespace;
562-
params.allow_controller_activation_with_inactive_hardware =
563-
params_->defaults.allow_controller_activation_with_inactive_hardware;
564-
params.return_failed_hardware_names_on_return_deactivate_write_cycle_ =
565-
params_->defaults.deactivate_controllers_on_hardware_self_deactivate;
566-
params.handle_exceptions = params_->handle_exceptions;
567-
resource_manager_ =
568-
std::make_unique<hardware_interface::ResourceManager>(params, !robot_description_.empty());
555+
init_resource_manager(urdf);
569556
init_controller_manager();
557+
if (is_resource_manager_initialized())
558+
{
559+
set_initial_hardware_components_state();
560+
init_services();
561+
}
570562
}
571563

572564
ControllerManager::ControllerManager(
@@ -586,7 +578,17 @@ ControllerManager::ControllerManager(
586578
robot_description_(resource_manager_->get_robot_description())
587579
{
588580
initialize_parameters();
589-
init_controller_manager();
581+
if (is_resource_manager_initialized())
582+
{
583+
init_controller_manager();
584+
set_initial_hardware_components_state();
585+
init_services();
586+
}
587+
else
588+
{
589+
RCLCPP_FATAL(get_logger(), "The resource manager is not properly initialized");
590+
throw std::runtime_error("Resource manager object is not valid. See the FATAL message above.");
591+
}
590592
}
591593

592594
ControllerManager::~ControllerManager()
@@ -633,50 +635,18 @@ bool ControllerManager::shutdown_controllers()
633635

634636
void ControllerManager::init_controller_manager()
635637
{
638+
// Initialized activity publisher and diagnostics
636639
controller_manager_activity_publisher_ =
637640
create_publisher<controller_manager_msgs::msg::ControllerManagerActivity>(
638641
"~/activity", rclcpp::QoS(1).reliable().transient_local());
639642
rt_controllers_wrapper_.set_on_switch_callback(
640643
std::bind(&ControllerManager::publish_activity, this));
641-
resource_manager_->set_on_component_state_switch_callback(
642-
std::bind(&ControllerManager::publish_activity, this));
643-
644-
// Get parameters needed for RT "update" loop to work
645-
if (is_resource_manager_initialized())
644+
if (resource_manager_)
646645
{
647-
if (params_->enforce_command_limits)
648-
{
649-
resource_manager_->import_joint_limiters(robot_description_);
650-
RCLCPP_INFO(get_logger(), "Enforcing command limits is enabled...");
651-
}
652-
else
653-
{
654-
RCLCPP_INFO(
655-
get_logger(),
656-
"Enforcing command limits is disabled. Command limits from URDF will be ignored.");
657-
}
658-
init_services();
659-
}
660-
else
661-
{
662-
robot_description_notification_timer_ = create_wall_timer(
663-
std::chrono::seconds(1),
664-
[&]()
665-
{
666-
RCLCPP_WARN(
667-
get_logger(), "Waiting for data on 'robot_description' topic to finish initialization");
668-
});
646+
resource_manager_->set_on_component_state_switch_callback(
647+
std::bind(&ControllerManager::publish_activity, this));
669648
}
670649

671-
// set QoS to transient local to get messages that have already been published
672-
// (if robot state publisher starts before controller manager)
673-
robot_description_subscription_ = create_subscription<std_msgs::msg::String>(
674-
"robot_description", rclcpp::QoS(1).transient_local(),
675-
std::bind(&ControllerManager::robot_description_callback, this, std::placeholders::_1));
676-
RCLCPP_INFO(
677-
get_logger(), "Subscribing to '%s' topic for robot description.",
678-
robot_description_subscription_->get_topic_name());
679-
680650
// Setup diagnostics
681651
periodicity_stats_.reset();
682652
diagnostics_updater_.setHardwareID("ros2_control");
@@ -719,6 +689,8 @@ void ControllerManager::init_controller_manager()
719689
}
720690
RCLCPP_INFO(get_logger(), "Shutting down the controller manager.");
721691
}));
692+
693+
init_robot_description_callback();
722694
}
723695

724696
void ControllerManager::initialize_parameters()
@@ -758,6 +730,30 @@ void ControllerManager::initialize_parameters()
758730
}
759731
}
760732

733+
void ControllerManager::init_robot_description_callback()
734+
{
735+
if (!robot_description_subscription_)
736+
{
737+
robot_description_subscription_ = create_subscription<std_msgs::msg::String>(
738+
"robot_description", rclcpp::QoS(1).transient_local(),
739+
std::bind(&ControllerManager::robot_description_callback, this, std::placeholders::_1));
740+
RCLCPP_INFO(
741+
get_logger(), "Subscribing to '%s' topic for robot description.",
742+
robot_description_subscription_->get_topic_name());
743+
}
744+
745+
if (!is_resource_manager_initialized() && !robot_description_notification_timer_)
746+
{
747+
robot_description_notification_timer_ = create_wall_timer(
748+
std::chrono::seconds(1),
749+
[&]()
750+
{
751+
RCLCPP_WARN(
752+
get_logger(), "Waiting for data on 'robot_description' topic to finish initialization");
753+
});
754+
}
755+
}
756+
761757
void ControllerManager::robot_description_callback(const std_msgs::msg::String & robot_description)
762758
{
763759
RCLCPP_INFO(get_logger(), "Received robot description from topic.");
@@ -768,50 +764,103 @@ void ControllerManager::robot_description_callback(const std_msgs::msg::String &
768764
{
769765
RCLCPP_WARN(
770766
get_logger(),
771-
"ResourceManager has already loaded a urdf. Ignoring attempt to reload a robot description.");
767+
"ResourceManager has already loaded a urdf and is initialized. Ignoring attempt to reload a "
768+
"robot description.");
772769
return;
773770
}
771+
774772
init_resource_manager(robot_description_);
775-
if (is_resource_manager_initialized())
773+
if (!is_resource_manager_initialized())
776774
{
777-
RCLCPP_INFO(
778-
get_logger(),
779-
"Resource Manager has been successfully initialized. Starting Controller Manager "
780-
"services...");
781-
init_services();
775+
// The RM failed to init AFTER we received the description - a critical error.
776+
// don't finalize controller manager, instead keep waiting for robot description - fallback
777+
// state
778+
resource_manager_ =
779+
std::make_unique<hardware_interface::ResourceManager>(trigger_clock_, get_logger());
780+
return;
782781
}
782+
set_initial_hardware_components_state();
783+
RCLCPP_INFO(
784+
get_logger(),
785+
"Resource Manager has been successfully initialized. Starting Controller Manager "
786+
"services...");
787+
788+
init_services();
783789
}
784790

785791
void ControllerManager::init_resource_manager(const std::string & robot_description)
786792
{
793+
hardware_interface::ResourceManagerParams params;
794+
params.robot_description = robot_description;
795+
params.clock = trigger_clock_;
796+
params.logger = this->get_logger();
797+
params.activate_all = activate_all_hw_components_;
798+
params.update_rate = static_cast<unsigned int>(params_->update_rate);
799+
params.executor = executor_;
800+
params.node_namespace = this->get_namespace();
801+
params.allow_controller_activation_with_inactive_hardware =
802+
params_->defaults.allow_controller_activation_with_inactive_hardware;
803+
params.return_failed_hardware_names_on_return_deactivate_write_cycle_ =
804+
params_->defaults.deactivate_controllers_on_hardware_self_deactivate;
805+
params.handle_exceptions = params_->handle_exceptions;
806+
resource_manager_ = std::make_unique<hardware_interface::ResourceManager>(params, false);
807+
808+
resource_manager_->set_on_component_state_switch_callback(
809+
std::bind(&ControllerManager::publish_activity, this));
810+
811+
if (robot_description.empty())
812+
{
813+
return;
814+
}
815+
787816
if (params_->enforce_command_limits)
788817
{
789-
resource_manager_->import_joint_limiters(robot_description_);
790818
RCLCPP_INFO(get_logger(), "Enforcing command limits is enabled...");
819+
try
820+
{
821+
resource_manager_->import_joint_limiters(robot_description);
822+
}
823+
catch (const std::exception & e)
824+
{
825+
RCLCPP_ERROR(get_logger(), "Error importing joint limiters: %s", e.what());
826+
return;
827+
}
791828
}
792829
else
793830
{
794831
RCLCPP_INFO(
795832
get_logger(),
796-
"Enforcing command limits is disabled. Command limits from URDF will be ignored.");
833+
"Enforcing command limits is disabled. Command limits from URDF will be "
834+
"ignored.");
797835
}
798-
hardware_interface::ResourceManagerParams params;
799-
params.robot_description = robot_description;
800-
params.clock = trigger_clock_;
801-
params.logger = this->get_logger();
802-
params.executor = executor_;
803-
params.node_namespace = this->get_namespace();
804-
params.update_rate = static_cast<unsigned int>(params_->update_rate);
805-
params.handle_exceptions = params_->handle_exceptions;
806-
if (!resource_manager_->load_and_initialize_components(params))
836+
837+
try
807838
{
808-
RCLCPP_WARN(
809-
get_logger(),
810-
"Could not load and initialize hardware. Please check previous output for more details. "
811-
"After you have corrected your URDF, try to publish robot description again.");
839+
if (!resource_manager_->load_and_initialize_components(params))
840+
{
841+
RCLCPP_WARN(
842+
get_logger(),
843+
"Could not load and initialize hardware. Please check previous output for more details. "
844+
"After you have corrected your URDF, try to publish robot description again.");
845+
return;
846+
}
847+
}
848+
catch (const std::exception & e)
849+
{
850+
// Other possible errors when loading components
851+
RCLCPP_ERROR(
852+
get_logger(), "Exception caught while loading and initializing components: %s", e.what());
812853
return;
813854
}
814855

856+
if (robot_description_notification_timer_)
857+
{
858+
robot_description_notification_timer_->cancel();
859+
}
860+
}
861+
862+
void ControllerManager::set_initial_hardware_components_state()
863+
{
815864
// Get all components and if they are not defined in parameters activate them automatically
816865
auto components_to_activate = resource_manager_->get_components_status();
817866

@@ -1019,7 +1068,6 @@ void ControllerManager::init_resource_manager(const std::string & robot_descript
10191068
group_name.c_str());
10201069
}
10211070
}
1022-
10231071
// Process ungrouped components individually (configure and activate each one)
10241072
for (const auto & component_name : ungrouped_components)
10251073
{
@@ -1030,8 +1078,10 @@ void ControllerManager::init_resource_manager(const std::string & robot_descript
10301078
}
10311079
}
10321080

1033-
robot_description_notification_timer_->cancel();
1034-
1081+
if (robot_description_notification_timer_)
1082+
{
1083+
robot_description_notification_timer_->cancel();
1084+
}
10351085
auto hw_components_info = resource_manager_->get_components_status();
10361086

10371087
for (const auto & [component_name, component_info] : hw_components_info)

controller_manager/test/controller_manager_test_common.hpp

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,10 @@ class ControllerManagerFixture : public ::testing::Test
6666
{
6767
cm_node_options.parameter_overrides(cm_parameters);
6868
}
69+
6970
cm_ = std::make_shared<CtrlMgr>(
70-
std::make_unique<hardware_interface::ResourceManager>(
71-
rm_node_->get_node_clock_interface(), rm_node_->get_node_logging_interface()),
72-
executor_, TEST_CM_NAME, cm_namespace, cm_node_options);
73-
// We want to be able to not pass robot description immediately
74-
if (!robot_description_.empty())
75-
{
76-
pass_robot_description_to_cm_and_rm(robot_description_);
77-
}
71+
executor_, robot_description_, true, TEST_CM_NAME, cm_namespace, cm_node_options);
72+
7873
time_ = rclcpp::Time(0, 0, cm_->get_trigger_clock()->get_clock_type());
7974
}
8075

controller_manager/test/test_controller_manager_hardware_error_handling.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ class TestableControllerManager : public controller_manager::ControllerManager
6161
std::move(resource_manager), executor, manager_node_name, node_namespace, node_options)
6262
{
6363
}
64+
TestableControllerManager(
65+
std::shared_ptr<rclcpp::Executor> executor, const std::string & urdf,
66+
bool activate_all_hw_components, const std::string & manager_node_name = "controller_manager",
67+
const std::string & node_namespace = "",
68+
const rclcpp::NodeOptions & options = controller_manager::get_cm_node_options())
69+
: ControllerManager(
70+
executor, urdf, activate_all_hw_components, manager_node_name, node_namespace, options)
71+
{
72+
}
6473
};
6574

6675
class TestControllerManagerWithTestableCM

0 commit comments

Comments
 (0)