You're reading the documentation for a development version. For the latest released version, please have a look at Kilted.
Migration Guides: Kilted Kaiju to Lyrical Luth
This list summarizes important changes between Kilted Kaiju (previous) and Lyrical Luth (current) releases, where changes to user code might be necessary.
controller_interface
ChainableControllerInterface
The
on_export_state_interfaces()method has been removed and replaced byon_export_state_interfaces_list()(#2988, #3610). The new method returns shared pointers instead of objects by value:// Old (removed) std::vector<hardware_interface::StateInterface> on_export_state_interfaces() // New std::vector<hardware_interface::StateInterface::SharedPtr> on_export_state_interfaces_list()
Example migration:
// Old implementation std::vector<hardware_interface::StateInterface> MyController::on_export_state_interfaces() { std::vector<hardware_interface::StateInterface> state_interfaces; state_interfaces.emplace_back( std::string(get_node()->get_name()) + "/my_state", "position", &my_state_value_); return state_interfaces; } // New implementation std::vector<hardware_interface::StateInterface::SharedPtr> MyController::on_export_state_interfaces_list() { my_state_itf_ = std::make_shared<hardware_interface::StateInterface>( std::string(get_node()->get_name()) + "/my_state", "position"); std::ignore = my_state_itf_->set_value(std::numeric_limits<double>::quiet_NaN()); return {my_state_itf_}; }
The
on_export_reference_interfaces()method has been removed and replaced byon_export_reference_interfaces_list()(#2988, #3610).on_export_reference_interfaces_list()is pure virtual, so every chainable controller must implement it, even if it exports no reference interfaces. The new method returns shared pointers instead of objects by value:// Old (removed) std::vector<hardware_interface::CommandInterface> on_export_reference_interfaces() // New std::vector<hardware_interface::CommandInterface::SharedPtr> on_export_reference_interfaces_list()
Example migration:
// Old implementation std::vector<hardware_interface::CommandInterface> MyController::on_export_reference_interfaces() { reference_interfaces_.resize(1, std::numeric_limits<double>::quiet_NaN()); std::vector<hardware_interface::CommandInterface> reference_interfaces; reference_interfaces.emplace_back( std::string(get_node()->get_name()) + "/my_ref", "velocity", &reference_interfaces_[0]); return reference_interfaces; } // New implementation std::vector<hardware_interface::CommandInterface::SharedPtr> MyController::on_export_reference_interfaces_list() { my_ref_itf_ = std::make_shared<hardware_interface::CommandInterface>( std::string(get_node()->get_name()) + "/my_ref", "velocity"); std::ignore = my_ref_itf_->set_value(std::numeric_limits<double>::quiet_NaN()); return {my_ref_itf_}; }
The exported state interfaces are now returned as
ConstSharedPtrfromChainableControllerInterface::export_state_interfaces()to ensure they are read-only for consumers (#1767).The internal storage variables
reference_interfaces_andstate_interfaces_values_have been removed (#2988, #3610). Values now live inside the exported interfaces themselves. Keep the shared pointers created inon_export_*_interfaces_list()as members, or use the ordered exported interface containers (ordered_exported_state_interfaces_andordered_exported_reference_interfaces_), and access the values throughget_optional()andset_value()inupdate_reference_from_subscribers(),update_and_write_commands()andon_set_chained_mode():// Old implementation controller_interface::return_type MyController::update_and_write_commands( const rclcpp::Time &, const rclcpp::Duration &) { if (!std::isnan(reference_interfaces_[0])) { std::ignore = command_interfaces_[0].set_value(reference_interfaces_[0]); } my_state_value_ = state_interfaces_[0].get_optional().value_or(my_state_value_); return controller_interface::return_type::OK; } // New implementation // my_ref_itf_ and my_state_itf_ are stored in on_export_*_interfaces_list() controller_interface::return_type MyController::update_and_write_commands( const rclcpp::Time &, const rclcpp::Duration &) { const double reference = my_ref_itf_->get_optional().value_or(std::numeric_limits<double>::quiet_NaN()); if (!std::isnan(reference)) { std::ignore = command_interfaces_[0].set_value(reference); } if (const auto state = state_interfaces_[0].get_optional(); state.has_value()) { std::ignore = my_state_itf_->set_value(state.value()); } return controller_interface::return_type::OK; }
get_optional()returnsstd::nulloptandset_value()returnsfalseif the interface lock could not be acquired, so handle these cases instead of assuming access always succeeds.Controllers exporting several interfaces can build them from a fixed list of names (#3350) and keep the returned pointers indexed the same way the old
reference_interfaces_vector was:static constexpr std::array<std::string_view, 3> reference_interface_names = { "angular_velocity.x", "angular_velocity.y", "angular_velocity.z"}; std::vector<hardware_interface::CommandInterface::SharedPtr> MyController::on_export_reference_interfaces_list() { ref_itfs_.clear(); for (const auto & name : reference_interface_names) { auto itf = std::make_shared<hardware_interface::CommandInterface>( get_node()->get_name(), std::string{name}); std::ignore = itf->set_value(std::numeric_limits<double>::quiet_NaN()); ref_itfs_.push_back(itf); } return ref_itfs_; }
ros2_controllers#2350 migrated the first-party chainable controllers and can be used as a reference.
Migration checklist for a chainable controller:
Mark the existing
on_export_state_interfaces()andon_export_reference_interfaces()overrides withoverride. If an old method is declared withoutoverride, it still compiles but is never called, so its interfaces are silently not exported; addingoverridemakes the compiler flag every method that has to change.Rename
on_export_state_interfaces()toon_export_state_interfaces_list()andon_export_reference_interfaces()toon_export_reference_interfaces_list(), changing the return types tostd::vector<...::SharedPtr>.Replace every
emplace_back(prefix, name, &storage)withstd::make_shared<...>(prefix, name), set the initial value withset_value(), and store the pointer in a member. The prefix of every exported interface must begin with the controller’s name (get_node()->get_name()), otherwise exporting throws astd::runtime_error.Implement
on_export_reference_interfaces_list()even if the controller has no reference interfaces; return an empty vector in that case.Remove the
reference_interfaces_.resize(...)andstate_interfaces_values_.resize(...)calls, usually found inon_configure()or the export methods.Replace every read of
reference_interfaces_[i]withref_itfs_[i]->get_optional()and every write withref_itfs_[i]->set_value(...). Do the same for the member variables that backed the exported state interfaces. Checkupdate_reference_from_subscribers(),update_and_write_commands(),on_set_chained_mode(),on_activate()andon_deactivate(), plus the controller’s tests.Search the package for
reference_interfaces_,state_interfaces_values_,on_export_state_interfaces()andon_export_reference_interfaces()in the controller sources; no matches should remain. Hardware components in the same package still useon_export_state_interfaces(), so ignore matches there.
controller_manager
configure_controllernow performs a single-step lifecycle transition and only accepts controllers in theunconfiguredstate (#3196). Previously aninactivecontroller was implicitly cleaned up and then reconfigured. To reconfigure aninactivecontroller, callcleanup_controllerfirst and thenconfigure_controller.The controller manager’s ros arguments are no longer forwarded to the controllers via NodeOptions. (#3016) So, any remapping done at the controller manager level will not be visible to the controllers anymore. It is recommended to use the
--controller-ros-argsoption of the spawner to pass ros arguments to controllers.control_node = Node( package="controller_manager", executable="ros2_control_node", parameters=[robot_controllers], remappings=[("/diffbot_base_controller/cmd_vel", "/cmd_vel")], output="both", )
to
control_node = Node( package="controller_manager", executable="ros2_control_node", parameters=[robot_controllers], output="both", ) spawner_node = Node( package="controller_manager", executable="spawner", arguments=[ "diffbot_base_controller", "--controller-ros-args", "--remap", "/diffbot_base_controller/cmd_vel:=/cmd_vel", ], )
hardware_interface
The signature for the
on_initmethod in allhardware_interface::*Interfaceclasses has changed (#2323, #2589) fromCallbackReturn on_init(const hardware_interface::HardwareInfo& info)
to
CallbackReturn on_init(const HardwareComponentInterfaceParams& params)
The
HardwareInfoobject can be accessed from theHardwareComponentInterfaceParamsobject usingparams.hardware_info. See Writing a Hardware Component for advanced usage of theHardwareComponentInterfaceParamsobject.The signature for the
init()method in allhardware_interface::*Interfaceclasses has changed (#2344, #2589) fromCallbackReturn init(const HardwareInfo & hardware_info, rclcpp::Logger logger, rclcpp::Clock::SharedPtr clock)
to
CallbackReturn init(const hardware_interface::HardwareComponentParams & params)
The
initializemethods of all hardware components (such asActuator,Sensor, etc.) have been changed from passing aconst HardwareInfo &to passing aconst HardwareComponentParams &(#2323, #2589).The
get_valueof LoanedStateInterface and LoanedCommandInterface is now accessed usingget_optionalmethod. The value will be returned as anstd::optional<T>. (#2061).This change was made to better handle cases where the interface value may not be accessible due to a concurrent access from other threads in the system.
The
double get_value()of standard StateInterface and CommandInterface is now accessed usingget_optionalorbool get_value(T & value, bool wait_for_lock)method. The value will be returned as anstd::optional<T>when usingget_optional(#2831).Likewise, the
set_valuemethod has been updated tobool set_value(const T & value, bool wait_for_lock)and return value is to indicate success or failure of the operation (#2831).You can use the return values of these methods to handle cases where the interface value may not be accessible due to a concurrent access from other threads in the system. You can set the
wait_for_lockparameter totrueto block until the lock is acquired, however, this is not real-time safe and should be used with caution in real-time contexts.The
export_state_interfaces()andexport_command_interfaces()methods ofHardwareComponentInterfacehave been removed (#3610). Hardware components can no longer export interfaces backed by their own member variables. Interfaces defined in theros2_controltag are created and exported by the framework, and their values are accessed withset_state/get_stateandset_command/get_command:// Old implementation std::vector<hardware_interface::StateInterface> MyHardware::export_state_interfaces() { std::vector<hardware_interface::StateInterface> state_interfaces; state_interfaces.emplace_back( info_.joints[0].name, hardware_interface::HW_IF_POSITION, &hw_position_); return state_interfaces; } hardware_interface::return_type MyHardware::read(const rclcpp::Time &, const rclcpp::Duration &) { hw_position_ = read_position_from_hardware(); return hardware_interface::return_type::OK; } // New implementation, no export method needed hardware_interface::return_type MyHardware::read(const rclcpp::Time &, const rclcpp::Duration &) { set_state(info_.joints[0].name + "/" + hardware_interface::HW_IF_POSITION, read_position_from_hardware()); return hardware_interface::return_type::OK; }
Interfaces not listed in the
ros2_controltag are added by overridingexport_unlisted_state_interface_descriptions()orexport_unlisted_command_interface_descriptions(). Overrideon_export_state_interfaces()oron_export_command_interfaces()only if full control over the exported interfaces is needed. See Writing a Hardware Component for details.Loops over the old storage vectors map to loops over the interface maps of the framework (
joint_state_interfaces_,joint_command_interfaces_, and likewise forsensor_,gpio_andunlisted_), keyed by the fully qualified interface name, e.g.prefix/joint_1/velocity. The example below assumes all interfaces in the map are of typedouble;set_statethrows for interfaces of other data types:// Old implementation for (size_t i = 0; i < hw_states_.size(); ++i) { hw_states_[i] = 0.0; } hw_commands_[x] = hw_states_[y]; // New implementation for (const auto & [name, descr] : joint_state_interfaces_) { set_state(name, 0.0); } set_command(name_of_command_interface_x, get_state(name_of_state_interface_y));
Migration checklist for a hardware component:
Mark the existing
export_state_interfaces()andexport_command_interfaces()overrides withoverride. If an old method is declared withoutoverride, it still compiles but is never called, so its interfaces are silently not exported; addingoverridemakes the compiler flag every method that has to change.Delete the
export_state_interfaces()andexport_command_interfaces()overrides.Make sure every interface they exported is listed in the
ros2_controltag of the URDF; move the rest toexport_unlisted_state_interface_descriptions()orexport_unlisted_command_interface_descriptions().In
read(), replace assignments to the member variables that backed the state interfaces (e.g.hw_positions_[i] = ...) withset_state("<joint>/<interface>", value).In
write(), replace reads of the member variables that backed the command interfaces withget_command<double>("<joint>/<interface>"). Do the same inon_activate(),on_deactivate()andperform_command_mode_switch().Delete the now unused storage vectors (
hw_states_,hw_commands_and similar) and theirresize()calls inon_init().Search the package for
export_state_interfacesandexport_command_interfaces, and for addresses of the old storage members (e.g.&hw_); no matches should remain.
This migration has been possible since Jazzy, see the Jazzy migration guide.
The
Handle(prefix_name, interface_name, double * value_ptr)constructor ofStateInterfaceandCommandInterfacehas been removed (#3610). Handles now always own their value. Construct them from anInterfaceDescriptionor withHandle(prefix_name, interface_name, data_type, initial_value), wheredata_typeandinitial_valueare strings defaulting to"double"and""(NaN). For a double interfaceHandle(prefix_name, interface_name)is enough.Handle::operator bool()has been removed (#3610). Useis_valid()instead.set_value<T>()on a handle now throws astd::runtime_errorifTdoes not match the handle’s data type, includingdouble(#3610). This error is raised at runtime, not at compile time:set_value(0)on a double interface throws because0is anint; useset_value(0.0). The same applies toset_stateandset_commandof hardware components.