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

[HW Interface] Change creation, exportation and storing of Command-/StateInterface + Support for variant #1240

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
56f9b17
move creation of stateinterfaces to systeminterface
mamueluth Dec 7, 2023
7f97182
store description in state_interface and provide functions for
mamueluth Dec 8, 2023
0a25035
return correct name for InterfaceDescription
mamueluth Dec 11, 2023
168f427
move parsing of interface description to component parser
mamueluth Dec 11, 2023
e1ac94c
adjusted actuator- and sensor_interface as well
mamueluth Dec 12, 2023
0894883
add first tests
mamueluth Dec 18, 2023
94eb6a8
adjust sensor_interface getting/setting and add tests
mamueluth Dec 18, 2023
715ccac
add more tests
mamueluth Dec 18, 2023
468b0c7
first steps towards variants and storing of value in handle, missing:
mamueluth Dec 19, 2023
6869198
add variant support
mamueluth Dec 19, 2023
239a25b
adjusted resource manager to work with shared_ptr adapt actuator,sensor
mamueluth Dec 20, 2023
213cbae
cleanup
mamueluth Dec 20, 2023
295b620
fix failing tests
mamueluth Dec 20, 2023
a133e2b
change rest of component_interface test and mark what should be removed
mamueluth Dec 21, 2023
bfab95c
code review suggestions and:
mamueluth Jan 23, 2024
c2db38f
undo prefix_ -> prefix (HWInfo) and Command-/StateIntefaceSharedPtr
mamueluth Jan 26, 2024
3232114
merge parser functions
mamueluth Jan 26, 2024
fb3c074
export_interfaces_2() virtual for custom interface export
mamueluth Jan 29, 2024
3378fcf
use unordered map and adjust tests
mamueluth Jan 29, 2024
6667d40
make states and commands of component interfaces private
mamueluth Feb 1, 2024
a6ab1f0
add migration guide for
mamueluth Apr 10, 2024
1ffdb84
update writing a hardware component
mamueluth Apr 11, 2024
4199be1
Fix RST syntax and some typos (#18)
christophfroehlich Apr 18, 2024
2d6e7ea
add changes for chainable controllers (epxport of rerference interfaces)
mamueluth Apr 22, 2024
3a53368
rename export_state/command_interface_2 to export_state/commad_interf…
mamueluth May 22, 2024
e9c2669
adapt to new changes and remove some rebase artefacts
mamueluth Jul 25, 2024
1cea7f2
fix test failures
mamueluth Jul 26, 2024
84002fd
reviwe changes
mamueluth Aug 14, 2024
b05f585
Merge branch 'master' into change_interface_export_variant
mamueluth Aug 14, 2024
05ec1bd
Merge branch 'master' into change_interface_export_variant
mamueluth Aug 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
#ifndef CONTROLLER_INTERFACE__CHAINABLE_CONTROLLER_INTERFACE_HPP_
#define CONTROLLER_INTERFACE__CHAINABLE_CONTROLLER_INTERFACE_HPP_

#include <memory>
#include <string>
#include <unordered_map>
#include <vector>

#include "controller_interface/controller_interface_base.hpp"
Expand Down Expand Up @@ -57,10 +59,11 @@ class ChainableControllerInterface : public ControllerInterfaceBase
bool is_chainable() const final;

CONTROLLER_INTERFACE_PUBLIC
std::vector<hardware_interface::StateInterface> export_state_interfaces() final;
std::vector<std::shared_ptr<hardware_interface::StateInterface>> export_state_interfaces() final;

CONTROLLER_INTERFACE_PUBLIC
std::vector<hardware_interface::CommandInterface> export_reference_interfaces() final;
std::vector<std::shared_ptr<hardware_interface::CommandInterface>> export_reference_interfaces()
final;

CONTROLLER_INTERFACE_PUBLIC
bool set_chained_mode(bool chained_mode) final;
Expand Down Expand Up @@ -135,7 +138,11 @@ class ChainableControllerInterface : public ControllerInterfaceBase

/// Storage of values for reference interfaces
std::vector<std::string> exported_reference_interface_names_;
// BEGIN (Handle export change): for backward compatibility
std::vector<double> reference_interfaces_;
// END
std::unordered_map<std::string, std::shared_ptr<hardware_interface::CommandInterface>>
reference_interfaces_ptrs_;

private:
/// A flag marking if a chainable controller is currently preceded by another controller.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,16 @@ class ControllerInterface : public controller_interface::ControllerInterfaceBase
* \returns empty list.
*/
CONTROLLER_INTERFACE_PUBLIC
std::vector<hardware_interface::StateInterface> export_state_interfaces() final;
std::vector<std::shared_ptr<hardware_interface::StateInterface>> export_state_interfaces() final;

/**
* Controller has no reference interfaces.
*
* \returns empty list.
*/
CONTROLLER_INTERFACE_PUBLIC
std::vector<hardware_interface::CommandInterface> export_reference_interfaces() final;
std::vector<std::shared_ptr<hardware_interface::CommandInterface>> export_reference_interfaces()
Copy link
Member

Choose a reason for hiding this comment

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

do these needs to be shared pointers? I'm just wondering about how best we can keep track of the lifecycle of things moving around...
would a unique_ptr be unusable? I also hate seeing std::move in active codebase but it is a fair use-case for this IMO

Copy link
Member Author

Choose a reason for hiding this comment

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

i guess no because we want to export the reference interfaces but the controller still needs to have access to them (since value is now stored directly). Its same with the hardware. We could avoid creating shared_ptrs by creating special loans for hw or chainable controllers. But i think should be fine in this case since handles have no lifecycle bit their lifecycle is indirectly connected to the components lifecycle itself. this is managed by resource manager which should be the only part having the pointers besides the components themselves.

final;

/**
* Controller is not chainable, therefore no chained mode can be set.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,8 @@ class ControllerInterfaceBase : public rclcpp_lifecycle::node_interfaces::Lifecy
* \returns list of command interfaces for preceding controllers.
*/
CONTROLLER_INTERFACE_PUBLIC
virtual std::vector<hardware_interface::CommandInterface> export_reference_interfaces() = 0;
virtual std::vector<std::shared_ptr<hardware_interface::CommandInterface>>
export_reference_interfaces() = 0;

/**
* Export interfaces for a chainable controller that can be used as state interface by other
Expand All @@ -230,7 +231,8 @@ class ControllerInterfaceBase : public rclcpp_lifecycle::node_interfaces::Lifecy
* \returns list of state interfaces for preceding controllers.
*/
CONTROLLER_INTERFACE_PUBLIC
virtual std::vector<hardware_interface::StateInterface> export_state_interfaces() = 0;
virtual std::vector<std::shared_ptr<hardware_interface::StateInterface>>
export_state_interfaces() = 0;

/**
* Set chained mode of a chainable controller. This method triggers internal processes to switch
Expand Down
86 changes: 62 additions & 24 deletions controller_interface/src/chainable_controller_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,53 +44,91 @@ return_type ChainableControllerInterface::update(
return ret;
}

std::vector<hardware_interface::StateInterface>
std::vector<std::shared_ptr<hardware_interface::StateInterface>>
ChainableControllerInterface::export_state_interfaces()
{
auto state_interfaces = on_export_state_interfaces();
std::vector<std::shared_ptr<hardware_interface::StateInterface>> state_interfaces_ptrs_vec;
state_interfaces_ptrs_vec.reserve(state_interfaces.size());

// check if the names of the controller state interfaces begin with the controller's name
for (const auto & interface : state_interfaces)
{
if (interface.get_prefix_name() != get_node()->get_name())
{
RCLCPP_FATAL(
get_node()->get_logger(),
"The name of the interface '%s' does not begin with the controller's name. This is "
"mandatory for state interfaces. No state interface will be exported. Please "
"correct and recompile the controller with name '%s' and try again.",
interface.get_name().c_str(), get_node()->get_name());
state_interfaces.clear();
break;
std::string error_msg =
"The prefix of the interface '" + interface.get_prefix_name() +
"' does not equal the controller's name '" + get_node()->get_name() +
"'. This is mandatory for state interfaces. No state interface will be exported. Please "
"correct and recompile the controller with name '" +
get_node()->get_name() + "' and try again.";
throw std::runtime_error(error_msg);
}

state_interfaces_ptrs_vec.push_back(
std::make_shared<hardware_interface::StateInterface>(interface));
}

return state_interfaces;
return state_interfaces_ptrs_vec;
}

std::vector<hardware_interface::CommandInterface>
std::vector<std::shared_ptr<hardware_interface::CommandInterface>>
ChainableControllerInterface::export_reference_interfaces()
{
auto reference_interfaces = on_export_reference_interfaces();
std::vector<std::shared_ptr<hardware_interface::CommandInterface>> reference_interfaces_ptrs_vec;
reference_interfaces_ptrs_vec.reserve(reference_interfaces.size());

// BEGIN (Handle export change): for backward compatibility
// check if the "reference_interfaces_" variable is resized to number of interfaces
if (reference_interfaces_.size() != reference_interfaces.size())
{
// TODO(destogl): Should here be "FATAL"? It is fatal in terms of controller but not for the
// framework
Comment on lines +86 to +87
Copy link
Member

Choose a reason for hiding this comment

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

I'm going to ask a very similar question as on 1021.

Can we do the allocation and fix everything instead of telling people what to do?

Also I agree with @destogl 's comment, shouldn't be a runtime error

Copy link
Member Author

Choose a reason for hiding this comment

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

i have to check this will comment later i would prefer fixing everything and providing as well.
About "Also I agree with @destogl 's comment, shouldn't be a runtime error" thats why i choose to throw an exception there and catch in cm. Then cm can decide what should be done in this case ( i just print out error and do not set chained mode)

std::string error_msg =
"The internal storage for reference values 'reference_interfaces_' variable has size '" +
std::to_string(reference_interfaces_.size()) + "', but it is expected to have the size '" +
std::to_string(reference_interfaces.size()) +
"' equal to the number of exported reference interfaces. Please correct and recompile the "
"controller with name '" +
get_node()->get_name() + "' and try again.";
throw std::runtime_error(error_msg);
}
// END

// check if the names of the reference interfaces begin with the controller's name
for (const auto & interface : reference_interfaces)
const auto ref_interface_size = reference_interfaces.size();
for (auto & interface : reference_interfaces)
{
if (interface.get_prefix_name() != get_node()->get_name())
{
RCLCPP_FATAL(
get_node()->get_logger(),
"The name of the interface '%s' does not begin with the controller's name. This is "
"mandatory "
" for reference interfaces. No reference interface will be exported. Please correct and "
"recompile the controller with name '%s' and try again.",
interface.get_name().c_str(), get_node()->get_name());
reference_interfaces.clear();
break;
std::string error_msg = "The name of the interface " + interface.get_name() +
" does not begin with the controller's name. This is mandatory for "
"reference interfaces. Please "
"correct and recompile the controller with name " +
get_node()->get_name() + " and try again.";
throw std::runtime_error(error_msg);
}

std::shared_ptr<hardware_interface::CommandInterface> interface_ptr =
std::make_shared<hardware_interface::CommandInterface>(std::move(interface));
reference_interfaces_ptrs_vec.push_back(interface_ptr);
reference_interfaces_ptrs_.insert(std::make_pair(interface_ptr->get_name(), interface_ptr));
}

return reference_interfaces;
if (reference_interfaces_ptrs_.size() != ref_interface_size)
{
std::string error_msg =
"The internal storage for reference ptrs 'reference_interfaces_ptrs_' variable has size '" +
std::to_string(reference_interfaces_ptrs_.size()) +
"', but it is expected to have the size '" + std::to_string(ref_interface_size) +
"' equal to the number of exported reference interfaces. Please correct and recompile the "
"controller with name '" +
get_node()->get_name() + "' and try again.";
throw std::runtime_error(error_msg);
}

return reference_interfaces_ptrs_vec;
}

bool ChainableControllerInterface::set_chained_mode(bool chained_mode)
Expand Down Expand Up @@ -130,8 +168,8 @@ ChainableControllerInterface::on_export_state_interfaces()
std::vector<hardware_interface::StateInterface> state_interfaces;
for (size_t i = 0; i < exported_state_interface_names_.size(); ++i)
{
state_interfaces.emplace_back(hardware_interface::StateInterface(
get_node()->get_name(), exported_state_interface_names_[i], &state_interfaces_values_[i]));
state_interfaces.emplace_back(
get_node()->get_name(), exported_state_interface_names_[i], &state_interfaces_values_[i]);
}
return state_interfaces;
}
Expand Down
6 changes: 4 additions & 2 deletions controller_interface/src/controller_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ ControllerInterface::ControllerInterface() : ControllerInterfaceBase() {}

bool ControllerInterface::is_chainable() const { return false; }

std::vector<hardware_interface::StateInterface> ControllerInterface::export_state_interfaces()
std::vector<std::shared_ptr<hardware_interface::StateInterface>>
ControllerInterface::export_state_interfaces()
{
return {};
}

std::vector<hardware_interface::CommandInterface> ControllerInterface::export_reference_interfaces()
std::vector<std::shared_ptr<hardware_interface::CommandInterface>>
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
std::vector<std::shared_ptr<hardware_interface::CommandInterface>>
std::vector<hardware_interface::CommandInterfacePtr>

Was wondering if we could do this instead? Just to shorten things...

Copy link
Member Author

Choose a reason for hiding this comment

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

no because then they would not override the base functions (which have to have this return type because controllers need access to the exported handles to set/get values from them).

Copy link
Member

Choose a reason for hiding this comment

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

I think @bmagyar means here that we define a new type, something like:

using CommandInterfacePtr = std::shared_ptr<CommandInterface>

This should go to the header where CommandInterface is defined.

On the other ahdn I am not fan of it, as it makes things shorter, but make them also less clear and direct.

ControllerInterface::export_reference_interfaces()
{
return {};
}
Expand Down
31 changes: 18 additions & 13 deletions controller_interface/test/test_chainable_controller_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "test_chainable_controller_interface.hpp"

#include <gmock/gmock.h>
#include <memory>

using ::testing::IsEmpty;
using ::testing::SizeIs;
Expand Down Expand Up @@ -48,10 +49,10 @@ TEST_F(ChainableControllerInterfaceTest, export_state_interfaces)
auto exported_state_interfaces = controller.export_state_interfaces();

ASSERT_THAT(exported_state_interfaces, SizeIs(1));
EXPECT_EQ(exported_state_interfaces[0].get_prefix_name(), TEST_CONTROLLER_NAME);
EXPECT_EQ(exported_state_interfaces[0].get_interface_name(), "test_state");
EXPECT_EQ(exported_state_interfaces[0]->get_prefix_name(), TEST_CONTROLLER_NAME);
EXPECT_EQ(exported_state_interfaces[0]->get_interface_name(), "test_state");

EXPECT_EQ(exported_state_interfaces[0].get_value(), EXPORTED_STATE_INTERFACE_VALUE);
EXPECT_EQ(exported_state_interfaces[0]->get_value(), EXPORTED_STATE_INTERFACE_VALUE);
}

TEST_F(ChainableControllerInterfaceTest, export_reference_interfaces)
Expand All @@ -68,10 +69,10 @@ TEST_F(ChainableControllerInterfaceTest, export_reference_interfaces)
auto reference_interfaces = controller.export_reference_interfaces();

ASSERT_THAT(reference_interfaces, SizeIs(1));
EXPECT_EQ(reference_interfaces[0].get_prefix_name(), TEST_CONTROLLER_NAME);
EXPECT_EQ(reference_interfaces[0].get_interface_name(), "test_itf");
EXPECT_EQ(reference_interfaces[0]->get_prefix_name(), TEST_CONTROLLER_NAME);
EXPECT_EQ(reference_interfaces[0]->get_interface_name(), "test_itf");

EXPECT_EQ(reference_interfaces[0].get_value(), INTERFACE_VALUE);
EXPECT_EQ(reference_interfaces[0]->get_value(), INTERFACE_VALUE);
}

TEST_F(ChainableControllerInterfaceTest, interfaces_prefix_is_not_node_name)
Expand All @@ -88,10 +89,15 @@ TEST_F(ChainableControllerInterfaceTest, interfaces_prefix_is_not_node_name)
controller.set_name_prefix_of_reference_interfaces("some_not_correct_interface_prefix");

// expect empty return because interface prefix is not equal to the node name
auto reference_interfaces = controller.export_reference_interfaces();
ASSERT_THAT(reference_interfaces, IsEmpty());
std::vector<std::shared_ptr<hardware_interface::CommandInterface>> exported_reference_interfaces;
EXPECT_THROW(
{ exported_reference_interfaces = controller.export_reference_interfaces(); },
std::runtime_error);
ASSERT_THAT(exported_reference_interfaces, IsEmpty());
// expect empty return because interface prefix is not equal to the node name
auto exported_state_interfaces = controller.export_state_interfaces();
std::vector<std::shared_ptr<hardware_interface::StateInterface>> exported_state_interfaces;
EXPECT_THROW(
{ exported_state_interfaces = controller.export_state_interfaces(); }, std::runtime_error);
ASSERT_THAT(exported_state_interfaces, IsEmpty());
}

Expand All @@ -114,8 +120,7 @@ TEST_F(ChainableControllerInterfaceTest, setting_chained_mode)
EXPECT_FALSE(controller.is_in_chained_mode());

// Fail setting chained mode
EXPECT_EQ(reference_interfaces[0].get_value(), INTERFACE_VALUE);
EXPECT_EQ(exported_state_interfaces[0].get_value(), EXPORTED_STATE_INTERFACE_VALUE);
EXPECT_EQ(reference_interfaces[0]->get_value(), INTERFACE_VALUE);

EXPECT_FALSE(controller.set_chained_mode(true));
EXPECT_FALSE(controller.is_in_chained_mode());
Expand All @@ -124,11 +129,11 @@ TEST_F(ChainableControllerInterfaceTest, setting_chained_mode)
EXPECT_FALSE(controller.is_in_chained_mode());

// Success setting chained mode
reference_interfaces[0].set_value(0.0);
reference_interfaces[0]->set_value(0.0);

EXPECT_TRUE(controller.set_chained_mode(true));
EXPECT_TRUE(controller.is_in_chained_mode());
EXPECT_EQ(exported_state_interfaces[0].get_value(), EXPORTED_STATE_INTERFACE_VALUE_IN_CHAINMODE);
EXPECT_EQ(exported_state_interfaces[0]->get_value(), EXPORTED_STATE_INTERFACE_VALUE_IN_CHAINMODE);

controller.configure();
EXPECT_TRUE(controller.set_chained_mode(false));
Expand Down
29 changes: 21 additions & 8 deletions controller_manager/src/controller_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -761,15 +761,28 @@ controller_interface::return_type ControllerManager::configure_controller(
get_logger(),
"Controller '%s' is chainable. Interfaces are being exported to resource manager.",
controller_name.c_str());
auto state_interfaces = controller->export_state_interfaces();
auto ref_interfaces = controller->export_reference_interfaces();
if (ref_interfaces.empty() && state_interfaces.empty())
std::vector<std::shared_ptr<hardware_interface::StateInterface>> state_interfaces;
std::vector<std::shared_ptr<hardware_interface::CommandInterface>> ref_interfaces;
try
{
// TODO(destogl): Add test for this!
RCLCPP_ERROR(
get_logger(),
"Controller '%s' is chainable, but does not export any state or reference interfaces.",
controller_name.c_str());
state_interfaces = controller->export_state_interfaces();
ref_interfaces = controller->export_reference_interfaces();
if (ref_interfaces.empty() && state_interfaces.empty())
{
// TODO(destogl): Add test for this!
RCLCPP_ERROR(
get_logger(),
"Controller '%s' is chainable, but does not export any reference interfaces. Did you "
"override the on_export_method() correctly?",
controller_name.c_str());
return controller_interface::return_type::ERROR;
}
}
catch (const std::runtime_error & e)
{
RCLCPP_FATAL(
get_logger(), "Creation of the reference interfaces failed with following error: %s",
e.what());
Comment on lines -764 to +785
Copy link
Member

Choose a reason for hiding this comment

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

I think this section could remain as it was before. Errors could be communicated via prints & empty returned arrays, no need for exceptions.

Copy link
Member Author

Choose a reason for hiding this comment

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

i personally would prefer exceptions since they make clear that an unexpected condition arose which needs some separate handling and ultimately controller manager should decide how to proceed in this case (thats why i cathc there and handle exception). i am not a big fan of implicitly encoding this in the return type. I personally would prefer explicit over implicit here.

Copy link
Member

Choose a reason for hiding this comment

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

Why can creation fail?

return controller_interface::return_type::ERROR;
}
resource_manager_->import_controller_reference_interfaces(controller_name, ref_interfaces);
Expand Down
Loading
Loading