Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Release Versions:
## Upcoming changes

- fix(controllers): safety check for predicate publisher access (#238)
- fix: remove ABI breaking assignment methods from C++ components and controllers (#240)

## 5.4.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
#include <modulo_core/exceptions.hpp>
#include <modulo_core/translators/parameter_translators.hpp>

#include <modulo_interfaces/msg/assignment.hpp>
#include <modulo_interfaces/msg/predicate_collection.hpp>
#include <modulo_interfaces/srv/empty_trigger.hpp>
#include <modulo_interfaces/srv/string_trigger.hpp>
Expand Down Expand Up @@ -163,34 +162,6 @@ class ComponentInterface {
virtual bool
on_validate_parameter_callback(const std::shared_ptr<state_representation::ParameterInterface>& parameter);

/**
* @brief Add an assignment to the map of assignments.
* @tparam T The type of the assignment
* @param assignment_name the name of the associated assignment
*/
template<typename T>
void add_assignment(const std::string& assignment_name);

/**
* @brief Set the value of an assignment.
* @tparam T The type of the assignment
* @param assignment_name The name of the assignment to set
* @param assignment_value The value of the assignment
*/
template<typename T>
void set_assignment(const std::string& assignment_name, const T& assignment_value);

/**
* @brief Get the value of an assignment.
* @tparam T The type of the assignment
* @param assignment_name The name of the assignment to get
* @throws modulo_core::exceptions::InvalidAssignmentException if the assignment does not exist or the type does not
match
@throws state_representation::exceptions::EmptyStateException if the assignment has not been set yet
*/
template<typename T>
T get_assignment(const std::string& assignment_name) const;

/**
* @brief Add a predicate to the map of predicates.
* @param predicate_name the name of the associated predicate
Expand Down Expand Up @@ -583,9 +554,6 @@ class ComponentInterface {
modulo_interfaces::msg::PredicateCollection predicate_message_;
std::vector<std::string> triggers_;///< List of triggers

state_representation::ParameterMap assignments_map_; ///< Map of assignments
std::shared_ptr<rclcpp::Publisher<modulo_interfaces::msg::Assignment>> assignment_publisher_;///< Assignment publisher

std::map<std::string, std::shared_ptr<rclcpp::Service<modulo_interfaces::srv::EmptyTrigger>>>
empty_services_;///< Map of EmptyTrigger services
std::map<std::string, std::shared_ptr<rclcpp::Service<modulo_interfaces::srv::StringTrigger>>>
Expand Down Expand Up @@ -852,79 +820,4 @@ inline void ComponentInterface::publish_transforms(
"Failed to send " << modifier << "transform: " << ex.what());
}
}

template<typename T>
inline void ComponentInterface::add_assignment(const std::string& assignment_name) {
std::string parsed_name = modulo_utils::parsing::parse_topic_name(assignment_name);
if (parsed_name.empty()) {
RCLCPP_ERROR_STREAM(
this->node_logging_->get_logger(),
"The parsed name for assignment '" + assignment_name
+ "' is empty. Provide a string with valid characters for the assignment name ([a-z0-9_]).");
return;
}
if (assignment_name != parsed_name) {
RCLCPP_WARN_STREAM(
this->node_logging_->get_logger(),
"The parsed name for assignment '" + assignment_name + "' is '" + parsed_name
+ "'. Use the parsed name to refer to this assignment.");
}
try {
this->assignments_map_.get_parameter(parsed_name);
RCLCPP_WARN_STREAM(
this->node_logging_->get_logger(), "Assignment with name '" + parsed_name + "' already exists, overwriting.");
} catch (const state_representation::exceptions::InvalidParameterException& ex) {
RCLCPP_DEBUG_STREAM(this->node_logging_->get_logger(), "Adding assignment '" << parsed_name << "'.");
}
try {
assignments_map_.set_parameter(state_representation::make_shared_parameter<T>(parsed_name));
} catch (const std::exception& ex) {
RCLCPP_ERROR_STREAM_THROTTLE(
this->node_logging_->get_logger(), *this->node_clock_->get_clock(), 1000,
"Failed to add assignment '" << parsed_name << "': " << ex.what());
}
}

template<typename T>
void ComponentInterface::set_assignment(const std::string& assignment_name, const T& assignment_value) {
modulo_interfaces::msg::Assignment message;
std::shared_ptr<state_representation::ParameterInterface> assignment;
try {
assignment = this->assignments_map_.get_parameter(assignment_name);
} catch (const state_representation::exceptions::InvalidParameterException&) {
RCLCPP_ERROR_STREAM_THROTTLE(
this->node_logging_->get_logger(), *this->node_clock_->get_clock(), 1000,
"Failed to set assignment '" << assignment_name << "': Assignment does not exist.");
return;
}
try {
assignment->set_parameter_value<T>(assignment_value);
} catch (const state_representation::exceptions::InvalidParameterCastException&) {
RCLCPP_ERROR_STREAM_THROTTLE(
this->node_logging_->get_logger(), *this->node_clock_->get_clock(), 1000,
"Failed to set assignment '" << assignment_name << "': Incompatible value type.");
return;
}
message.node = this->node_base_->get_fully_qualified_name();
message.assignment = modulo_core::translators::write_parameter(assignment).to_parameter_msg();
this->assignment_publisher_->publish(message);
}

template<typename T>
T ComponentInterface::get_assignment(const std::string& assignment_name) const {
std::shared_ptr<state_representation::ParameterInterface> assignment;
try {
assignment = this->assignments_map_.get_parameter(assignment_name);
} catch (const state_representation::exceptions::InvalidParameterException&) {
throw modulo_core::exceptions::InvalidAssignmentException(
"Failed to get value of assignment '" + assignment_name + "': Assignment does not exist.");
}
try {
return assignment->get_parameter_value<T>();
} catch (const state_representation::exceptions::InvalidParameterCastException&) {
auto expected_type = state_representation::get_parameter_type_name(assignment->get_parameter_type());
throw modulo_core::exceptions::InvalidAssignmentException(
"Incompatible type for assignment '" + assignment_name + "' defined with type '" + expected_type + "'.");
}
}
}// namespace modulo_components
3 changes: 0 additions & 3 deletions source/modulo_components/src/ComponentInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ ComponentInterface::ComponentInterface(
});
this->add_parameter("rate", 10.0, "The rate in Hertz for all periodic callbacks", true);

this->assignment_publisher_ = rclcpp::create_publisher<modulo_interfaces::msg::Assignment>(
this->node_parameters_, this->node_topics_, "/assignments", this->qos_);

this->predicate_publisher_ = rclcpp::create_publisher<modulo_interfaces::msg::PredicateCollection>(
this->node_parameters_, this->node_topics_, "/predicates", this->qos_);
this->predicate_message_.node = this->node_base_->get_fully_qualified_name();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ class ComponentInterfacePublicInterface : public ComponentInterface {
using ComponentInterface::add_input;
using ComponentInterface::add_parameter;
using ComponentInterface::add_predicate;
using ComponentInterface::add_assignment;
using ComponentInterface::add_service;
using ComponentInterface::add_static_tf_broadcaster;
using ComponentInterface::add_tf_broadcaster;
Expand All @@ -31,7 +30,6 @@ class ComponentInterfacePublicInterface : public ComponentInterface {
using ComponentInterface::declare_input;
using ComponentInterface::declare_output;
using ComponentInterface::empty_services_;
using ComponentInterface::get_assignment;
using ComponentInterface::get_parameter;
using ComponentInterface::get_parameter_value;
using ComponentInterface::get_predicate;
Expand All @@ -46,15 +44,13 @@ class ComponentInterfacePublicInterface : public ComponentInterface {
using ComponentInterface::parameter_map_;
using ComponentInterface::periodic_outputs_;
using ComponentInterface::predicates_;
using ComponentInterface::assignments_map_;
using ComponentInterface::publish_output;
using ComponentInterface::raise_error;
using ComponentInterface::remove_input;
using ComponentInterface::send_static_transform;
using ComponentInterface::send_static_transforms;
using ComponentInterface::send_transform;
using ComponentInterface::send_transforms;
using ComponentInterface::set_assignment;
using ComponentInterface::set_parameter_value;
using ComponentInterface::set_predicate;
using ComponentInterface::set_qos;
Expand Down
30 changes: 0 additions & 30 deletions source/modulo_components/test/cpp/test_component_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
#include <modulo_utils/testutils/ServiceClient.hpp>

#include "test_modulo_components/component_public_interfaces.hpp"
#include "state_representation/exceptions/EmptyStateException.hpp"

#include <sensor_msgs/msg/image.hpp>

Expand Down Expand Up @@ -42,35 +41,6 @@ class ComponentInterfaceTest : public ::testing::Test {
using NodeTypes = ::testing::Types<rclcpp::Node, rclcpp_lifecycle::LifecycleNode>;
TYPED_TEST_SUITE(ComponentInterfaceTest, NodeTypes);

TYPED_TEST(ComponentInterfaceTest, AddAssignment) {
this->component_->template add_assignment<int>("an_assignment");
// adding an assignment with empty name should fail
EXPECT_NO_THROW(this->component_->template add_assignment<int>(""));
// adding an assignment with the same name should just overwrite
this->component_->template add_assignment<int>("an_assignment");
EXPECT_EQ(this->component_->assignments_map_.get_parameter_list().size(), 1);
// names should be cleaned up
EXPECT_NO_THROW(this->component_->template add_assignment<int>("7cleEaGn_AaSssiGNgn#ment"));
EXPECT_EQ(this->component_->assignments_map_.get_parameter_list().size(), 2);
// names without valid characters should fail
EXPECT_NO_THROW(this->component_->template add_assignment<int>("@@@@@@"));
EXPECT_EQ(this->component_->assignments_map_.get_parameter_list().size(), 2);
}

TYPED_TEST(ComponentInterfaceTest, GetSetAssignment) {
this->component_->template add_assignment<int>("int_assignment");

EXPECT_THROW(this->component_->template get_assignment<int>("non_existent"), modulo_core::exceptions::InvalidAssignmentException);
EXPECT_NO_THROW(this->component_->set_assignment("non_existent", 5));

EXPECT_THROW(this->component_->template get_assignment<int>("int_assignment"), state_representation::exceptions::EmptyStateException);
EXPECT_NO_THROW(this->component_->set_assignment("int_assignment", 5));
EXPECT_NO_THROW(this->component_->set_assignment("int_assignment", std::string("test")));

EXPECT_EQ(this->component_->template get_assignment<int>("int_assignment"), 5);
EXPECT_THROW(this->component_->template get_assignment<std::string>("int_assignment"), modulo_core::exceptions::InvalidAssignmentException);
}

TYPED_TEST(ComponentInterfaceTest, AddBoolPredicate) {
this->component_->add_predicate("foo", true);
auto predicate_iterator = this->component_->predicates_.find("foo");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
#include <modulo_core/translators/message_writers.hpp>
#include <modulo_core/translators/parameter_translators.hpp>

#include <modulo_interfaces/msg/assignment.hpp>
#include <modulo_interfaces/msg/predicate_collection.hpp>
#include <modulo_interfaces/srv/empty_trigger.hpp>
#include <modulo_interfaces/srv/string_trigger.hpp>
Expand Down Expand Up @@ -190,34 +189,6 @@ class BaseControllerInterface : public controller_interface::ControllerInterface
template<typename T>
void set_parameter_value(const std::string& name, const T& value);

/**
* @brief Add an assignment to the map of assignments.
* @tparam T The type of the assignment
* @param assignment_name the name of the associated assignment
*/
template<typename T>
void add_assignment(const std::string& assignment_name);

/**
* @brief Set an assignment.
* @tparam T The type of the assignment
* @param assignment_name The name of the assignment to publish
* @param assignment_value The value of the assignment
*/
template<typename T>
void set_assignment(const std::string& assignment_name, const T& assignment_value);

/**
* @brief Get the value of an assignment.
* @tparam T The type of the assignment value
* @param assignment_name The name of the assignment to get
* @throws modulo_core::exceptions::InvalidAssignmentException if the assignment does not exist or the type does not
match
@throws state_representation::exceptions::EmptyStateException if the assignment has not been set yet
*/
template<typename T>
T get_assignment(const std::string& assignment_name) const;

/**
* @brief Add a predicate to the map of predicates.
* @param predicate_name the name of the associated predicate
Expand Down Expand Up @@ -527,9 +498,6 @@ class BaseControllerInterface : public controller_interface::ControllerInterface
std::map<std::string, std::shared_ptr<rclcpp::Service<modulo_interfaces::srv::StringTrigger>>>
string_services_;///< Map of StringTrigger services

state_representation::ParameterMap assignments_map_; ///< Map of assignments
std::shared_ptr<rclcpp::Publisher<modulo_interfaces::msg::Assignment>> assignment_publisher_;///< Assignment publisher

std::map<std::string, modulo_core::Predicate> predicates_;///< Map of predicates
std::shared_ptr<rclcpp::Publisher<modulo_interfaces::msg::PredicateCollection>>
predicate_publisher_; ///< Predicate publisher
Expand Down Expand Up @@ -989,85 +957,4 @@ BaseControllerInterface::create_service(const std::string& service_name, const s
}
}

template<typename T>
inline void BaseControllerInterface::add_assignment(const std::string& assignment_name) {
std::string parsed_name = modulo_utils::parsing::parse_topic_name(assignment_name);
if (parsed_name.empty()) {
RCLCPP_ERROR_STREAM(
get_node()->get_logger(),
"The parsed name for assignment '" + assignment_name
+ "' is empty. Provide a string with valid characters for the assignment name ([a-z0-9_]).");
return;
}
if (assignment_name != parsed_name) {
RCLCPP_WARN_STREAM(
get_node()->get_logger(),
"The parsed name for assignment '" + assignment_name + "' is '" + parsed_name
+ "'. Use the parsed name to refer to this assignment.");
}
try {
this->assignments_map_.get_parameter(parsed_name);
RCLCPP_WARN_STREAM(
get_node()->get_logger(), "Assignment with name '" + parsed_name + "' already exists, overwriting.");
} catch (const state_representation::exceptions::InvalidParameterException& ex) {
RCLCPP_DEBUG_STREAM(get_node()->get_logger(), "Adding assignment '" << parsed_name << "'.");
}
try {
assignments_map_.set_parameter(state_representation::make_shared_parameter<T>(parsed_name));
} catch (const std::exception& ex) {
RCLCPP_ERROR_STREAM_THROTTLE(
get_node()->get_logger(), *get_node()->get_clock(), 1000,
"Failed to add assignment '" << parsed_name << "': " << ex.what());
}
}

template<typename T>
inline void BaseControllerInterface::set_assignment(const std::string& assignment_name, const T& assignment_value) {
modulo_interfaces::msg::Assignment message;
std::shared_ptr<state_representation::ParameterInterface> assignment;
try {
assignment = assignments_map_.get_parameter(assignment_name);
} catch (const state_representation::exceptions::InvalidParameterException&) {
RCLCPP_ERROR_STREAM_THROTTLE(
get_node()->get_logger(), *get_node()->get_clock(), 1000,
"Failed to set assignment '" << assignment_name << "': Assignment does not exist.");
return;
}
try {
assignment->set_parameter_value<T>(assignment_value);
} catch (const state_representation::exceptions::InvalidParameterCastException&) {
RCLCPP_ERROR_STREAM_THROTTLE(
get_node()->get_logger(), *get_node()->get_clock(), 1000,
"Failed to set assignment '" << assignment_name << "': Incompatible value type.");
return;
}
if (assignment_publisher_ == nullptr) {
RCLCPP_ERROR_STREAM_THROTTLE(
get_node()->get_logger(), *get_node()->get_clock(), 1000,
"No assignment publisher configured. Make sure to add assignments `on_init` of the controller.");
return;
}
message.node = get_node()->get_fully_qualified_name();
message.assignment = modulo_core::translators::write_parameter(assignment).to_parameter_msg();
assignment_publisher_->publish(message);
}

template<typename T>
inline T BaseControllerInterface::get_assignment(const std::string& assignment_name) const {
std::shared_ptr<state_representation::ParameterInterface> assignment;
try {
assignment = assignments_map_.get_parameter(assignment_name);
} catch (const state_representation::exceptions::InvalidParameterException&) {
throw modulo_core::exceptions::InvalidAssignmentException(
"Failed to get value of assignment '" + assignment_name + "': Assignment does not exist.");
}
try {
return assignment->get_parameter_value<T>();
} catch (const state_representation::exceptions::InvalidParameterCastException&) {
auto expected_type = state_representation::get_parameter_type_name(assignment->get_parameter_type());
throw modulo_core::exceptions::InvalidAssignmentException(
"Incompatible type for assignment '" + assignment_name + "' defined with type '" + expected_type + "'.");
}
}

}// namespace modulo_controllers
3 changes: 0 additions & 3 deletions source/modulo_controllers/src/BaseControllerInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,6 @@ BaseControllerInterface::on_configure(const rclcpp_lifecycle::State&) {
std::chrono::nanoseconds(static_cast<int64_t>(1e9 / get_parameter_value<double>("predicate_publishing_rate"))),
[this]() { this->publish_predicates(); });
}
if (assignments_map_.get_parameters().size()) {
assignment_publisher_ = get_node()->create_publisher<modulo_interfaces::msg::Assignment>("/assignments", qos_);
}

RCLCPP_DEBUG(get_node()->get_logger(), "Configuration of BaseControllerInterface successful");
return CallbackReturn::SUCCESS;
Expand Down
10 changes: 0 additions & 10 deletions source/modulo_core/include/modulo_core/exceptions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,6 @@ class AddSignalException : public CoreException {
explicit AddSignalException(const std::string& msg) : CoreException("AddSignalException", msg) {}
};

/**
* @class InvalidAssignmentException
* @brief An exception class to notify errors when getting the value of an assignment.
* @details This is an exception class to be thrown if there is a problem while getting the value of an assignment in a modulo class.
*/
class InvalidAssignmentException : public CoreException {
public:
explicit InvalidAssignmentException(const std::string& msg) : CoreException("InvalidAssignmentException", msg) {}
};

/**
* @class InvalidPointerCastException
* @brief An exception class to notify if the result of getting an instance of a derived class through dynamic
Expand Down
Loading