Skip to content

Commit

Permalink
Adapt controller Reference/StateInterfaces to New Way of Exporting (v…
Browse files Browse the repository at this point in the history
…ariant support) (#1689)

---------

Co-authored-by: Bence Magyar <[email protected]>
  • Loading branch information
mamueluth and bmagyar authored Oct 7, 2024
1 parent ab84b74 commit 34d2b36
Show file tree
Hide file tree
Showing 12 changed files with 202 additions and 81 deletions.
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,10 @@ class ChainableControllerInterface : public ControllerInterfaceBase
bool is_chainable() const final;

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

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

CONTROLLER_INTERFACE_PUBLIC
bool set_chained_mode(bool chained_mode) final;
Expand Down Expand Up @@ -131,11 +133,21 @@ class ChainableControllerInterface : public ControllerInterfaceBase

/// Storage of values for state interfaces
std::vector<std::string> exported_state_interface_names_;
std::vector<hardware_interface::StateInterface::SharedPtr> ordered_exported_state_interfaces_;
std::unordered_map<std::string, hardware_interface::StateInterface::SharedPtr>
exported_state_interfaces_;
// BEGIN (Handle export change): for backward compatibility
std::vector<double> state_interfaces_values_;
// END

/// 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::vector<hardware_interface::CommandInterface::SharedPtr> ordered_reference_interfaces_;
std::unordered_map<std::string, hardware_interface::CommandInterface::SharedPtr>
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,15 @@ class ControllerInterface : public controller_interface::ControllerInterfaceBase
* \returns empty list.
*/
CONTROLLER_INTERFACE_PUBLIC
std::vector<hardware_interface::StateInterface> export_state_interfaces() final;
std::vector<hardware_interface::StateInterface::SharedPtr> 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<hardware_interface::CommandInterface::SharedPtr> export_reference_interfaces() 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 @@ -235,7 +235,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<hardware_interface::CommandInterface::SharedPtr>
export_reference_interfaces() = 0;

/**
* Export interfaces for a chainable controller that can be used as state interface by other
Expand All @@ -244,7 +245,7 @@ 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<hardware_interface::StateInterface::SharedPtr> export_state_interfaces() = 0;

/**
* Set chained mode of a chainable controller. This method triggers internal processes to switch
Expand Down
129 changes: 105 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,134 @@ return_type ChainableControllerInterface::update(
return ret;
}

std::vector<hardware_interface::StateInterface>
std::vector<hardware_interface::StateInterface::SharedPtr>
ChainableControllerInterface::export_state_interfaces()
{
auto state_interfaces = on_export_state_interfaces();
std::vector<hardware_interface::StateInterface::SharedPtr> state_interfaces_ptrs_vec;
state_interfaces_ptrs_vec.reserve(state_interfaces.size());
ordered_exported_state_interfaces_.reserve(state_interfaces.size());
exported_state_interface_names_.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);
}
auto state_interface = std::make_shared<hardware_interface::StateInterface>(interface);
const auto interface_name = state_interface->get_name();
auto [it, succ] = exported_state_interfaces_.insert({interface_name, state_interface});
// either we have name duplicate which we want to avoid under all circumstances since interfaces
// need to be uniquely identify able or something else really went wrong. In any case abort and
// inform cm by throwing exception
if (!succ)
{
std::string error_msg =
"Could not insert StateInterface<" + interface_name +
"> into exported_state_interfaces_ map. Check if you export duplicates. The "
"map returned iterator with interface_name<" +
it->second->get_name() +
">. If its a duplicate adjust exportation of InterfacesDescription so that all the "
"interface names are unique.";
exported_state_interfaces_.clear();
exported_state_interface_names_.clear();
state_interfaces_ptrs_vec.clear();
throw std::runtime_error(error_msg);
}
ordered_exported_state_interfaces_.push_back(state_interface);
exported_state_interface_names_.push_back(interface_name);
state_interfaces_ptrs_vec.push_back(state_interface);
}

return state_interfaces;
return state_interfaces_ptrs_vec;
}

std::vector<hardware_interface::CommandInterface>
std::vector<hardware_interface::CommandInterface::SharedPtr>
ChainableControllerInterface::export_reference_interfaces()
{
auto reference_interfaces = on_export_reference_interfaces();
std::vector<hardware_interface::CommandInterface::SharedPtr> reference_interfaces_ptrs_vec;
reference_interfaces_ptrs_vec.reserve(reference_interfaces.size());
exported_reference_interface_names_.reserve(reference_interfaces.size());
ordered_reference_interfaces_.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())
{
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);
}

hardware_interface::CommandInterface::SharedPtr reference_interface =
std::make_shared<hardware_interface::CommandInterface>(std::move(interface));
const auto inteface_name = reference_interface->get_name();
// check the exported interface name is unique
auto [it, succ] = reference_interfaces_ptrs_.insert({inteface_name, reference_interface});
// either we have name duplicate which we want to avoid under all circumstances since interfaces
// need to be uniquely identify able or something else really went wrong. In any case abort and
// inform cm by throwing exception
if (!succ)
{
std::string error_msg =
"Could not insert Reference interface<" + inteface_name +
"> into reference_interfaces_ map. Check if you export duplicates. The "
"map returned iterator with interface_name<" +
it->second->get_name() +
">. If its a duplicate adjust exportation of InterfacesDescription so that all the "
"interface names are unique.";
reference_interfaces_.clear();
exported_reference_interface_names_.clear();
reference_interfaces_ptrs_vec.clear();
throw std::runtime_error(error_msg);
}
ordered_reference_interfaces_.push_back(reference_interface);
exported_reference_interface_names_.push_back(inteface_name);
reference_interfaces_ptrs_vec.push_back(reference_interface);
}

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 +211,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<hardware_interface::StateInterface::SharedPtr>
ControllerInterface::export_state_interfaces()
{
return {};
}

std::vector<hardware_interface::CommandInterface> ControllerInterface::export_reference_interfaces()
std::vector<hardware_interface::CommandInterface::SharedPtr>
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<hardware_interface::CommandInterface::SharedPtr> 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<hardware_interface::StateInterface::SharedPtr> 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 @@ -771,15 +771,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<hardware_interface::StateInterface::SharedPtr> state_interfaces;
std::vector<hardware_interface::CommandInterface::SharedPtr> 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());
return controller_interface::return_type::ERROR;
}
resource_manager_->import_controller_reference_interfaces(controller_name, ref_interfaces);
Expand Down
Loading

0 comments on commit 34d2b36

Please sign in to comment.