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

[Feature] Hardware Components Grouping #1458

Merged
merged 19 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
fdca293
added the group element to HardwareInfo and added its parsing
saikishor Mar 22, 2024
bc742fa
Add group to the hardware component info
saikishor Mar 22, 2024
35f2cae
Added the get_group_name method to different components and their int…
saikishor Mar 22, 2024
8197452
Added a method to update the component group return type state
saikishor Mar 22, 2024
d7956f5
use the hardware grouping logic in the read and write methods
saikishor Mar 22, 2024
26cc638
reset the hardware group state on configure, cleanup and shutdown of …
saikishor Mar 22, 2024
9961789
Fix a typo in the component parser
saikishor Mar 22, 2024
874475f
Add the hardware component group tests to the component parser
saikishor Mar 22, 2024
9d6e711
update the tests of the generic system
saikishor Mar 22, 2024
9c8cf01
if infinity is set to the command interfaces then return ERROR in moc…
saikishor Mar 24, 2024
c61ebab
Add testing for the same group hardware error propagation
saikishor Mar 24, 2024
a6da599
added testing for the recoverable phases of error propagation
saikishor Mar 24, 2024
fffcdfd
Apply formatting changes
saikishor Mar 24, 2024
f5f20c8
Add documentation on hardware grouping
saikishor Mar 24, 2024
65664c3
Added hardware component grouping example
saikishor Mar 24, 2024
d3e2e86
update tests to use IsEmpty from gmock
saikishor Mar 26, 2024
c357aeb
add pre-commit changes after the rebase
saikishor May 11, 2024
a0a40b3
Merge branch 'master' into feat/hardware_grouping
bmagyar May 20, 2024
63e4062
Apply suggestions from Bence's code review
saikishor May 21, 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
69 changes: 69 additions & 0 deletions hardware_interface/doc/hardware_interface_types_userdoc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ The ``<gpio>`` tag can be used as a child of all three types of hardware compone
Because ports implemented as ``<gpio>``-tag are typically very application-specific, there exists no generic publisher
within the ros2_control framework. A custom gpio-controller has to be implemented for each application. As an example, see :ref:`the GPIO controller example <ros2_control_demos_example_10_userdoc>` as part of the demo repository.

Hardware Groups
*****************************
Hardware Component Groups serve as a critical organizational mechanism within complex systems, facilitating error handling and fault tolerance. By grouping related hardware components together, such as actuators within a manipulator, users can establish a unified framework for error detection and response.

Hardware Component Groups play a vital role in propagating errors across interconnected hardware components. For instance, in a manipulator system, grouping actuators together allows for error propagation. If one actuator fails within the group, the error can propagate to the other actuators, signaling a potential issue across the system. By default, the actuator errors are isolated to their own hardware component, allowing the rest to continue operation unaffected. In the provided ROS2 control configuration, the ``<group>`` tag within each ``<ros2_control>`` block signifies the grouping of hardware components, enabling error propagation mechanisms within the system.
saikishor marked this conversation as resolved.
Show resolved Hide resolved

Examples
*****************************
The following examples show how to use the different hardware interface types in a ``ros2_control`` URDF.
Expand Down Expand Up @@ -152,3 +158,66 @@ They can be combined together within the different hardware component types (sys
<state_interface name="calibration_matrix_nr"/>
</gpio>
</ros2_control>

4. Robot with multiple hardware components belonging to same group : ``Group1``

- RRBot System 1 and 2
- Digital: Total 4 inputs and 2 outputs
- Analog: Total 2 inputs and 1 output
- Vacuum valve at the flange (on/off)
- Group: Group1

.. code:: xml

<ros2_control name="RRBotSystem1" type="system">
<hardware>
<plugin>ros2_control_demo_hardware/RRBotSystemPositionOnlyHardware</plugin>
<group>Group1</group>
<param name="example_param_hw_start_duration_sec">2.0</param>
<param name="example_param_hw_stop_duration_sec">3.0</param>
<param name="example_param_hw_slowdown">2.0</param>
</hardware>
<joint name="joint1">
<command_interface name="position">
<param name="min">-1</param>
<param name="max">1</param>
</command_interface>
<state_interface name="position"/>
</joint>
<gpio name="flange_analog_IOs">
<command_interface name="analog_output1"/>
<state_interface name="analog_output1"> <!-- Needed to know current state of the output -->
<param name="initial_value">3.1</param> <!-- Optional initial value for mock_hardware -->
</state_interface>
<state_interface name="analog_input1"/>
<state_interface name="analog_input2"/>
</gpio>
<gpio name="flange_vacuum">
<command_interface name="vacuum"/>
<state_interface name="vacuum"/> <!-- Needed to know current state of the output -->
</gpio>
</ros2_control>
<ros2_control name="RRBotSystem2" type="system">
<hardware>
<plugin>ros2_control_demo_hardware/RRBotSystemPositionOnlyHardware</plugin>
<group>Group1</group>
<param name="example_param_hw_start_duration_sec">2.0</param>
<param name="example_param_hw_stop_duration_sec">3.0</param>
<param name="example_param_hw_slowdown">2.0</param>
</hardware>
<joint name="joint2">
<command_interface name="position">
<param name="min">-1</param>
<param name="max">1</param>
</command_interface>
<state_interface name="position"/>
</joint>
<gpio name="flange_digital_IOs">
<command_interface name="digital_output1"/>
<state_interface name="digital_output1"/> <!-- Needed to know current state of the output -->
<command_interface name="digital_output2"/>
<state_interface name="digital_output2"/>
<state_interface name="digital_input1"/>
<state_interface name="digital_input2"/>
</gpio>
</ros2_control>
3 changes: 3 additions & 0 deletions hardware_interface/include/hardware_interface/actuator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ class Actuator final
HARDWARE_INTERFACE_PUBLIC
std::string get_name() const;

HARDWARE_INTERFACE_PUBLIC
std::string get_group_name() const;

HARDWARE_INTERFACE_PUBLIC
const rclcpp_lifecycle::State & get_state() const;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,12 @@ class ActuatorInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNod
*/
virtual std::string get_name() const { return info_.name; }

/// Get name of the actuator hardware group to which it belongs to.
/**
* \return group name.
*/
virtual std::string get_group_name() const { return info_.group; }

/// Get life-cycle state of the actuator hardware.
/**
* \return state.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ struct HardwareComponentInfo
/// Component "classification": "actuator", "sensor" or "system"
std::string type;

/// Component group
std::string group;

/// Component pluginlib plugin name.
std::string plugin_name;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ struct HardwareInfo
std::string name;
/// Type of the hardware: actuator, sensor or system.
std::string type;
/// Hardware group to which the hardware belongs.
std::string group;
/// Component is async
bool is_async;
/// Name of the pluginlib plugin of the hardware that will be loaded.
Expand Down
3 changes: 3 additions & 0 deletions hardware_interface/include/hardware_interface/sensor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ class Sensor final
HARDWARE_INTERFACE_PUBLIC
std::string get_name() const;

HARDWARE_INTERFACE_PUBLIC
std::string get_group_name() const;

HARDWARE_INTERFACE_PUBLIC
const rclcpp_lifecycle::State & get_state() const;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ class SensorInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNodeI
*/
virtual std::string get_name() const { return info_.name; }

/// Get name of the actuator hardware group to which it belongs to.
/**
* \return group name.
*/
virtual std::string get_group_name() const { return info_.group; }

/// Get life-cycle state of the actuator hardware.
/**
* \return state.
Expand Down
3 changes: 3 additions & 0 deletions hardware_interface/include/hardware_interface/system.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ class System final
HARDWARE_INTERFACE_PUBLIC
std::string get_name() const;

HARDWARE_INTERFACE_PUBLIC
std::string get_group_name() const;

HARDWARE_INTERFACE_PUBLIC
const rclcpp_lifecycle::State & get_state() const;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@ class SystemInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNodeI
*/
virtual std::string get_name() const { return info_.name; }

/// Get name of the actuator hardware group to which it belongs to.
/**
* \return group name.
*/
virtual std::string get_group_name() const { return info_.group; }

/// Get life-cycle state of the actuator hardware.
/**
* \return state.
Expand Down
2 changes: 2 additions & 0 deletions hardware_interface/src/actuator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ return_type Actuator::perform_command_mode_switch(

std::string Actuator::get_name() const { return impl_->get_name(); }

std::string Actuator::get_group_name() const { return impl_->get_group_name(); }

const rclcpp_lifecycle::State & Actuator::get_state() const { return impl_->get_state(); }

return_type Actuator::read(const rclcpp::Time & time, const rclcpp::Duration & period)
Expand Down
6 changes: 6 additions & 0 deletions hardware_interface/src/component_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ constexpr const auto kROS2ControlTag = "ros2_control";
constexpr const auto kHardwareTag = "hardware";
constexpr const auto kPluginNameTag = "plugin";
constexpr const auto kParamTag = "param";
constexpr const auto kGroupTag = "group";
constexpr const auto kActuatorTag = "actuator";
constexpr const auto kJointTag = "joint";
constexpr const auto kSensorTag = "sensor";
Expand Down Expand Up @@ -578,6 +579,11 @@ HardwareInfo parse_resource_from_xml(
const auto * type_it = ros2_control_child_it->FirstChildElement(kPluginNameTag);
hardware.hardware_plugin_name =
get_text_for_element(type_it, std::string("hardware ") + kPluginNameTag);
const auto * group_it = ros2_control_child_it->FirstChildElement(kGroupTag);
if (group_it)
{
hardware.group = get_text_for_element(group_it, std::string("hardware.") + kGroupTag);
}
const auto * params_it = ros2_control_child_it->FirstChildElement(kParamTag);
if (params_it)
{
Expand Down
18 changes: 11 additions & 7 deletions hardware_interface/src/mock_components/generic_system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,8 @@ return_type GenericSystem::read(const rclcpp::Time & /*time*/, const rclcpp::Dur
return return_type::OK;
}

auto mirror_command_to_state = [](auto & states_, auto commands_, size_t start_index = 0)
auto mirror_command_to_state =
[](auto & states_, auto commands_, size_t start_index = 0) -> return_type
{
for (size_t i = start_index; i < states_.size(); ++i)
{
Expand All @@ -469,8 +470,13 @@ return_type GenericSystem::read(const rclcpp::Time & /*time*/, const rclcpp::Dur
{
states_[i][j] = commands_[i][j];
}
if (std::isinf(commands_[i][j]))
{
return return_type::ERROR;
}
}
}
return return_type::OK;
};

for (size_t j = 0; j < joint_states_[POSITION_INTERFACE_INDEX].size(); ++j)
Expand Down Expand Up @@ -556,13 +562,11 @@ return_type GenericSystem::read(const rclcpp::Time & /*time*/, const rclcpp::Dur

// do loopback on all other interfaces - starts from 1 or 3 because 0, 1, 3 are position,
// velocity, and acceleration interface
if (calculate_dynamics_)
{
mirror_command_to_state(joint_states_, joint_commands_, 3);
}
else
if (
mirror_command_to_state(joint_states_, joint_commands_, calculate_dynamics_ ? 3 : 1) !=
return_type::OK)
{
mirror_command_to_state(joint_states_, joint_commands_, 1);
return return_type::ERROR;
}

for (const auto & mimic_joint : info_.mimic_joints)
Expand Down
42 changes: 42 additions & 0 deletions hardware_interface/src/resource_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,12 @@ class ResourceStorage
HardwareComponentInfo component_info;
component_info.name = hardware_info.name;
component_info.type = hardware_info.type;
component_info.group = hardware_info.group;
component_info.plugin_name = hardware_info.hardware_plugin_name;
component_info.is_async = hardware_info.is_async;

hardware_info_map_.insert(std::make_pair(component_info.name, component_info));
hw_group_state_.insert(std::make_pair(component_info.group, return_type::OK));
hardware_used_by_controllers_.insert(
std::make_pair(component_info.name, std::vector<std::string>()));
is_loaded = true;
Expand Down Expand Up @@ -298,6 +300,10 @@ class ResourceStorage
async_component_threads_.at(hardware.get_name()).register_component(&hardware);
}
}
if (!hardware.get_group_name().empty())
{
hw_group_state_[hardware.get_group_name()] = return_type::OK;
}
return result;
}

Expand Down Expand Up @@ -380,6 +386,10 @@ class ResourceStorage
{
remove_all_hardware_interfaces_from_available_list(hardware.get_name());
}
if (!hardware.get_group_name().empty())
{
hw_group_state_[hardware.get_group_name()] = return_type::OK;
}
return result;
}

Expand Down Expand Up @@ -414,6 +424,10 @@ class ResourceStorage
// deimport_non_movement_command_interfaces(hardware);
// deimport_state_interfaces(hardware);
// use remove_command_interfaces(hardware);
if (!hardware.get_group_name().empty())
{
hw_group_state_[hardware.get_group_name()] = return_type::OK;
}
}
return result;
}
Expand Down Expand Up @@ -884,6 +898,27 @@ class ResourceStorage
}
}

/**
* Returns the return type of the hardware component group state, if the return type is other
* than OK, then updates the return type of the group to the respective one
*/
return_type update_hardware_component_group_state(
const std::string & group_name, const return_type & value)
{
// This is for the components that has no configured group
if (group_name.empty())
{
return value;
}
// If it is anything other than OK, change the return type of the hardware group state
// to the respective return type
if (value > hw_group_state_.at(group_name))
saikishor marked this conversation as resolved.
Show resolved Hide resolved
{
hw_group_state_.at(group_name) = value;
}
return hw_group_state_.at(group_name);
}

// hardware plugins
pluginlib::ClassLoader<ActuatorInterface> actuator_loader_;
pluginlib::ClassLoader<SensorInterface> sensor_loader_;
Expand All @@ -898,6 +933,7 @@ class ResourceStorage
std::vector<System> async_systems_;

std::unordered_map<std::string, HardwareComponentInfo> hardware_info_map_;
std::unordered_map<std::string, hardware_interface::return_type> hw_group_state_;

/// Mapping between hardware and controllers that are using it (accessing data from it)
std::unordered_map<std::string, std::vector<std::string>> hardware_used_by_controllers_;
Expand Down Expand Up @@ -1575,6 +1611,9 @@ HardwareReadWriteStatus ResourceManager::read(
try
{
ret_val = component.read(time, period);
const auto component_group = component.get_group_name();
ret_val =
resource_storage_->update_hardware_component_group_state(component_group, ret_val);
}
catch (const std::exception & e)
{
Expand Down Expand Up @@ -1633,6 +1672,9 @@ HardwareReadWriteStatus ResourceManager::write(
try
{
ret_val = component.write(time, period);
const auto component_group = component.get_group_name();
ret_val =
resource_storage_->update_hardware_component_group_state(component_group, ret_val);
}
catch (const std::exception & e)
{
Expand Down
2 changes: 2 additions & 0 deletions hardware_interface/src/sensor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ std::vector<StateInterface> Sensor::export_state_interfaces()

std::string Sensor::get_name() const { return impl_->get_name(); }

std::string Sensor::get_group_name() const { return impl_->get_group_name(); }

const rclcpp_lifecycle::State & Sensor::get_state() const { return impl_->get_state(); }

return_type Sensor::read(const rclcpp::Time & time, const rclcpp::Duration & period)
Expand Down
2 changes: 2 additions & 0 deletions hardware_interface/src/system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ return_type System::perform_command_mode_switch(

std::string System::get_name() const { return impl_->get_name(); }

std::string System::get_group_name() const { return impl_->get_group_name(); }

const rclcpp_lifecycle::State & System::get_state() const { return impl_->get_state(); }

return_type System::read(const rclcpp::Time & time, const rclcpp::Duration & period)
Expand Down
Loading
Loading