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

A method to get node options to setup the controller node #api-breaking #1169

Merged
merged 10 commits into from
Jan 31, 2024
2 changes: 2 additions & 0 deletions controller_interface/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ if(BUILD_TESTING)
)

ament_add_gmock(test_controller_with_options test/test_controller_with_options.cpp)
install(FILES test/test_controller_node_options.yaml
DESTINATION test)
target_link_libraries(test_controller_with_options
controller_interface
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,9 @@ class ControllerInterfaceBase : public rclcpp_lifecycle::node_interfaces::Lifecy
void release_interfaces();

CONTROLLER_INTERFACE_PUBLIC
virtual return_type init(
return_type init(
const std::string & controller_name, const std::string & urdf, unsigned int cm_update_rate,
const std::string & node_namespace = "",
const rclcpp::NodeOptions & node_options = rclcpp::NodeOptions().enable_logger_service(true));
const std::string & node_namespace, const rclcpp::NodeOptions & node_options);

/// Custom configure method to read additional parameters for controller-nodes
/*
Expand Down Expand Up @@ -159,6 +158,23 @@ class ControllerInterfaceBase : public rclcpp_lifecycle::node_interfaces::Lifecy
CONTROLLER_INTERFACE_PUBLIC
const std::string & get_robot_description() const;

/**
* Method used by the controller_manager for base NodeOptions to instantiate the Lifecycle node
* of the controller upon loading the controller.
*
* \note The controller_manager will modify these NodeOptions in case a params file is passed
* by the spawner to load the controller parameters or when controllers are loaded in simulation
* (see ros2_control#1311, ros2_controllers#698 , ros2_controllers#795,ros2_controllers#966 for
* more details)
*
* @returns NodeOptions required for the configuration of the controller lifecycle node
*/
CONTROLLER_INTERFACE_PUBLIC
virtual rclcpp::NodeOptions define_custom_node_options() const
{
return rclcpp::NodeOptions().enable_logger_service(true);
}

/// Declare and initialize a parameter with a type.
/**
*
Expand Down
2 changes: 1 addition & 1 deletion controller_interface/src/controller_interface_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ return_type ControllerInterfaceBase::init(
const std::string & controller_name, const std::string & urdf, unsigned int cm_update_rate,
const std::string & node_namespace, const rclcpp::NodeOptions & node_options)
{
urdf_ = urdf;
node_ = std::make_shared<rclcpp_lifecycle::LifecycleNode>(
controller_name, node_namespace, node_options,
false); // disable LifecycleNode service interfaces
urdf_ = urdf;

try
{
Expand Down
30 changes: 24 additions & 6 deletions controller_interface/test/test_chainable_controller_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ TEST_F(ChainableControllerInterfaceTest, default_returns)
TestableChainableControllerInterface controller;

// initialize, create node
ASSERT_EQ(controller.init(TEST_CONTROLLER_NAME, "", 50.0), controller_interface::return_type::OK);
const auto node_options = controller.define_custom_node_options();
ASSERT_EQ(
controller.init(TEST_CONTROLLER_NAME, "", 50.0, "", node_options),
controller_interface::return_type::OK);
ASSERT_NO_THROW(controller.get_node());

EXPECT_TRUE(controller.is_chainable());
Expand All @@ -33,7 +36,10 @@ TEST_F(ChainableControllerInterfaceTest, export_reference_interfaces)
TestableChainableControllerInterface controller;

// initialize, create node
ASSERT_EQ(controller.init(TEST_CONTROLLER_NAME, "", 50.0), controller_interface::return_type::OK);
const auto node_options = controller.define_custom_node_options();
ASSERT_EQ(
controller.init(TEST_CONTROLLER_NAME, "", 50.0, "", node_options),
controller_interface::return_type::OK);
ASSERT_NO_THROW(controller.get_node());

auto reference_interfaces = controller.export_reference_interfaces();
Expand All @@ -50,7 +56,10 @@ TEST_F(ChainableControllerInterfaceTest, reference_interfaces_storage_not_correc
TestableChainableControllerInterface controller;

// initialize, create node
ASSERT_EQ(controller.init(TEST_CONTROLLER_NAME, "", 50.0), controller_interface::return_type::OK);
const auto node_options = controller.define_custom_node_options();
ASSERT_EQ(
controller.init(TEST_CONTROLLER_NAME, "", 50.0, "", node_options),
controller_interface::return_type::OK);
ASSERT_NO_THROW(controller.get_node());

// expect empty return because storage is not resized
Expand All @@ -64,7 +73,10 @@ TEST_F(ChainableControllerInterfaceTest, reference_interfaces_prefix_is_not_node
TestableChainableControllerInterface controller;

// initialize, create node
ASSERT_EQ(controller.init(TEST_CONTROLLER_NAME, "", 50.0), controller_interface::return_type::OK);
const auto node_options = controller.define_custom_node_options();
ASSERT_EQ(
controller.init(TEST_CONTROLLER_NAME, "", 50.0, "", node_options),
controller_interface::return_type::OK);
ASSERT_NO_THROW(controller.get_node());

controller.set_name_prefix_of_reference_interfaces("some_not_correct_interface_prefix");
Expand All @@ -79,7 +91,10 @@ TEST_F(ChainableControllerInterfaceTest, setting_chained_mode)
TestableChainableControllerInterface controller;

// initialize, create node
ASSERT_EQ(controller.init(TEST_CONTROLLER_NAME, "", 50.0), controller_interface::return_type::OK);
const auto node_options = controller.define_custom_node_options();
ASSERT_EQ(
controller.init(TEST_CONTROLLER_NAME, "", 50.0, "", node_options),
controller_interface::return_type::OK);
ASSERT_NO_THROW(controller.get_node());

auto reference_interfaces = controller.export_reference_interfaces();
Expand Down Expand Up @@ -126,7 +141,10 @@ TEST_F(ChainableControllerInterfaceTest, test_update_logic)
TestableChainableControllerInterface controller;

// initialize, create node
ASSERT_EQ(controller.init(TEST_CONTROLLER_NAME, "", 50.0), controller_interface::return_type::OK);
const auto node_options = controller.define_custom_node_options();
ASSERT_EQ(
controller.init(TEST_CONTROLLER_NAME, "", 50.0, "", node_options),
controller_interface::return_type::OK);
ASSERT_NO_THROW(controller.get_node());

EXPECT_FALSE(controller.is_in_chained_mode());
Expand Down
18 changes: 14 additions & 4 deletions controller_interface/test/test_controller_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ TEST(TestableControllerInterface, init)
ASSERT_THROW(controller.get_node(), std::runtime_error);

// initialize, create node
ASSERT_EQ(controller.init(TEST_CONTROLLER_NAME, "", 10.0), controller_interface::return_type::OK);
const auto node_options = controller.define_custom_node_options();
ASSERT_EQ(
controller.init(TEST_CONTROLLER_NAME, "", 10.0, "", node_options),
controller_interface::return_type::OK);
ASSERT_NO_THROW(controller.get_node());

// update_rate is set to default 0
Expand All @@ -60,7 +63,10 @@ TEST(TestableControllerInterface, setting_update_rate_in_configure)

TestableControllerInterface controller;
// initialize, create node
ASSERT_EQ(controller.init(TEST_CONTROLLER_NAME, "", 1.0), controller_interface::return_type::OK);
const auto node_options = controller.define_custom_node_options();
ASSERT_EQ(
controller.init(TEST_CONTROLLER_NAME, "", 1.0, "", node_options),
controller_interface::return_type::OK);

// initialize executor to be able to get parameter update
auto executor =
Expand Down Expand Up @@ -122,8 +128,10 @@ TEST(TestableControllerInterfaceInitError, init_with_error)
TestableControllerInterfaceInitError controller;

// initialize, create node
const auto node_options = controller.define_custom_node_options();
ASSERT_EQ(
controller.init(TEST_CONTROLLER_NAME, "", 100.0), controller_interface::return_type::ERROR);
controller.init(TEST_CONTROLLER_NAME, "", 100.0, "", node_options),
controller_interface::return_type::ERROR);

rclcpp::shutdown();
}
Expand All @@ -137,8 +145,10 @@ TEST(TestableControllerInterfaceInitFailure, init_with_failure)
TestableControllerInterfaceInitFailure controller;

// initialize, create node
const auto node_options = controller.define_custom_node_options();
ASSERT_EQ(
controller.init(TEST_CONTROLLER_NAME, "", 50.0), controller_interface::return_type::ERROR);
controller.init(TEST_CONTROLLER_NAME, "", 50.0, "", node_options),
controller_interface::return_type::ERROR);

rclcpp::shutdown();
}
Expand Down
6 changes: 6 additions & 0 deletions controller_interface/test/test_controller_node_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
controller_name:
ros__parameters:
parameter_list:
parameter1: 20.0
parameter2: 23.14
parameter3: -52.323
103 changes: 100 additions & 3 deletions controller_interface/test/test_controller_with_options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

#include <gtest/gtest.h>
#include <string>

#include "ament_index_cpp/get_package_prefix.hpp"
#include "rclcpp/rclcpp.hpp"

class FriendControllerWithOptions : public controller_with_options::ControllerWithOptions
Expand All @@ -42,11 +42,14 @@ TEST(ControllerWithOption, init_with_overrides)
rclcpp::init(argc, argv);
// creates the controller
FriendControllerWithOptions controller;
EXPECT_EQ(controller.init("controller_name", "", 50.0), controller_interface::return_type::OK);
EXPECT_EQ(
controller.init("controller_name", "", 50.0, "", controller.define_custom_node_options()),
controller_interface::return_type::OK);
// checks that the node options have been updated
const auto & node_options = controller.get_node()->get_node_options();
EXPECT_TRUE(node_options.allow_undeclared_parameters());
EXPECT_TRUE(node_options.automatically_declare_parameters_from_overrides());
EXPECT_TRUE(node_options.arguments().empty());
// checks that the parameters have been correctly processed
EXPECT_EQ(controller.params.size(), 3u);
EXPECT_EQ(controller.params["parameter1"], 1.);
Expand All @@ -55,6 +58,98 @@ TEST(ControllerWithOption, init_with_overrides)
rclcpp::shutdown();
}

TEST(ControllerWithOption, init_with_node_options_arguments_parameters)
{
char const * const argv[] = {""};
int argc = arrlen(argv);
rclcpp::init(argc, argv);
// creates the controller
FriendControllerWithOptions controller;
auto controller_node_options = controller.define_custom_node_options();
controller_node_options.arguments(
{"--ros-args", "-p", "parameter_list.parameter1:=1.", "-p", "parameter_list.parameter2:=2.",
"-p", "parameter_list.parameter3:=3."});
EXPECT_EQ(
controller.init("controller_name", "", 50.0, "", controller_node_options),
controller_interface::return_type::OK);
// checks that the node options have been updated
const auto & node_options = controller.get_node()->get_node_options();
EXPECT_TRUE(node_options.allow_undeclared_parameters());
EXPECT_TRUE(node_options.automatically_declare_parameters_from_overrides());
EXPECT_EQ(7lu, node_options.arguments().size());
// checks that the parameters have been correctly processed
EXPECT_EQ(controller.params.size(), 3u);
EXPECT_EQ(controller.params["parameter1"], 1.);
EXPECT_EQ(controller.params["parameter2"], 2.);
EXPECT_EQ(controller.params["parameter3"], 3.);
rclcpp::shutdown();
}

TEST(ControllerWithOption, init_with_node_options_arguments_parameters_file)
{
char const * const argv[] = {""};
int argc = arrlen(argv);
rclcpp::init(argc, argv);
// creates the controller
FriendControllerWithOptions controller;
const std::string params_file_path = ament_index_cpp::get_package_prefix("controller_interface") +
"/test/test_controller_node_options.yaml";
std::cerr << params_file_path << std::endl;
auto controller_node_options = controller.define_custom_node_options();
controller_node_options.arguments({"--ros-args", "--params-file", params_file_path});
EXPECT_EQ(
controller.init("controller_name", "", 50.0, "", controller_node_options),
controller_interface::return_type::OK);
// checks that the node options have been updated
const auto & node_options = controller.get_node()->get_node_options();
EXPECT_TRUE(node_options.allow_undeclared_parameters());
EXPECT_TRUE(node_options.automatically_declare_parameters_from_overrides());
EXPECT_EQ(3lu, node_options.arguments().size());
// checks that the parameters have been correctly processed
EXPECT_EQ(controller.params.size(), 3u);
EXPECT_EQ(controller.params["parameter1"], 20.0);
EXPECT_EQ(controller.params["parameter2"], 23.14);
EXPECT_EQ(controller.params["parameter3"], -52.323);
bool use_sim_time(true);
controller.get_node()->get_parameter_or("use_sim_time", use_sim_time, false);
ASSERT_FALSE(use_sim_time);
rclcpp::shutdown();
}

TEST(
ControllerWithOption, init_with_node_options_arguments_parameters_file_and_override_command_line)
{
char const * const argv[] = {""};
int argc = arrlen(argv);
rclcpp::init(argc, argv);
// creates the controller
FriendControllerWithOptions controller;
const std::string params_file_path = ament_index_cpp::get_package_prefix("controller_interface") +
"/test/test_controller_node_options.yaml";
std::cerr << params_file_path << std::endl;
auto controller_node_options = controller.define_custom_node_options();
controller_node_options.arguments(
{"--ros-args", "--params-file", params_file_path, "-p", "parameter_list.parameter1:=562.785",
"-p", "use_sim_time:=true"});
EXPECT_EQ(
controller.init("controller_name", "", 50.0, "", controller_node_options),
controller_interface::return_type::OK);
// checks that the node options have been updated
const auto & node_options = controller.get_node()->get_node_options();
EXPECT_TRUE(node_options.allow_undeclared_parameters());
EXPECT_TRUE(node_options.automatically_declare_parameters_from_overrides());
EXPECT_EQ(7lu, node_options.arguments().size());
// checks that the parameters have been correctly processed
EXPECT_EQ(controller.params.size(), 3u);
EXPECT_EQ(controller.params["parameter1"], 562.785);
EXPECT_EQ(controller.params["parameter2"], 23.14);
EXPECT_EQ(controller.params["parameter3"], -52.323);
bool use_sim_time(false);
controller.get_node()->get_parameter_or("use_sim_time", use_sim_time, false);
ASSERT_TRUE(use_sim_time);
rclcpp::shutdown();
}
Comment on lines +88 to +151
Copy link
Member Author

@saikishor saikishor Jan 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@destogl @bmagyar @christophfroehlich These tests should give you guys an idea on how we are going to tackle in the CM with the other PR


TEST(ControllerWithOption, init_without_overrides)
{
// mocks the declaration of overrides parameters in a yaml file
Expand All @@ -63,7 +158,9 @@ TEST(ControllerWithOption, init_without_overrides)
rclcpp::init(argc, argv);
// creates the controller
FriendControllerWithOptions controller;
EXPECT_EQ(controller.init("controller_name", "", 50.0), controller_interface::return_type::ERROR);
EXPECT_EQ(
controller.init("controller_name", "", 50.0, "", controller.define_custom_node_options()),
controller_interface::return_type::ERROR);
// checks that the node options have been updated
const auto & node_options = controller.get_node()->get_node_options();
EXPECT_TRUE(node_options.allow_undeclared_parameters());
Expand Down
35 changes: 9 additions & 26 deletions controller_interface/test/test_controller_with_options.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,36 +36,19 @@ class ControllerWithOptions : public controller_interface::ControllerInterface
ControllerWithOptions() = default;
LifecycleNodeInterface::CallbackReturn on_init() override
{
return LifecycleNodeInterface::CallbackReturn::SUCCESS;
}

controller_interface::return_type init(
const std::string & controller_name, const std::string & urdf, unsigned int cm_update_rate,
const std::string & node_namespace = "",
const rclcpp::NodeOptions & node_options =
rclcpp::NodeOptions()
.allow_undeclared_parameters(true)
.automatically_declare_parameters_from_overrides(true)) override
{
ControllerInterface::init(controller_name, urdf, cm_update_rate, node_namespace, node_options);

switch (on_init())
{
case LifecycleNodeInterface::CallbackReturn::SUCCESS:
break;
case LifecycleNodeInterface::CallbackReturn::ERROR:
case LifecycleNodeInterface::CallbackReturn::FAILURE:
return controller_interface::return_type::ERROR;
}
if (get_node()->get_parameters("parameter_list", params))
{
RCLCPP_INFO_STREAM(get_node()->get_logger(), "I found " << params.size() << " parameters.");
return controller_interface::return_type::OK;
}
else
{
return controller_interface::return_type::ERROR;
return LifecycleNodeInterface::CallbackReturn::SUCCESS;
}
return LifecycleNodeInterface::CallbackReturn::FAILURE;
}

rclcpp::NodeOptions define_custom_node_options() const override
{
return rclcpp::NodeOptions()
.allow_undeclared_parameters(true)
.automatically_declare_parameters_from_overrides(true);
}

controller_interface::InterfaceConfiguration command_interface_configuration() const override
Expand Down
2 changes: 1 addition & 1 deletion controller_manager/src/controller_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2606,7 +2606,7 @@ rclcpp::NodeOptions ControllerManager::determine_controller_node_options(
auto check_for_element = [](const auto & list, const auto & element)
{ return std::find(list.begin(), list.end(), element) != list.end(); };

rclcpp::NodeOptions controller_node_options = rclcpp::NodeOptions().enable_logger_service(true);
rclcpp::NodeOptions controller_node_options = controller.c->define_custom_node_options();
std::vector<std::string> node_options_arguments = controller_node_options.arguments();
const std::string ros_args_arg = "--ros-args";
if (controller.info.parameters_file.has_value())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,6 @@ TestControllerFailedInit::on_init()
return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::FAILURE;
}

controller_interface::return_type TestControllerFailedInit::init(
const std::string & /* controller_name */, const std::string & /* urdf */,
unsigned int /*cm_update_rate*/, const std::string & /*node_namespace*/,
const rclcpp::NodeOptions & /*node_options*/)
{
return controller_interface::return_type::ERROR;
}

Comment on lines -34 to -41
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we still keep this test?

What I understand, you are here not moving the node options only, but changing how init is provoking error in this case. It could be that I am missing a detail or something.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, you don't need to have this as the same behavior will be achieved as the main init method in the base class will call the get_node_options method

controller_interface::return_type TestControllerFailedInit::update(
const rclcpp::Time & /*time*/, const rclcpp::Duration & /*period*/)
{
Expand Down
Loading
Loading