From 14d827c885877369a1aad0e6076d822f8f434637 Mon Sep 17 00:00:00 2001 From: Dominic Reber Date: Thu, 2 Apr 2026 15:26:07 +0200 Subject: [PATCH 1/6] Revert "feat(controllers): add assignments in BaseControllerInterface (#236)" This reverts commit 3d4c2eef354d72403abf9548a42f7d4168044b1f. --- CHANGELOG.md | 2 + .../BaseControllerInterface.hpp | 113 ------------------ .../src/BaseControllerInterface.cpp | 3 - 3 files changed, 2 insertions(+), 116 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 041eaf21..4d65f302 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ Release Versions: ## Upcoming changes - fix(controllers): safety check for predicate publisher access (#238) +- fix(controllers): remove ABI breaking assignment methods (#240) + ## 5.4.0 diff --git a/source/modulo_controllers/include/modulo_controllers/BaseControllerInterface.hpp b/source/modulo_controllers/include/modulo_controllers/BaseControllerInterface.hpp index d68b230f..8aa8e033 100644 --- a/source/modulo_controllers/include/modulo_controllers/BaseControllerInterface.hpp +++ b/source/modulo_controllers/include/modulo_controllers/BaseControllerInterface.hpp @@ -18,7 +18,6 @@ #include #include -#include #include #include #include @@ -190,34 +189,6 @@ class BaseControllerInterface : public controller_interface::ControllerInterface template 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 - 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 - 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 - 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 @@ -527,9 +498,6 @@ class BaseControllerInterface : public controller_interface::ControllerInterface std::map>> string_services_;///< Map of StringTrigger services - state_representation::ParameterMap assignments_map_; ///< Map of assignments - std::shared_ptr> assignment_publisher_;///< Assignment publisher - std::map predicates_;///< Map of predicates std::shared_ptr> predicate_publisher_; ///< Predicate publisher @@ -989,85 +957,4 @@ BaseControllerInterface::create_service(const std::string& service_name, const s } } -template -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(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 -inline void BaseControllerInterface::set_assignment(const std::string& assignment_name, const T& assignment_value) { - modulo_interfaces::msg::Assignment message; - std::shared_ptr 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(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 -inline T BaseControllerInterface::get_assignment(const std::string& assignment_name) const { - std::shared_ptr 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(); - } 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 diff --git a/source/modulo_controllers/src/BaseControllerInterface.cpp b/source/modulo_controllers/src/BaseControllerInterface.cpp index a55e24be..8ce1c8ff 100644 --- a/source/modulo_controllers/src/BaseControllerInterface.cpp +++ b/source/modulo_controllers/src/BaseControllerInterface.cpp @@ -58,9 +58,6 @@ BaseControllerInterface::on_configure(const rclcpp_lifecycle::State&) { std::chrono::nanoseconds(static_cast(1e9 / get_parameter_value("predicate_publishing_rate"))), [this]() { this->publish_predicates(); }); } - if (assignments_map_.get_parameters().size()) { - assignment_publisher_ = get_node()->create_publisher("/assignments", qos_); - } RCLCPP_DEBUG(get_node()->get_logger(), "Configuration of BaseControllerInterface successful"); return CallbackReturn::SUCCESS; From f15a5dcaaa01ff501bd0b1e5493ab0c39f371c0c Mon Sep 17 00:00:00 2001 From: Dominic Reber Date: Thu, 2 Apr 2026 15:27:55 +0200 Subject: [PATCH 2/6] Revert "feat(components): add assignment methods (c++) (#224)" This reverts commit b8d7f0e1a5090bee7efdce415442889eb8216f49. --- CHANGELOG.md | 3 +- .../modulo_components/ComponentInterface.hpp | 107 ------------------ .../src/ComponentInterface.cpp | 3 - .../component_public_interfaces.hpp | 4 - .../test/cpp/test_component_interface.cpp | 30 ----- .../include/modulo_core/exceptions.hpp | 10 -- 6 files changed, 1 insertion(+), 156 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d65f302..c0af29f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,8 +28,7 @@ Release Versions: ## Upcoming changes - fix(controllers): safety check for predicate publisher access (#238) -- fix(controllers): remove ABI breaking assignment methods (#240) - +- fix: remove ABI breaking assignment methods from C++ components and controllers (#240) ## 5.4.0 diff --git a/source/modulo_components/include/modulo_components/ComponentInterface.hpp b/source/modulo_components/include/modulo_components/ComponentInterface.hpp index b2b43f18..dad79e84 100644 --- a/source/modulo_components/include/modulo_components/ComponentInterface.hpp +++ b/source/modulo_components/include/modulo_components/ComponentInterface.hpp @@ -17,7 +17,6 @@ #include #include -#include #include #include #include @@ -163,34 +162,6 @@ class ComponentInterface { virtual bool on_validate_parameter_callback(const std::shared_ptr& 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 - 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 - 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 - 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 @@ -583,9 +554,6 @@ class ComponentInterface { modulo_interfaces::msg::PredicateCollection predicate_message_; std::vector triggers_;///< List of triggers - state_representation::ParameterMap assignments_map_; ///< Map of assignments - std::shared_ptr> assignment_publisher_;///< Assignment publisher - std::map>> empty_services_;///< Map of EmptyTrigger services std::map>> @@ -852,79 +820,4 @@ inline void ComponentInterface::publish_transforms( "Failed to send " << modifier << "transform: " << ex.what()); } } - -template -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(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 -void ComponentInterface::set_assignment(const std::string& assignment_name, const T& assignment_value) { - modulo_interfaces::msg::Assignment message; - std::shared_ptr 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(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 -T ComponentInterface::get_assignment(const std::string& assignment_name) const { - std::shared_ptr 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(); - } 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 diff --git a/source/modulo_components/src/ComponentInterface.cpp b/source/modulo_components/src/ComponentInterface.cpp index ce745380..cc0b7c15 100644 --- a/source/modulo_components/src/ComponentInterface.cpp +++ b/source/modulo_components/src/ComponentInterface.cpp @@ -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( - this->node_parameters_, this->node_topics_, "/assignments", this->qos_); - this->predicate_publisher_ = rclcpp::create_publisher( this->node_parameters_, this->node_topics_, "/predicates", this->qos_); this->predicate_message_.node = this->node_base_->get_fully_qualified_name(); diff --git a/source/modulo_components/test/cpp/include/test_modulo_components/component_public_interfaces.hpp b/source/modulo_components/test/cpp/include/test_modulo_components/component_public_interfaces.hpp index c0aa1d2b..84016ad2 100644 --- a/source/modulo_components/test/cpp/include/test_modulo_components/component_public_interfaces.hpp +++ b/source/modulo_components/test/cpp/include/test_modulo_components/component_public_interfaces.hpp @@ -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; @@ -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; @@ -46,7 +44,6 @@ 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; @@ -54,7 +51,6 @@ class ComponentInterfacePublicInterface : public ComponentInterface { 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; diff --git a/source/modulo_components/test/cpp/test_component_interface.cpp b/source/modulo_components/test/cpp/test_component_interface.cpp index 467875c9..2d07f5ff 100644 --- a/source/modulo_components/test/cpp/test_component_interface.cpp +++ b/source/modulo_components/test/cpp/test_component_interface.cpp @@ -7,7 +7,6 @@ #include #include "test_modulo_components/component_public_interfaces.hpp" -#include "state_representation/exceptions/EmptyStateException.hpp" #include @@ -42,35 +41,6 @@ class ComponentInterfaceTest : public ::testing::Test { using NodeTypes = ::testing::Types; TYPED_TEST_SUITE(ComponentInterfaceTest, NodeTypes); -TYPED_TEST(ComponentInterfaceTest, AddAssignment) { - this->component_->template add_assignment("an_assignment"); - // adding an assignment with empty name should fail - EXPECT_NO_THROW(this->component_->template add_assignment("")); - // adding an assignment with the same name should just overwrite - this->component_->template add_assignment("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("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("@@@@@@")); - EXPECT_EQ(this->component_->assignments_map_.get_parameter_list().size(), 2); -} - -TYPED_TEST(ComponentInterfaceTest, GetSetAssignment) { - this->component_->template add_assignment("int_assignment"); - - EXPECT_THROW(this->component_->template get_assignment("non_existent"), modulo_core::exceptions::InvalidAssignmentException); - EXPECT_NO_THROW(this->component_->set_assignment("non_existent", 5)); - - EXPECT_THROW(this->component_->template get_assignment("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_assignment"), 5); - EXPECT_THROW(this->component_->template get_assignment("int_assignment"), modulo_core::exceptions::InvalidAssignmentException); -} - TYPED_TEST(ComponentInterfaceTest, AddBoolPredicate) { this->component_->add_predicate("foo", true); auto predicate_iterator = this->component_->predicates_.find("foo"); diff --git a/source/modulo_core/include/modulo_core/exceptions.hpp b/source/modulo_core/include/modulo_core/exceptions.hpp index eef2894d..feca6adc 100644 --- a/source/modulo_core/include/modulo_core/exceptions.hpp +++ b/source/modulo_core/include/modulo_core/exceptions.hpp @@ -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 From c3a721473c20e4275c637bc15571d240c0a54176 Mon Sep 17 00:00:00 2001 From: Dominic Reber Date: Thu, 2 Apr 2026 15:50:55 +0200 Subject: [PATCH 3/6] fix: revert ABI breaking change for control type --- .../include/modulo_controllers/RobotControllerInterface.hpp | 1 - source/modulo_controllers/src/RobotControllerInterface.cpp | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/modulo_controllers/include/modulo_controllers/RobotControllerInterface.hpp b/source/modulo_controllers/include/modulo_controllers/RobotControllerInterface.hpp index 61799c33..f9aa0f18 100644 --- a/source/modulo_controllers/include/modulo_controllers/RobotControllerInterface.hpp +++ b/source/modulo_controllers/include/modulo_controllers/RobotControllerInterface.hpp @@ -133,7 +133,6 @@ class RobotControllerInterface : public ControllerInterface { std::vector joints_;///< The joint names provided by a parameter std::string control_type_; ///< The high-level interface type (position, velocity, acceleration or effort) - bool control_type_fixed_; ///< If true, the control type cannot be changed after bool robot_model_required_;///< If true, check that a robot model is available on configure bool load_geometries_; ///< If true, load geometries from the URDF into the robot model diff --git a/source/modulo_controllers/src/RobotControllerInterface.cpp b/source/modulo_controllers/src/RobotControllerInterface.cpp index cda655ca..a56e5692 100644 --- a/source/modulo_controllers/src/RobotControllerInterface.cpp +++ b/source/modulo_controllers/src/RobotControllerInterface.cpp @@ -145,7 +145,6 @@ rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn RobotC for (const auto& joint : joints_) { add_command_interface(joint, control_type_); } - control_type_fixed_ = true; } auto ft_sensor_name = get_parameter("ft_sensor_name"); @@ -387,7 +386,9 @@ std::string RobotControllerInterface::get_control_type() const { } void RobotControllerInterface::set_control_type(const std::string& control_type) { - if (control_type_fixed_) { + // FIXME: this is a quick solution to prevent adding a new private attribute to the class. + // The joint_state is initialized shortly before the interfaces are added so it can be used as a proxy. + if (joint_state_.get_name() == hardware_name_) { throw std::runtime_error("Control type is fixed and cannot be changed anymore"); } if (!control_type.empty() && interface_map.count(control_type) == 0) { From df1226cfc9774cb67aed37c45b18f08957e49fdc Mon Sep 17 00:00:00 2001 From: Dominic Reber Date: Thu, 2 Apr 2026 15:52:28 +0200 Subject: [PATCH 4/6] docs: changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0af29f1..a112153f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ Release Versions: - fix(controllers): safety check for predicate publisher access (#238) - fix: remove ABI breaking assignment methods from C++ components and controllers (#240) +- fix(controllers): revert ABI breaking change for control type (240) ## 5.4.0 From 67d3c8d590c1e9906fde79251a7b90bb52c6b70c Mon Sep 17 00:00:00 2001 From: Dominic Reber Date: Thu, 2 Apr 2026 15:59:21 +0200 Subject: [PATCH 5/6] fix --- source/modulo_controllers/src/RobotControllerInterface.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/source/modulo_controllers/src/RobotControllerInterface.cpp b/source/modulo_controllers/src/RobotControllerInterface.cpp index a56e5692..5addcc94 100644 --- a/source/modulo_controllers/src/RobotControllerInterface.cpp +++ b/source/modulo_controllers/src/RobotControllerInterface.cpp @@ -21,7 +21,6 @@ RobotControllerInterface::RobotControllerInterface( bool robot_model_required, const std::string& control_type, bool load_geometries) : ControllerInterface(true), control_type_(control_type), - control_type_fixed_(false), robot_model_required_(robot_model_required), load_geometries_(load_geometries), new_joint_command_ready_(false), From 426cdb8d9a3831f01a901fd69f418b6cb3bfb8ae Mon Sep 17 00:00:00 2001 From: Dominic Reber Date: Wed, 8 Apr 2026 10:00:27 +0200 Subject: [PATCH 6/6] fix: typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a112153f..4a809b22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,7 @@ Release Versions: - fix(controllers): safety check for predicate publisher access (#238) - fix: remove ABI breaking assignment methods from C++ components and controllers (#240) -- fix(controllers): revert ABI breaking change for control type (240) +- fix(controllers): revert ABI breaking change for control type (#240) ## 5.4.0