From d07d18f4be619a5d120bfd5fd02ef14e8c90d73a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Fr=C3=B6hlich?= Date: Tue, 14 May 2024 07:48:11 +0200 Subject: [PATCH 1/9] [CI] Update branch of reusable workflow (#1537) * Update branch of reusable workflow * Include workflow file to paths filter --- .github/workflows/rolling-check-docs.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rolling-check-docs.yml b/.github/workflows/rolling-check-docs.yml index 80e8287abd..bd83c0caca 100644 --- a/.github/workflows/rolling-check-docs.yml +++ b/.github/workflows/rolling-check-docs.yml @@ -9,10 +9,11 @@ on: - '**.rst' - '**.md' - '**.yaml' + - '.github/workflows/rolling-check-docs.yml' jobs: check-docs: name: Check Docs - uses: ros-controls/control.ros.org/.github/workflows/reusable-sphinx-check-single-version.yml@master + uses: ros-controls/control.ros.org/.github/workflows/reusable-sphinx-check-single-version.yml@rolling with: ROS2_CONTROL_PR: ${{ github.ref }} From d8e276f731dadc1b5c00a97f829526f4012779b7 Mon Sep 17 00:00:00 2001 From: Bence Magyar Date: Tue, 14 May 2024 06:49:02 +0100 Subject: [PATCH 2/9] Update changelogs --- controller_interface/CHANGELOG.rst | 6 ++++++ controller_manager/CHANGELOG.rst | 5 +++++ controller_manager_msgs/CHANGELOG.rst | 3 +++ hardware_interface/CHANGELOG.rst | 6 ++++++ hardware_interface_testing/CHANGELOG.rst | 3 +++ joint_limits/CHANGELOG.rst | 6 ++++++ ros2_control/CHANGELOG.rst | 3 +++ ros2_control_test_assets/CHANGELOG.rst | 5 +++++ ros2controlcli/CHANGELOG.rst | 3 +++ rqt_controller_manager/CHANGELOG.rst | 3 +++ transmission_interface/CHANGELOG.rst | 5 +++++ 11 files changed, 48 insertions(+) diff --git a/controller_interface/CHANGELOG.rst b/controller_interface/CHANGELOG.rst index b8301e0cba..c9e8da8d60 100644 --- a/controller_interface/CHANGELOG.rst +++ b/controller_interface/CHANGELOG.rst @@ -2,6 +2,12 @@ Changelog for package controller_interface ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- +* Fix dependencies for source build (`#1533 `_) +* Add find_package for ament_cmake_gen_version_h (`#1534 `_) +* Contributors: Christoph Fröhlich + 4.10.0 (2024-05-08) ------------------- * Working async controllers and components [not synchronized] (`#1041 `_) diff --git a/controller_manager/CHANGELOG.rst b/controller_manager/CHANGELOG.rst index 4c5e9579ef..d7a2b0a589 100644 --- a/controller_manager/CHANGELOG.rst +++ b/controller_manager/CHANGELOG.rst @@ -2,6 +2,11 @@ Changelog for package controller_manager ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- +* Add find_package for ament_cmake_gen_version_h (`#1534 `_) +* Contributors: Christoph Fröhlich + 4.10.0 (2024-05-08) ------------------- * allow extra spawner arguments to not declare every argument in launch utils (`#1505 `_) diff --git a/controller_manager_msgs/CHANGELOG.rst b/controller_manager_msgs/CHANGELOG.rst index cf752dde2a..df41e0d67e 100644 --- a/controller_manager_msgs/CHANGELOG.rst +++ b/controller_manager_msgs/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package controller_manager_msgs ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- + 4.10.0 (2024-05-08) ------------------- diff --git a/hardware_interface/CHANGELOG.rst b/hardware_interface/CHANGELOG.rst index a27d097d6e..597a9713bb 100644 --- a/hardware_interface/CHANGELOG.rst +++ b/hardware_interface/CHANGELOG.rst @@ -2,6 +2,12 @@ Changelog for package hardware_interface ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- +* Add find_package for ament_cmake_gen_version_h (`#1534 `_) +* Parse URDF soft_limits into the HardwareInfo structure (`#1488 `_) +* Contributors: Christoph Fröhlich, adriaroig + 4.10.0 (2024-05-08) ------------------- * Add hardware components exception handling in resource manager (`#1508 `_) diff --git a/hardware_interface_testing/CHANGELOG.rst b/hardware_interface_testing/CHANGELOG.rst index cecc4e220c..1aa2f19b81 100644 --- a/hardware_interface_testing/CHANGELOG.rst +++ b/hardware_interface_testing/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package hardware_interface_testing ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- + 4.10.0 (2024-05-08) ------------------- diff --git a/joint_limits/CHANGELOG.rst b/joint_limits/CHANGELOG.rst index dba0758112..e74d66ea22 100644 --- a/joint_limits/CHANGELOG.rst +++ b/joint_limits/CHANGELOG.rst @@ -2,6 +2,12 @@ Changelog for package joint_limits ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- +* Fix dependencies for source build (`#1533 `_) +* Add find_package for ament_cmake_gen_version_h (`#1534 `_) +* Contributors: Christoph Fröhlich + 4.10.0 (2024-05-08) ------------------- diff --git a/ros2_control/CHANGELOG.rst b/ros2_control/CHANGELOG.rst index a6df0c09e3..2f80568e7a 100644 --- a/ros2_control/CHANGELOG.rst +++ b/ros2_control/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package ros2_control ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- + 4.10.0 (2024-05-08) ------------------- diff --git a/ros2_control_test_assets/CHANGELOG.rst b/ros2_control_test_assets/CHANGELOG.rst index 4057ceaa9e..fccd0d84e0 100644 --- a/ros2_control_test_assets/CHANGELOG.rst +++ b/ros2_control_test_assets/CHANGELOG.rst @@ -2,6 +2,11 @@ Changelog for package ros2_control_test_assets ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- +* Parse URDF soft_limits into the HardwareInfo structure (`#1488 `_) +* Contributors: adriaroig + 4.10.0 (2024-05-08) ------------------- * Parse URDF joint hard limits into the HardwareInfo structure (`#1472 `_) diff --git a/ros2controlcli/CHANGELOG.rst b/ros2controlcli/CHANGELOG.rst index 84a64c1fd8..3c567083ee 100644 --- a/ros2controlcli/CHANGELOG.rst +++ b/ros2controlcli/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package ros2controlcli ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- + 4.10.0 (2024-05-08) ------------------- diff --git a/rqt_controller_manager/CHANGELOG.rst b/rqt_controller_manager/CHANGELOG.rst index 33660cc16d..24a443b9cc 100644 --- a/rqt_controller_manager/CHANGELOG.rst +++ b/rqt_controller_manager/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package rqt_controller_manager ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- + 4.10.0 (2024-05-08) ------------------- diff --git a/transmission_interface/CHANGELOG.rst b/transmission_interface/CHANGELOG.rst index 8ac4224337..e1808bd2b8 100644 --- a/transmission_interface/CHANGELOG.rst +++ b/transmission_interface/CHANGELOG.rst @@ -2,6 +2,11 @@ Changelog for package transmission_interface ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- +* Add find_package for ament_cmake_gen_version_h (`#1534 `_) +* Contributors: Christoph Fröhlich + 4.10.0 (2024-05-08) ------------------- From 62b32afef6df68f05430f0e89b385473114dd786 Mon Sep 17 00:00:00 2001 From: Bence Magyar Date: Tue, 14 May 2024 06:49:25 +0100 Subject: [PATCH 3/9] 4.11.0 --- controller_interface/CHANGELOG.rst | 4 ++-- controller_interface/package.xml | 2 +- controller_manager/CHANGELOG.rst | 4 ++-- controller_manager/package.xml | 2 +- controller_manager_msgs/CHANGELOG.rst | 4 ++-- controller_manager_msgs/package.xml | 2 +- hardware_interface/CHANGELOG.rst | 4 ++-- hardware_interface/package.xml | 2 +- hardware_interface_testing/CHANGELOG.rst | 4 ++-- hardware_interface_testing/package.xml | 2 +- joint_limits/CHANGELOG.rst | 4 ++-- joint_limits/package.xml | 2 +- ros2_control/CHANGELOG.rst | 4 ++-- ros2_control/package.xml | 2 +- ros2_control_test_assets/CHANGELOG.rst | 4 ++-- ros2_control_test_assets/package.xml | 2 +- ros2controlcli/CHANGELOG.rst | 4 ++-- ros2controlcli/package.xml | 2 +- ros2controlcli/setup.py | 2 +- rqt_controller_manager/CHANGELOG.rst | 4 ++-- rqt_controller_manager/package.xml | 2 +- rqt_controller_manager/setup.py | 2 +- transmission_interface/CHANGELOG.rst | 4 ++-- transmission_interface/package.xml | 2 +- 24 files changed, 35 insertions(+), 35 deletions(-) diff --git a/controller_interface/CHANGELOG.rst b/controller_interface/CHANGELOG.rst index c9e8da8d60..8d77f4203f 100644 --- a/controller_interface/CHANGELOG.rst +++ b/controller_interface/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package controller_interface ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +4.11.0 (2024-05-14) +------------------- * Fix dependencies for source build (`#1533 `_) * Add find_package for ament_cmake_gen_version_h (`#1534 `_) * Contributors: Christoph Fröhlich diff --git a/controller_interface/package.xml b/controller_interface/package.xml index a4b89265e3..9a99449043 100644 --- a/controller_interface/package.xml +++ b/controller_interface/package.xml @@ -2,7 +2,7 @@ controller_interface - 4.10.0 + 4.11.0 Description of controller_interface Bence Magyar Denis Štogl diff --git a/controller_manager/CHANGELOG.rst b/controller_manager/CHANGELOG.rst index d7a2b0a589..2248f5f668 100644 --- a/controller_manager/CHANGELOG.rst +++ b/controller_manager/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package controller_manager ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +4.11.0 (2024-05-14) +------------------- * Add find_package for ament_cmake_gen_version_h (`#1534 `_) * Contributors: Christoph Fröhlich diff --git a/controller_manager/package.xml b/controller_manager/package.xml index ec5b0e4f0e..e2aa998332 100644 --- a/controller_manager/package.xml +++ b/controller_manager/package.xml @@ -2,7 +2,7 @@ controller_manager - 4.10.0 + 4.11.0 Description of controller_manager Bence Magyar Denis Štogl diff --git a/controller_manager_msgs/CHANGELOG.rst b/controller_manager_msgs/CHANGELOG.rst index df41e0d67e..6cc2a09a43 100644 --- a/controller_manager_msgs/CHANGELOG.rst +++ b/controller_manager_msgs/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package controller_manager_msgs ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +4.11.0 (2024-05-14) +------------------- 4.10.0 (2024-05-08) ------------------- diff --git a/controller_manager_msgs/package.xml b/controller_manager_msgs/package.xml index 1f078be2cf..ddd840154e 100644 --- a/controller_manager_msgs/package.xml +++ b/controller_manager_msgs/package.xml @@ -2,7 +2,7 @@ controller_manager_msgs - 4.10.0 + 4.11.0 Messages and services for the controller manager. Bence Magyar Denis Štogl diff --git a/hardware_interface/CHANGELOG.rst b/hardware_interface/CHANGELOG.rst index 597a9713bb..77185ecf28 100644 --- a/hardware_interface/CHANGELOG.rst +++ b/hardware_interface/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package hardware_interface ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +4.11.0 (2024-05-14) +------------------- * Add find_package for ament_cmake_gen_version_h (`#1534 `_) * Parse URDF soft_limits into the HardwareInfo structure (`#1488 `_) * Contributors: Christoph Fröhlich, adriaroig diff --git a/hardware_interface/package.xml b/hardware_interface/package.xml index 33dea58c73..f3aaad3306 100644 --- a/hardware_interface/package.xml +++ b/hardware_interface/package.xml @@ -1,7 +1,7 @@ hardware_interface - 4.10.0 + 4.11.0 ros2_control hardware interface Bence Magyar Denis Štogl diff --git a/hardware_interface_testing/CHANGELOG.rst b/hardware_interface_testing/CHANGELOG.rst index 1aa2f19b81..66d8794675 100644 --- a/hardware_interface_testing/CHANGELOG.rst +++ b/hardware_interface_testing/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package hardware_interface_testing ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +4.11.0 (2024-05-14) +------------------- 4.10.0 (2024-05-08) ------------------- diff --git a/hardware_interface_testing/package.xml b/hardware_interface_testing/package.xml index 737d228df3..b63e76ec6d 100644 --- a/hardware_interface_testing/package.xml +++ b/hardware_interface_testing/package.xml @@ -1,7 +1,7 @@ hardware_interface_testing - 4.10.0 + 4.11.0 ros2_control hardware interface testing Bence Magyar Denis Štogl diff --git a/joint_limits/CHANGELOG.rst b/joint_limits/CHANGELOG.rst index e74d66ea22..12cfce3bd6 100644 --- a/joint_limits/CHANGELOG.rst +++ b/joint_limits/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package joint_limits ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +4.11.0 (2024-05-14) +------------------- * Fix dependencies for source build (`#1533 `_) * Add find_package for ament_cmake_gen_version_h (`#1534 `_) * Contributors: Christoph Fröhlich diff --git a/joint_limits/package.xml b/joint_limits/package.xml index 1aa1ac5705..aec1c44b76 100644 --- a/joint_limits/package.xml +++ b/joint_limits/package.xml @@ -1,6 +1,6 @@ joint_limits - 4.10.0 + 4.11.0 Interfaces for handling of joint limits for controllers or hardware. Bence Magyar diff --git a/ros2_control/CHANGELOG.rst b/ros2_control/CHANGELOG.rst index 2f80568e7a..c036c1c914 100644 --- a/ros2_control/CHANGELOG.rst +++ b/ros2_control/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package ros2_control ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +4.11.0 (2024-05-14) +------------------- 4.10.0 (2024-05-08) ------------------- diff --git a/ros2_control/package.xml b/ros2_control/package.xml index 1fa309643f..3941d9a17e 100644 --- a/ros2_control/package.xml +++ b/ros2_control/package.xml @@ -1,7 +1,7 @@ ros2_control - 4.10.0 + 4.11.0 Metapackage for ROS2 control related packages Bence Magyar Denis Štogl diff --git a/ros2_control_test_assets/CHANGELOG.rst b/ros2_control_test_assets/CHANGELOG.rst index fccd0d84e0..1b475c5c40 100644 --- a/ros2_control_test_assets/CHANGELOG.rst +++ b/ros2_control_test_assets/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package ros2_control_test_assets ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +4.11.0 (2024-05-14) +------------------- * Parse URDF soft_limits into the HardwareInfo structure (`#1488 `_) * Contributors: adriaroig diff --git a/ros2_control_test_assets/package.xml b/ros2_control_test_assets/package.xml index a76e1109a8..4ed6912f83 100644 --- a/ros2_control_test_assets/package.xml +++ b/ros2_control_test_assets/package.xml @@ -2,7 +2,7 @@ ros2_control_test_assets - 4.10.0 + 4.11.0 The package provides shared test resources for ros2_control stack Bence Magyar diff --git a/ros2controlcli/CHANGELOG.rst b/ros2controlcli/CHANGELOG.rst index 3c567083ee..52ad767224 100644 --- a/ros2controlcli/CHANGELOG.rst +++ b/ros2controlcli/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package ros2controlcli ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +4.11.0 (2024-05-14) +------------------- 4.10.0 (2024-05-08) ------------------- diff --git a/ros2controlcli/package.xml b/ros2controlcli/package.xml index 8e2b02b266..d37c053413 100644 --- a/ros2controlcli/package.xml +++ b/ros2controlcli/package.xml @@ -2,7 +2,7 @@ ros2controlcli - 4.10.0 + 4.11.0 The ROS 2 command line tools for ROS2 Control. diff --git a/ros2controlcli/setup.py b/ros2controlcli/setup.py index d3ce54204e..a0ee818309 100644 --- a/ros2controlcli/setup.py +++ b/ros2controlcli/setup.py @@ -19,7 +19,7 @@ setup( name=package_name, - version="4.10.0", + version="4.11.0", packages=find_packages(exclude=["test"]), data_files=[ ("share/" + package_name, ["package.xml"]), diff --git a/rqt_controller_manager/CHANGELOG.rst b/rqt_controller_manager/CHANGELOG.rst index 24a443b9cc..a82b6e708f 100644 --- a/rqt_controller_manager/CHANGELOG.rst +++ b/rqt_controller_manager/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package rqt_controller_manager ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +4.11.0 (2024-05-14) +------------------- 4.10.0 (2024-05-08) ------------------- diff --git a/rqt_controller_manager/package.xml b/rqt_controller_manager/package.xml index c722043890..e2866496ab 100644 --- a/rqt_controller_manager/package.xml +++ b/rqt_controller_manager/package.xml @@ -2,7 +2,7 @@ rqt_controller_manager - 4.10.0 + 4.11.0 Graphical frontend for interacting with the controller manager. Bence Magyar Denis Štogl diff --git a/rqt_controller_manager/setup.py b/rqt_controller_manager/setup.py index ae79268335..1f23378eba 100644 --- a/rqt_controller_manager/setup.py +++ b/rqt_controller_manager/setup.py @@ -20,7 +20,7 @@ setup( name=package_name, - version="4.10.0", + version="4.11.0", packages=[package_name], data_files=[ ("share/ament_index/resource_index/packages", ["resource/" + package_name]), diff --git a/transmission_interface/CHANGELOG.rst b/transmission_interface/CHANGELOG.rst index e1808bd2b8..44c85099dd 100644 --- a/transmission_interface/CHANGELOG.rst +++ b/transmission_interface/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package transmission_interface ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +4.11.0 (2024-05-14) +------------------- * Add find_package for ament_cmake_gen_version_h (`#1534 `_) * Contributors: Christoph Fröhlich diff --git a/transmission_interface/package.xml b/transmission_interface/package.xml index 83da85a2ef..15c834b838 100644 --- a/transmission_interface/package.xml +++ b/transmission_interface/package.xml @@ -2,7 +2,7 @@ transmission_interface - 4.10.0 + 4.11.0 transmission_interface contains data structures for representing mechanical transmissions, methods for propagating values between actuator and joint spaces and tooling to support this. Bence Magyar Denis Štogl From 6ae39507231ea111974c6a3362dc1e652524e857 Mon Sep 17 00:00:00 2001 From: Sai Kishor Kothakota Date: Wed, 22 May 2024 19:38:18 +0200 Subject: [PATCH 4/9] [Feature] Hardware Components Grouping (#1458) --- .../doc/hardware_interface_types_userdoc.rst | 69 +++++ .../include/hardware_interface/actuator.hpp | 3 + .../hardware_interface/actuator_interface.hpp | 6 + .../hardware_component_info.hpp | 3 + .../hardware_interface/hardware_info.hpp | 2 + .../include/hardware_interface/sensor.hpp | 3 + .../hardware_interface/sensor_interface.hpp | 6 + .../include/hardware_interface/system.hpp | 3 + .../hardware_interface/system_interface.hpp | 6 + hardware_interface/src/actuator.cpp | 2 + hardware_interface/src/component_parser.cpp | 6 + .../src/mock_components/generic_system.cpp | 18 +- hardware_interface/src/resource_manager.cpp | 42 +++ hardware_interface/src/sensor.cpp | 2 + hardware_interface/src/system.cpp | 2 + .../mock_components/test_generic_system.cpp | 244 +++++++++++++++++- .../test/test_component_parser.cpp | 12 + .../components_urdfs.hpp | 6 + 18 files changed, 426 insertions(+), 9 deletions(-) diff --git a/hardware_interface/doc/hardware_interface_types_userdoc.rst b/hardware_interface/doc/hardware_interface_types_userdoc.rst index ecf852cb94..d8338bf7a6 100644 --- a/hardware_interface/doc/hardware_interface_types_userdoc.rst +++ b/hardware_interface/doc/hardware_interface_types_userdoc.rst @@ -41,6 +41,12 @@ The ```` tag can be used as a child of all three types of hardware compone Because ports implemented as ````-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 ` 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 ```` tag within each ```` block signifies the grouping of hardware components, enabling error propagation mechanisms within the system. + Examples ***************************** The following examples show how to use the different hardware interface types in a ``ros2_control`` URDF. @@ -152,3 +158,66 @@ They can be combined together within the different hardware component types (sys + +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_demo_hardware/RRBotSystemPositionOnlyHardware + Group1 + 2.0 + 3.0 + 2.0 + + + + -1 + 1 + + + + + + + 3.1 + + + + + + + + + + + + ros2_control_demo_hardware/RRBotSystemPositionOnlyHardware + Group1 + 2.0 + 3.0 + 2.0 + + + + -1 + 1 + + + + + + + + + + + + diff --git a/hardware_interface/include/hardware_interface/actuator.hpp b/hardware_interface/include/hardware_interface/actuator.hpp index 4082863370..b23b913d75 100644 --- a/hardware_interface/include/hardware_interface/actuator.hpp +++ b/hardware_interface/include/hardware_interface/actuator.hpp @@ -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; diff --git a/hardware_interface/include/hardware_interface/actuator_interface.hpp b/hardware_interface/include/hardware_interface/actuator_interface.hpp index abfd8eb45a..556d7a2047 100644 --- a/hardware_interface/include/hardware_interface/actuator_interface.hpp +++ b/hardware_interface/include/hardware_interface/actuator_interface.hpp @@ -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. diff --git a/hardware_interface/include/hardware_interface/hardware_component_info.hpp b/hardware_interface/include/hardware_interface/hardware_component_info.hpp index 45afebdb34..e7d47bcaa4 100644 --- a/hardware_interface/include/hardware_interface/hardware_component_info.hpp +++ b/hardware_interface/include/hardware_interface/hardware_component_info.hpp @@ -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; diff --git a/hardware_interface/include/hardware_interface/hardware_info.hpp b/hardware_interface/include/hardware_interface/hardware_info.hpp index d94573bf5e..2bd2099e69 100644 --- a/hardware_interface/include/hardware_interface/hardware_info.hpp +++ b/hardware_interface/include/hardware_interface/hardware_info.hpp @@ -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. diff --git a/hardware_interface/include/hardware_interface/sensor.hpp b/hardware_interface/include/hardware_interface/sensor.hpp index 5d0677c587..4c267bef77 100644 --- a/hardware_interface/include/hardware_interface/sensor.hpp +++ b/hardware_interface/include/hardware_interface/sensor.hpp @@ -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; diff --git a/hardware_interface/include/hardware_interface/sensor_interface.hpp b/hardware_interface/include/hardware_interface/sensor_interface.hpp index 14a59e4588..5a3601afa8 100644 --- a/hardware_interface/include/hardware_interface/sensor_interface.hpp +++ b/hardware_interface/include/hardware_interface/sensor_interface.hpp @@ -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. diff --git a/hardware_interface/include/hardware_interface/system.hpp b/hardware_interface/include/hardware_interface/system.hpp index ece14f814d..fb28929948 100644 --- a/hardware_interface/include/hardware_interface/system.hpp +++ b/hardware_interface/include/hardware_interface/system.hpp @@ -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; diff --git a/hardware_interface/include/hardware_interface/system_interface.hpp b/hardware_interface/include/hardware_interface/system_interface.hpp index e5c6f2f542..0a7421531a 100644 --- a/hardware_interface/include/hardware_interface/system_interface.hpp +++ b/hardware_interface/include/hardware_interface/system_interface.hpp @@ -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. diff --git a/hardware_interface/src/actuator.cpp b/hardware_interface/src/actuator.cpp index 6b58e365dc..b80f76ebf5 100644 --- a/hardware_interface/src/actuator.cpp +++ b/hardware_interface/src/actuator.cpp @@ -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) diff --git a/hardware_interface/src/component_parser.cpp b/hardware_interface/src/component_parser.cpp index 14e016df21..ef585c971b 100644 --- a/hardware_interface/src/component_parser.cpp +++ b/hardware_interface/src/component_parser.cpp @@ -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"; @@ -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) { diff --git a/hardware_interface/src/mock_components/generic_system.cpp b/hardware_interface/src/mock_components/generic_system.cpp index 2d8a01a34f..162c3aa60d 100644 --- a/hardware_interface/src/mock_components/generic_system.cpp +++ b/hardware_interface/src/mock_components/generic_system.cpp @@ -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) { @@ -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) @@ -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) diff --git a/hardware_interface/src/resource_manager.cpp b/hardware_interface/src/resource_manager.cpp index 92b6d01519..d77f915eee 100644 --- a/hardware_interface/src/resource_manager.cpp +++ b/hardware_interface/src/resource_manager.cpp @@ -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())); is_loaded = true; @@ -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; } @@ -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; } @@ -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; } @@ -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 != return_type::OK) + { + hw_group_state_.at(group_name) = value; + } + return hw_group_state_.at(group_name); + } + // hardware plugins pluginlib::ClassLoader actuator_loader_; pluginlib::ClassLoader sensor_loader_; @@ -898,6 +933,7 @@ class ResourceStorage std::vector async_systems_; std::unordered_map hardware_info_map_; + std::unordered_map hw_group_state_; /// Mapping between hardware and controllers that are using it (accessing data from it) std::unordered_map> hardware_used_by_controllers_; @@ -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) { @@ -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) { diff --git a/hardware_interface/src/sensor.cpp b/hardware_interface/src/sensor.cpp index 2e53e447b9..2da627f892 100644 --- a/hardware_interface/src/sensor.cpp +++ b/hardware_interface/src/sensor.cpp @@ -191,6 +191,8 @@ std::vector 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) diff --git a/hardware_interface/src/system.cpp b/hardware_interface/src/system.cpp index ee942d6581..8e950faa89 100644 --- a/hardware_interface/src/system.cpp +++ b/hardware_interface/src/system.cpp @@ -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) diff --git a/hardware_interface/test/mock_components/test_generic_system.cpp b/hardware_interface/test/mock_components/test_generic_system.cpp index ac89dc1553..c7777b3e21 100644 --- a/hardware_interface/test/mock_components/test_generic_system.cpp +++ b/hardware_interface/test/mock_components/test_generic_system.cpp @@ -96,6 +96,7 @@ class TestGenericSystem : public ::testing::Test mock_components/GenericSystem + Hardware Group @@ -121,6 +122,7 @@ class TestGenericSystem : public ::testing::Test mock_components/GenericSystem + Hardware Group @@ -289,6 +291,7 @@ class TestGenericSystem : public ::testing::Test mock_components/GenericSystem + Hardware Group -3 actual_position @@ -351,6 +354,7 @@ class TestGenericSystem : public ::testing::Test mock_components/GenericSystem + Hardware Group 2 2 @@ -579,6 +583,70 @@ class TestGenericSystem : public ::testing::Test )"; + + hardware_system_2dof_standard_interfaces_with_same_hardware_group_ = + R"( + + + mock_components/GenericSystem + Hardware Group + + + + + + 3.45 + + + + + + + mock_components/GenericSystem + Hardware Group + + + + + + 2.78 + + + + +)"; + + hardware_system_2dof_standard_interfaces_with_two_diff_hw_groups_ = + R"( + + + mock_components/GenericSystem + Hardware Group 1 + + + + + + 3.45 + + + + + + + mock_components/GenericSystem + Hardware Group 2 + + + + + + 2.78 + + + + +)"; } std::string hardware_system_2dof_; @@ -600,6 +668,8 @@ class TestGenericSystem : public ::testing::Test std::string hardware_system_2dof_standard_interfaces_with_different_control_modes_; std::string valid_hardware_system_2dof_standard_interfaces_with_different_control_modes_; std::string disabled_commands_; + std::string hardware_system_2dof_standard_interfaces_with_same_hardware_group_; + std::string hardware_system_2dof_standard_interfaces_with_two_diff_hw_groups_; }; // Forward declaration @@ -814,7 +884,7 @@ void generic_system_functional_test( ASSERT_EQ(0.44, j2v_c.get_value()); // write() does not change values - rm.write(TIME, PERIOD); + ASSERT_TRUE(rm.write(TIME, PERIOD).ok); ASSERT_EQ(3.45, j1p_s.get_value()); ASSERT_EQ(0.0, j1v_s.get_value()); ASSERT_EQ(2.78, j2p_s.get_value()); @@ -825,7 +895,7 @@ void generic_system_functional_test( ASSERT_EQ(0.44, j2v_c.get_value()); // read() mirrors commands + offset to states - rm.read(TIME, PERIOD); + ASSERT_TRUE(rm.read(TIME, PERIOD).ok); ASSERT_EQ(0.11 + offset, j1p_s.get_value()); ASSERT_EQ(0.22, j1v_s.get_value()); ASSERT_EQ(0.33 + offset, j2p_s.get_value()); @@ -857,6 +927,158 @@ void generic_system_functional_test( status_map[component_name].state.label(), hardware_interface::lifecycle_state_names::INACTIVE); } +void generic_system_error_group_test( + const std::string & urdf, const std::string component_prefix, bool validate_same_group) +{ + TestableResourceManager rm(urdf); + const std::string component1 = component_prefix + "1"; + const std::string component2 = component_prefix + "2"; + // check is hardware is configured + auto status_map = rm.get_components_status(); + for (auto component : {component1, component2}) + { + EXPECT_EQ( + status_map[component].state.label(), hardware_interface::lifecycle_state_names::UNCONFIGURED); + configure_components(rm, {component}); + status_map = rm.get_components_status(); + EXPECT_EQ( + status_map[component].state.label(), hardware_interface::lifecycle_state_names::INACTIVE); + activate_components(rm, {component}); + status_map = rm.get_components_status(); + EXPECT_EQ( + status_map[component].state.label(), hardware_interface::lifecycle_state_names::ACTIVE); + } + + // Check initial values + hardware_interface::LoanedStateInterface j1p_s = rm.claim_state_interface("joint1/position"); + hardware_interface::LoanedStateInterface j1v_s = rm.claim_state_interface("joint1/velocity"); + hardware_interface::LoanedStateInterface j2p_s = rm.claim_state_interface("joint2/position"); + hardware_interface::LoanedStateInterface j2v_s = rm.claim_state_interface("joint2/velocity"); + hardware_interface::LoanedCommandInterface j1p_c = rm.claim_command_interface("joint1/position"); + hardware_interface::LoanedCommandInterface j1v_c = rm.claim_command_interface("joint1/velocity"); + hardware_interface::LoanedCommandInterface j2p_c = rm.claim_command_interface("joint2/position"); + hardware_interface::LoanedCommandInterface j2v_c = rm.claim_command_interface("joint2/velocity"); + + // State interfaces without initial value are set to 0 + ASSERT_EQ(3.45, j1p_s.get_value()); + ASSERT_EQ(0.0, j1v_s.get_value()); + ASSERT_EQ(2.78, j2p_s.get_value()); + ASSERT_EQ(0.0, j2v_s.get_value()); + ASSERT_TRUE(std::isnan(j1p_c.get_value())); + ASSERT_TRUE(std::isnan(j1v_c.get_value())); + ASSERT_TRUE(std::isnan(j2p_c.get_value())); + ASSERT_TRUE(std::isnan(j2v_c.get_value())); + + // set some new values in commands + j1p_c.set_value(0.11); + j1v_c.set_value(0.22); + j2p_c.set_value(0.33); + j2v_c.set_value(0.44); + + // State values should not be changed + ASSERT_EQ(3.45, j1p_s.get_value()); + ASSERT_EQ(0.0, j1v_s.get_value()); + ASSERT_EQ(2.78, j2p_s.get_value()); + ASSERT_EQ(0.0, j2v_s.get_value()); + ASSERT_EQ(0.11, j1p_c.get_value()); + ASSERT_EQ(0.22, j1v_c.get_value()); + ASSERT_EQ(0.33, j2p_c.get_value()); + ASSERT_EQ(0.44, j2v_c.get_value()); + + // write() does not change values + ASSERT_TRUE(rm.write(TIME, PERIOD).ok); + ASSERT_EQ(3.45, j1p_s.get_value()); + ASSERT_EQ(0.0, j1v_s.get_value()); + ASSERT_EQ(2.78, j2p_s.get_value()); + ASSERT_EQ(0.0, j2v_s.get_value()); + ASSERT_EQ(0.11, j1p_c.get_value()); + ASSERT_EQ(0.22, j1v_c.get_value()); + ASSERT_EQ(0.33, j2p_c.get_value()); + ASSERT_EQ(0.44, j2v_c.get_value()); + + // read() mirrors commands to states + ASSERT_TRUE(rm.read(TIME, PERIOD).ok); + ASSERT_EQ(0.11, j1p_s.get_value()); + ASSERT_EQ(0.22, j1v_s.get_value()); + ASSERT_EQ(0.33, j2p_s.get_value()); + ASSERT_EQ(0.44, j2v_s.get_value()); + ASSERT_EQ(0.11, j1p_c.get_value()); + ASSERT_EQ(0.22, j1v_c.get_value()); + ASSERT_EQ(0.33, j2p_c.get_value()); + ASSERT_EQ(0.44, j2v_c.get_value()); + + // set some new values in commands + j1p_c.set_value(0.55); + j1v_c.set_value(0.66); + j2p_c.set_value(0.77); + j2v_c.set_value(0.88); + + // state values should not be changed + ASSERT_EQ(0.11, j1p_s.get_value()); + ASSERT_EQ(0.22, j1v_s.get_value()); + ASSERT_EQ(0.33, j2p_s.get_value()); + ASSERT_EQ(0.44, j2v_s.get_value()); + ASSERT_EQ(0.55, j1p_c.get_value()); + ASSERT_EQ(0.66, j1v_c.get_value()); + ASSERT_EQ(0.77, j2p_c.get_value()); + ASSERT_EQ(0.88, j2v_c.get_value()); + + // Error testing + j1p_c.set_value(std::numeric_limits::infinity()); + j1v_c.set_value(std::numeric_limits::infinity()); + // read() should now bring error in the first component + auto read_result = rm.read(TIME, PERIOD); + ASSERT_FALSE(read_result.ok); + if (validate_same_group) + { + // If they belong to the same group, show the error in all hardware components of same group + EXPECT_THAT(read_result.failed_hardware_names, ::testing::ElementsAre(component1, component2)); + } + else + { + // If they don't belong to the same group, show the error in only that hardware component + EXPECT_THAT(read_result.failed_hardware_names, ::testing::ElementsAre(component1)); + } + + // Check initial values + ASSERT_FALSE(rm.state_interface_is_available("joint1/position")); + ASSERT_FALSE(rm.state_interface_is_available("joint1/velocity")); + ASSERT_FALSE(rm.command_interface_is_available("joint1/position")); + ASSERT_FALSE(rm.command_interface_is_available("joint1/velocity")); + + if (validate_same_group) + { + ASSERT_FALSE(rm.state_interface_is_available("joint2/position")); + ASSERT_FALSE(rm.state_interface_is_available("joint2/velocity")); + ASSERT_FALSE(rm.command_interface_is_available("joint2/position")); + ASSERT_FALSE(rm.command_interface_is_available("joint2/velocity")); + } + else + { + ASSERT_TRUE(rm.state_interface_is_available("joint2/position")); + ASSERT_TRUE(rm.state_interface_is_available("joint2/velocity")); + ASSERT_TRUE(rm.command_interface_is_available("joint2/position")); + ASSERT_TRUE(rm.command_interface_is_available("joint2/velocity")); + } + + // Error should be recoverable only after reactivating the hardware component + j1p_c.set_value(0.0); + j1v_c.set_value(0.0); + ASSERT_FALSE(rm.read(TIME, PERIOD).ok); + + // Now it should be recoverable + deactivate_components(rm, {component1}); + activate_components(rm, {component1}); + ASSERT_TRUE(rm.read(TIME, PERIOD).ok); + + deactivate_components(rm, {component1, component2}); + status_map = rm.get_components_status(); + EXPECT_EQ( + status_map[component1].state.label(), hardware_interface::lifecycle_state_names::INACTIVE); + EXPECT_EQ( + status_map[component2].state.label(), hardware_interface::lifecycle_state_names::INACTIVE); +} + TEST_F(TestGenericSystem, generic_system_2dof_functionality) { auto urdf = ros2_control_test_assets::urdf_head + hardware_system_2dof_standard_interfaces_ + @@ -865,6 +1087,24 @@ TEST_F(TestGenericSystem, generic_system_2dof_functionality) generic_system_functional_test(urdf, {"MockHardwareSystem"}); } +TEST_F(TestGenericSystem, generic_system_2dof_error_propagation_different_group) +{ + auto urdf = ros2_control_test_assets::urdf_head + + hardware_system_2dof_standard_interfaces_with_two_diff_hw_groups_ + + ros2_control_test_assets::urdf_tail; + + generic_system_error_group_test(urdf, {"MockHardwareSystem"}, false); +} + +TEST_F(TestGenericSystem, generic_system_2dof_error_propagation_same_group) +{ + auto urdf = ros2_control_test_assets::urdf_head + + hardware_system_2dof_standard_interfaces_with_same_hardware_group_ + + ros2_control_test_assets::urdf_tail; + + generic_system_error_group_test(urdf, {"MockHardwareSystem"}, true); +} + TEST_F(TestGenericSystem, generic_system_2dof_other_interfaces) { auto urdf = ros2_control_test_assets::urdf_head + hardware_system_2dof_with_other_interface_ + diff --git a/hardware_interface/test/test_component_parser.cpp b/hardware_interface/test/test_component_parser.cpp index 6a0c11cf72..2e2cae9807 100644 --- a/hardware_interface/test/test_component_parser.cpp +++ b/hardware_interface/test/test_component_parser.cpp @@ -113,6 +113,7 @@ TEST_F(TestComponentParser, successfully_parse_valid_urdf_system_one_interface) EXPECT_EQ(hardware_info.name, "RRBotSystemPositionOnly"); EXPECT_EQ(hardware_info.type, "system"); + ASSERT_THAT(hardware_info.group, IsEmpty()); EXPECT_EQ( hardware_info.hardware_plugin_name, "ros2_control_demo_hardware/RRBotSystemPositionOnlyHardware"); @@ -176,6 +177,7 @@ TEST_F(TestComponentParser, successfully_parse_valid_urdf_system_multi_interface EXPECT_EQ(hardware_info.name, "RRBotSystemMultiInterface"); EXPECT_EQ(hardware_info.type, "system"); + ASSERT_THAT(hardware_info.group, IsEmpty()); EXPECT_EQ( hardware_info.hardware_plugin_name, "ros2_control_demo_hardware/RRBotSystemMultiInterfaceHardware"); @@ -238,6 +240,7 @@ TEST_F(TestComponentParser, successfully_parse_valid_urdf_system_robot_with_sens EXPECT_EQ(hardware_info.name, "RRBotSystemWithSensor"); EXPECT_EQ(hardware_info.type, "system"); + ASSERT_THAT(hardware_info.group, IsEmpty()); EXPECT_EQ( hardware_info.hardware_plugin_name, "ros2_control_demo_hardware/RRBotSystemWithSensorHardware"); ASSERT_THAT(hardware_info.hardware_parameters, SizeIs(2)); @@ -306,6 +309,7 @@ TEST_F(TestComponentParser, successfully_parse_valid_urdf_system_robot_with_exte EXPECT_EQ(hardware_info.name, "RRBotSystemPositionOnlyWithExternalSensor"); EXPECT_EQ(hardware_info.type, "system"); + ASSERT_THAT(hardware_info.group, IsEmpty()); EXPECT_EQ( hardware_info.hardware_plugin_name, "ros2_control_demo_hardware/RRBotSystemPositionOnlyHardware"); @@ -372,6 +376,7 @@ TEST_F(TestComponentParser, successfully_parse_valid_urdf_actuator_modular_robot auto hardware_info = control_hardware.at(0); EXPECT_EQ(hardware_info.name, "RRBotModularJoint1"); + EXPECT_EQ(hardware_info.group, "Hardware Group"); EXPECT_EQ(hardware_info.type, "actuator"); EXPECT_EQ( hardware_info.hardware_plugin_name, "ros2_control_demo_hardware/PositionActuatorHardware"); @@ -400,6 +405,7 @@ TEST_F(TestComponentParser, successfully_parse_valid_urdf_actuator_modular_robot hardware_info = control_hardware.at(1); EXPECT_EQ(hardware_info.name, "RRBotModularJoint2"); + EXPECT_EQ(hardware_info.group, "Hardware Group"); EXPECT_EQ(hardware_info.type, "actuator"); EXPECT_EQ( hardware_info.hardware_plugin_name, "ros2_control_demo_hardware/PositionActuatorHardware"); @@ -445,6 +451,7 @@ TEST_F(TestComponentParser, successfully_parse_valid_urdf_actuator_modular_robot auto hardware_info = control_hardware.at(0); EXPECT_EQ(hardware_info.name, "RRBotModularJoint1"); + EXPECT_EQ(hardware_info.group, "Hardware Group 1"); EXPECT_EQ(hardware_info.type, "actuator"); EXPECT_EQ( hardware_info.hardware_plugin_name, "ros2_control_demo_hardware/VelocityActuatorHardware"); @@ -484,6 +491,7 @@ TEST_F(TestComponentParser, successfully_parse_valid_urdf_actuator_modular_robot hardware_info = control_hardware.at(1); EXPECT_EQ(hardware_info.name, "RRBotModularJoint2"); + EXPECT_EQ(hardware_info.group, "Hardware Group 2"); EXPECT_EQ(hardware_info.type, "actuator"); EXPECT_EQ( hardware_info.hardware_plugin_name, "ros2_control_demo_hardware/VelocityActuatorHardware"); @@ -523,6 +531,7 @@ TEST_F(TestComponentParser, successfully_parse_valid_urdf_actuator_modular_robot hardware_info = control_hardware.at(2); EXPECT_EQ(hardware_info.name, "RRBotModularPositionSensorJoint1"); + EXPECT_EQ(hardware_info.group, "Hardware Group 1"); EXPECT_EQ(hardware_info.type, "sensor"); EXPECT_EQ( hardware_info.hardware_plugin_name, "ros2_control_demo_hardware/PositionSensorHardware"); @@ -554,6 +563,7 @@ TEST_F(TestComponentParser, successfully_parse_valid_urdf_actuator_modular_robot hardware_info = control_hardware.at(3); EXPECT_EQ(hardware_info.name, "RRBotModularPositionSensorJoint2"); + EXPECT_EQ(hardware_info.group, "Hardware Group 2"); EXPECT_EQ(hardware_info.type, "sensor"); EXPECT_EQ( hardware_info.hardware_plugin_name, "ros2_control_demo_hardware/PositionSensorHardware"); @@ -602,6 +612,7 @@ TEST_F(TestComponentParser, successfully_parse_valid_urdf_system_multi_joints_tr EXPECT_EQ(hardware_info.name, "RRBotModularWrist"); EXPECT_EQ(hardware_info.type, "system"); + ASSERT_THAT(hardware_info.group, IsEmpty()); EXPECT_EQ( hardware_info.hardware_plugin_name, "ros2_control_demo_hardware/ActuatorHardwareMultiDOF"); ASSERT_THAT(hardware_info.hardware_parameters, SizeIs(2)); @@ -644,6 +655,7 @@ TEST_F(TestComponentParser, successfully_parse_valid_urdf_sensor_only) EXPECT_EQ(hardware_info.name, "CameraWithIMU"); EXPECT_EQ(hardware_info.type, "sensor"); + ASSERT_THAT(hardware_info.group, IsEmpty()); EXPECT_EQ(hardware_info.hardware_plugin_name, "ros2_control_demo_hardware/CameraWithIMUSensor"); ASSERT_THAT(hardware_info.hardware_parameters, SizeIs(1)); EXPECT_EQ(hardware_info.hardware_parameters.at("example_param_read_for_sec"), "2"); diff --git a/ros2_control_test_assets/include/ros2_control_test_assets/components_urdfs.hpp b/ros2_control_test_assets/include/ros2_control_test_assets/components_urdfs.hpp index 7b46eda9c0..eba16c1e71 100644 --- a/ros2_control_test_assets/include/ros2_control_test_assets/components_urdfs.hpp +++ b/ros2_control_test_assets/include/ros2_control_test_assets/components_urdfs.hpp @@ -172,6 +172,7 @@ const auto valid_urdf_ros2_control_actuator_modular_robot = ros2_control_demo_hardware/PositionActuatorHardware + Hardware Group 1.23 3 @@ -186,6 +187,7 @@ const auto valid_urdf_ros2_control_actuator_modular_robot = ros2_control_demo_hardware/PositionActuatorHardware + Hardware Group 1.23 3 @@ -206,6 +208,7 @@ const auto valid_urdf_ros2_control_actuator_modular_robot_sensors = ros2_control_demo_hardware/VelocityActuatorHardware + Hardware Group 1 1.23 3 @@ -226,6 +229,7 @@ const auto valid_urdf_ros2_control_actuator_modular_robot_sensors = ros2_control_demo_hardware/VelocityActuatorHardware + Hardware Group 2 1.23 3 @@ -240,6 +244,7 @@ const auto valid_urdf_ros2_control_actuator_modular_robot_sensors = ros2_control_demo_hardware/PositionSensorHardware + Hardware Group 1 2 @@ -249,6 +254,7 @@ const auto valid_urdf_ros2_control_actuator_modular_robot_sensors = ros2_control_demo_hardware/PositionSensorHardware + Hardware Group 2 2 From 9f1007b0bffc13f7eb2e755369dd24302d2db7d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Fr=C3=B6hlich?= Date: Thu, 23 May 2024 22:22:24 +0200 Subject: [PATCH 5/9] [CI] Test also changes to py files (i.e., spawner) (#1548) --- .github/workflows/humble-abi-compatibility.yml | 1 + .github/workflows/humble-binary-build.yml | 2 ++ .github/workflows/humble-coverage-build.yml | 2 ++ .github/workflows/humble-debian-build.yml | 1 + .github/workflows/humble-rhel-binary-build.yml | 1 + .github/workflows/humble-semi-binary-build.yml | 2 ++ .github/workflows/humble-source-build.yml | 1 + .github/workflows/iron-abi-compatibility.yml | 1 + .github/workflows/iron-binary-build.yml | 2 ++ .github/workflows/iron-coverage-build.yml | 2 ++ .github/workflows/iron-debian-build.yml | 1 + .github/workflows/iron-rhel-binary-build.yml | 1 + .github/workflows/iron-semi-binary-build.yml | 2 ++ .github/workflows/iron-source-build.yml | 1 + .github/workflows/jazzy-abi-compatibility.yml | 1 + .github/workflows/jazzy-binary-build.yml | 2 ++ .github/workflows/jazzy-debian-build.yml | 1 + .github/workflows/jazzy-rhel-binary-build.yml | 1 + .github/workflows/jazzy-semi-binary-build.yml | 2 ++ .github/workflows/jazzy-source-build.yml | 1 + .github/workflows/rolling-abi-compatibility.yml | 1 + .github/workflows/rolling-binary-build.yml | 2 ++ .github/workflows/rolling-coverage-build.yml | 2 ++ .github/workflows/rolling-debian-build.yml | 1 + .github/workflows/rolling-rhel-binary-build.yml | 1 + .github/workflows/rolling-semi-binary-build.yml | 2 ++ .github/workflows/rolling-source-build.yml | 1 + 27 files changed, 38 insertions(+) diff --git a/.github/workflows/humble-abi-compatibility.yml b/.github/workflows/humble-abi-compatibility.yml index 8b1950babc..0e2488d31e 100644 --- a/.github/workflows/humble-abi-compatibility.yml +++ b/.github/workflows/humble-abi-compatibility.yml @@ -8,6 +8,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/humble-abi-compatibility.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/humble-binary-build.yml b/.github/workflows/humble-binary-build.yml index 18dfae9666..6392ba8e68 100644 --- a/.github/workflows/humble-binary-build.yml +++ b/.github/workflows/humble-binary-build.yml @@ -11,6 +11,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/humble-binary-build.yml' - '**/package.xml' - '**/CMakeLists.txt' @@ -22,6 +23,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/humble-binary-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/humble-coverage-build.yml b/.github/workflows/humble-coverage-build.yml index d699e99278..209c931d4e 100644 --- a/.github/workflows/humble-coverage-build.yml +++ b/.github/workflows/humble-coverage-build.yml @@ -8,6 +8,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/humble-coverage-build.yml' - '**/package.xml' - '**/CMakeLists.txt' @@ -20,6 +21,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/humble-coverage-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/humble-debian-build.yml b/.github/workflows/humble-debian-build.yml index 5f0c33fa5e..85a1b38241 100644 --- a/.github/workflows/humble-debian-build.yml +++ b/.github/workflows/humble-debian-build.yml @@ -8,6 +8,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/humble-debian-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/humble-rhel-binary-build.yml b/.github/workflows/humble-rhel-binary-build.yml index dacaf8fee5..62c5cd62ba 100644 --- a/.github/workflows/humble-rhel-binary-build.yml +++ b/.github/workflows/humble-rhel-binary-build.yml @@ -8,6 +8,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/humble-rhel-binary-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/humble-semi-binary-build.yml b/.github/workflows/humble-semi-binary-build.yml index ec994cf7ff..560ac37cff 100644 --- a/.github/workflows/humble-semi-binary-build.yml +++ b/.github/workflows/humble-semi-binary-build.yml @@ -11,6 +11,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/humble-semi-binary-build.yml' - '**/package.xml' - '**/CMakeLists.txt' @@ -22,6 +23,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/humble-semi-binary-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/humble-source-build.yml b/.github/workflows/humble-source-build.yml index 5f437f8b0d..878ad92677 100644 --- a/.github/workflows/humble-source-build.yml +++ b/.github/workflows/humble-source-build.yml @@ -8,6 +8,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/humble-source-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/iron-abi-compatibility.yml b/.github/workflows/iron-abi-compatibility.yml index d9476298a2..c2d9c19110 100644 --- a/.github/workflows/iron-abi-compatibility.yml +++ b/.github/workflows/iron-abi-compatibility.yml @@ -8,6 +8,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/iron-abi-compatibility.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/iron-binary-build.yml b/.github/workflows/iron-binary-build.yml index 0fb0adcb1b..ef90e256a0 100644 --- a/.github/workflows/iron-binary-build.yml +++ b/.github/workflows/iron-binary-build.yml @@ -11,6 +11,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/iron-binary-build.yml' - '**/package.xml' - '**/CMakeLists.txt' @@ -22,6 +23,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/iron-binary-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/iron-coverage-build.yml b/.github/workflows/iron-coverage-build.yml index c74d66c3fd..ff5be81d7d 100644 --- a/.github/workflows/iron-coverage-build.yml +++ b/.github/workflows/iron-coverage-build.yml @@ -8,6 +8,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/iron-coverage-build.yml' - '**/package.xml' - '**/CMakeLists.txt' @@ -20,6 +21,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/iron-coverage-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/iron-debian-build.yml b/.github/workflows/iron-debian-build.yml index 2e6ba2289c..3cbe0c5127 100644 --- a/.github/workflows/iron-debian-build.yml +++ b/.github/workflows/iron-debian-build.yml @@ -8,6 +8,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/iron-debian-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/iron-rhel-binary-build.yml b/.github/workflows/iron-rhel-binary-build.yml index a45244b336..f308c495f3 100644 --- a/.github/workflows/iron-rhel-binary-build.yml +++ b/.github/workflows/iron-rhel-binary-build.yml @@ -8,6 +8,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/iron-rhel-binary-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/iron-semi-binary-build.yml b/.github/workflows/iron-semi-binary-build.yml index 8392655c35..30a83e5367 100644 --- a/.github/workflows/iron-semi-binary-build.yml +++ b/.github/workflows/iron-semi-binary-build.yml @@ -11,6 +11,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/iron-semi-binary-build.yml' - '**/package.xml' - '**/CMakeLists.txt' @@ -22,6 +23,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/iron-semi-binary-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/iron-source-build.yml b/.github/workflows/iron-source-build.yml index 608702ab72..3b7c53f6ff 100644 --- a/.github/workflows/iron-source-build.yml +++ b/.github/workflows/iron-source-build.yml @@ -8,6 +8,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/iron-source-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/jazzy-abi-compatibility.yml b/.github/workflows/jazzy-abi-compatibility.yml index 331234d30f..367b3736fb 100644 --- a/.github/workflows/jazzy-abi-compatibility.yml +++ b/.github/workflows/jazzy-abi-compatibility.yml @@ -8,6 +8,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/jazzy-abi-compatibility.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/jazzy-binary-build.yml b/.github/workflows/jazzy-binary-build.yml index ac335c79f4..5be853ebfc 100644 --- a/.github/workflows/jazzy-binary-build.yml +++ b/.github/workflows/jazzy-binary-build.yml @@ -11,6 +11,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/jazzy-binary-build.yml' - '**/package.xml' - '**/CMakeLists.txt' @@ -22,6 +23,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/jazzy-binary-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/jazzy-debian-build.yml b/.github/workflows/jazzy-debian-build.yml index b61f101ae5..4ec6a29fff 100644 --- a/.github/workflows/jazzy-debian-build.yml +++ b/.github/workflows/jazzy-debian-build.yml @@ -8,6 +8,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/jazzy-debian-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/jazzy-rhel-binary-build.yml b/.github/workflows/jazzy-rhel-binary-build.yml index 1320624345..0dcc912dab 100644 --- a/.github/workflows/jazzy-rhel-binary-build.yml +++ b/.github/workflows/jazzy-rhel-binary-build.yml @@ -8,6 +8,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/jazzy-rhel-binary-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/jazzy-semi-binary-build.yml b/.github/workflows/jazzy-semi-binary-build.yml index b6c669245a..9634732cf9 100644 --- a/.github/workflows/jazzy-semi-binary-build.yml +++ b/.github/workflows/jazzy-semi-binary-build.yml @@ -11,6 +11,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/jazzy-semi-binary-build.yml' - '**/package.xml' - '**/CMakeLists.txt' @@ -22,6 +23,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/jazzy-semi-binary-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/jazzy-source-build.yml b/.github/workflows/jazzy-source-build.yml index 1acadc4a12..65066a4bf2 100644 --- a/.github/workflows/jazzy-source-build.yml +++ b/.github/workflows/jazzy-source-build.yml @@ -8,6 +8,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/jazzy-source-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/rolling-abi-compatibility.yml b/.github/workflows/rolling-abi-compatibility.yml index 52b3ae8c57..b7828390fb 100644 --- a/.github/workflows/rolling-abi-compatibility.yml +++ b/.github/workflows/rolling-abi-compatibility.yml @@ -8,6 +8,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/rolling-abi-compatibility.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/rolling-binary-build.yml b/.github/workflows/rolling-binary-build.yml index f556962d3a..24a28f16ae 100644 --- a/.github/workflows/rolling-binary-build.yml +++ b/.github/workflows/rolling-binary-build.yml @@ -11,6 +11,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/rolling-binary-build.yml' - '**/package.xml' - '**/CMakeLists.txt' @@ -22,6 +23,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/rolling-binary-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/rolling-coverage-build.yml b/.github/workflows/rolling-coverage-build.yml index 4c61b47cbc..45b10876e7 100644 --- a/.github/workflows/rolling-coverage-build.yml +++ b/.github/workflows/rolling-coverage-build.yml @@ -8,6 +8,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/rolling-coverage-build.yml' - '**/package.xml' - '**/CMakeLists.txt' @@ -20,6 +21,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/rolling-coverage-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/rolling-debian-build.yml b/.github/workflows/rolling-debian-build.yml index 38ee0d213a..00d4ad844b 100644 --- a/.github/workflows/rolling-debian-build.yml +++ b/.github/workflows/rolling-debian-build.yml @@ -8,6 +8,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/rolling-debian-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/rolling-rhel-binary-build.yml b/.github/workflows/rolling-rhel-binary-build.yml index e7e0e93fd1..c8939d6015 100644 --- a/.github/workflows/rolling-rhel-binary-build.yml +++ b/.github/workflows/rolling-rhel-binary-build.yml @@ -8,6 +8,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/rolling-rhel-binary-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/rolling-semi-binary-build.yml b/.github/workflows/rolling-semi-binary-build.yml index 07cbcfc2f0..4cdb7ab585 100644 --- a/.github/workflows/rolling-semi-binary-build.yml +++ b/.github/workflows/rolling-semi-binary-build.yml @@ -11,6 +11,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/rolling-semi-binary-build.yml' - '**/package.xml' - '**/CMakeLists.txt' @@ -22,6 +23,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/rolling-semi-binary-build.yml' - '**/package.xml' - '**/CMakeLists.txt' diff --git a/.github/workflows/rolling-source-build.yml b/.github/workflows/rolling-source-build.yml index ae6b40a4b4..9bbf09cda4 100644 --- a/.github/workflows/rolling-source-build.yml +++ b/.github/workflows/rolling-source-build.yml @@ -8,6 +8,7 @@ on: - '**.hpp' - '**.h' - '**.cpp' + - '**.py' - '.github/workflows/rolling-source-build.yml' - '**/package.xml' - '**/CMakeLists.txt' From 765241e3a7d522f805681024ddd7c64262497b92 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 1 Jun 2024 13:26:23 +0200 Subject: [PATCH 6/9] Bump version of pre-commit hooks (#1556) * Bump version of pre-commit hooks * Fix codespell and satisfy linter --------- Co-authored-by: christophfroehlich <3367244+christophfroehlich@users.noreply.github.com> Co-authored-by: Christoph Froehlich --- .pre-commit-config.yaml | 6 +++--- controller_manager/test/test_spawner_unspawner.cpp | 3 ++- .../include/hardware_interface/actuator_interface.hpp | 4 ++-- .../include/hardware_interface/resource_manager.hpp | 2 +- .../include/hardware_interface/system_interface.hpp | 4 ++-- joint_limits/include/joint_limits/joint_limits.hpp | 9 ++++++--- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e039dd7ebb..8e604b2f7f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -62,7 +62,7 @@ repos: # CPP hooks - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v18.1.4 + rev: v18.1.5 hooks: - id: clang-format args: ['-fallback-style=none', '-i'] @@ -125,14 +125,14 @@ repos: # Spellcheck in comments and docs # skipping of *.svg files is not working... - repo: https://github.com/codespell-project/codespell - rev: v2.2.6 + rev: v2.3.0 hooks: - id: codespell args: ['--write-changes', '--uri-ignore-words-list=ist', '-L manuel'] exclude: CHANGELOG\.rst|\.(svg|pyc|drawio)$ - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.28.2 + rev: 0.28.4 hooks: - id: check-github-workflows args: ["--verbose"] diff --git a/controller_manager/test/test_spawner_unspawner.cpp b/controller_manager/test/test_spawner_unspawner.cpp index d65689c6f2..a5dd8fcda3 100644 --- a/controller_manager/test/test_spawner_unspawner.cpp +++ b/controller_manager/test/test_spawner_unspawner.cpp @@ -315,7 +315,8 @@ TEST_F(TestLoadController, unload_on_kill) // Launch spawner with unload on kill // timeout command will kill it after the specified time with signal SIGINT std::stringstream ss; - ss << "timeout --signal=INT 5 " << "ros2 run controller_manager spawner " + ss << "timeout --signal=INT 5 " + << "ros2 run controller_manager spawner " << "ctrl_3 -c test_controller_manager -t " << std::string(test_controller::TEST_CONTROLLER_CLASS_NAME) << " --unload-on-kill"; diff --git a/hardware_interface/include/hardware_interface/actuator_interface.hpp b/hardware_interface/include/hardware_interface/actuator_interface.hpp index 556d7a2047..1ae05aa5f7 100644 --- a/hardware_interface/include/hardware_interface/actuator_interface.hpp +++ b/hardware_interface/include/hardware_interface/actuator_interface.hpp @@ -131,7 +131,7 @@ class ActuatorInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNod * \note All starting and stopping interface keys are passed to all components, so the function * should return return_type::OK by default when given interface keys not relevant for this * component. \param[in] start_interfaces vector of string identifiers for the command interfaces - * starting. \param[in] stop_interfaces vector of string identifiers for the command interfacs + * starting. \param[in] stop_interfaces vector of string identifiers for the command interfaces * stopping. \return return_type::OK if the new command interface combination can be prepared, or * if the interface key is not relevant to this system. Returns return_type::ERROR otherwise. */ @@ -150,7 +150,7 @@ class ActuatorInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNod * \note All starting and stopping interface keys are passed to all components, so the function * should return return_type::OK by default when given interface keys not relevant for this * component. \param[in] start_interfaces vector of string identifiers for the command interfaces - * starting. \param[in] stop_interfaces vector of string identifiers for the command interfacs + * starting. \param[in] stop_interfaces vector of string identifiers for the command interfaces * stopping. \return return_type::OK if the new command interface combination can be switched to, * or if the interface key is not relevant to this system. Returns return_type::ERROR otherwise. */ diff --git a/hardware_interface/include/hardware_interface/resource_manager.hpp b/hardware_interface/include/hardware_interface/resource_manager.hpp index 91a98d1e96..31def4598b 100644 --- a/hardware_interface/include/hardware_interface/resource_manager.hpp +++ b/hardware_interface/include/hardware_interface/resource_manager.hpp @@ -353,7 +353,7 @@ class HARDWARE_INTERFACE_PUBLIC ResourceManager * \note it is assumed that `prepare_command_mode_switch` is called just before this method * with the same input arguments. * \param[in] start_interfaces vector of string identifiers for the command interfaces starting. - * \param[in] stop_interfaces vector of string identifiers for the command interfacs stopping. + * \param[in] stop_interfaces vector of string identifiers for the command interfaces stopping. * \return true if switch is performed, false if a component rejects switching. */ bool perform_command_mode_switch( diff --git a/hardware_interface/include/hardware_interface/system_interface.hpp b/hardware_interface/include/hardware_interface/system_interface.hpp index 0a7421531a..6b2c38b915 100644 --- a/hardware_interface/include/hardware_interface/system_interface.hpp +++ b/hardware_interface/include/hardware_interface/system_interface.hpp @@ -132,7 +132,7 @@ class SystemInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNodeI * \note All starting and stopping interface keys are passed to all components, so the function * should return return_type::OK by default when given interface keys not relevant for this * component. \param[in] start_interfaces vector of string identifiers for the command interfaces - * starting. \param[in] stop_interfaces vector of string identifiers for the command interfacs + * starting. \param[in] stop_interfaces vector of string identifiers for the command interfaces * stopping. \return return_type::OK if the new command interface combination can be prepared, or * if the interface key is not relevant to this system. Returns return_type::ERROR otherwise. */ @@ -151,7 +151,7 @@ class SystemInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNodeI * \note All starting and stopping interface keys are passed to all components, so the function * should return return_type::OK by default when given interface keys not relevant for this * component. \param[in] start_interfaces vector of string identifiers for the command interfaces - * starting. \param[in] stop_interfaces vector of string identifiers for the command interfacs + * starting. \param[in] stop_interfaces vector of string identifiers for the command interfaces * stopping. \return return_type::OK if the new command interface combination can be switched to, * or if the interface key is not relevant to this system. Returns return_type::ERROR otherwise. */ diff --git a/joint_limits/include/joint_limits/joint_limits.hpp b/joint_limits/include/joint_limits/joint_limits.hpp index f9944a85b1..809bfd777b 100644 --- a/joint_limits/include/joint_limits/joint_limits.hpp +++ b/joint_limits/include/joint_limits/joint_limits.hpp @@ -128,11 +128,14 @@ struct SoftJointLimits { std::stringstream ss_output; - ss_output << " soft position limits: " << "[" << min_position << ", " << max_position << "]\n"; + ss_output << " soft position limits: " + << "[" << min_position << ", " << max_position << "]\n"; - ss_output << " k-position: " << "[" << k_position << "]\n"; + ss_output << " k-position: " + << "[" << k_position << "]\n"; - ss_output << " k-velocity: " << "[" << k_velocity << "]\n"; + ss_output << " k-velocity: " + << "[" << k_velocity << "]\n"; return ss_output.str(); } From 9c84eb7e7a6550001e43543b04a172398f0ac587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Fr=C3=B6hlich?= Date: Sun, 2 Jun 2024 17:01:43 +0200 Subject: [PATCH 7/9] Reactivate generate_version_header (#1544) --- joint_limits/CMakeLists.txt | 3 +-- joint_limits/package.xml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/joint_limits/CMakeLists.txt b/joint_limits/CMakeLists.txt index 316a1ec7a2..3e296cd584 100644 --- a/joint_limits/CMakeLists.txt +++ b/joint_limits/CMakeLists.txt @@ -62,5 +62,4 @@ install(TARGETS joint_limits ament_export_targets(export_joint_limits HAS_LIBRARY_TARGET) ament_export_dependencies(${THIS_PACKAGE_INCLUDE_DEPENDS}) ament_package() -# TODO(anyone) uncomment if https://github.com/ament/ament_cmake/pull/526 is merged -# ament_generate_version_header(${PROJECT_NAME}) +ament_generate_version_header(${PROJECT_NAME}) diff --git a/joint_limits/package.xml b/joint_limits/package.xml index aec1c44b76..29b32e9819 100644 --- a/joint_limits/package.xml +++ b/joint_limits/package.xml @@ -13,8 +13,7 @@ https://github.com/ros-controls/ros2_control ament_cmake - - + ament_cmake_gen_version_h rclcpp rclcpp_lifecycle From e122f717109b2bb069ac2ed2418f36755d2e54c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Fr=C3=B6hlich?= Date: Sun, 2 Jun 2024 21:07:12 +0200 Subject: [PATCH 8/9] Add doc page about joint kinematics (#1497) --------- Co-authored-by: Sai Kishor Kothakota --- doc/index.rst | 7 +- hardware_interface/doc/joints_userdoc.rst | 132 ++++++++++++++++++++++ 2 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 hardware_interface/doc/joints_userdoc.rst diff --git a/doc/index.rst b/doc/index.rst index 91f965d601..b1d8e21c7d 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -16,12 +16,6 @@ API Documentation API documentation is parsed by doxygen and can be found `here <../../api/index.html>`_ -========= -Features -========= - -* :ref:`Command Line Interface (CLI) ` - ======== Concepts ======== @@ -31,5 +25,6 @@ Concepts Controller Manager <../controller_manager/doc/userdoc.rst> Controller Chaining / Cascade Control <../controller_manager/doc/controller_chaining.rst> + Joint Kinematics <../hardware_interface/doc/joints_userdoc.rst> Hardware Components <../hardware_interface/doc/hardware_components_userdoc.rst> Mock Components <../hardware_interface/doc/mock_components_userdoc.rst> diff --git a/hardware_interface/doc/joints_userdoc.rst b/hardware_interface/doc/joints_userdoc.rst new file mode 100644 index 0000000000..af88c8825f --- /dev/null +++ b/hardware_interface/doc/joints_userdoc.rst @@ -0,0 +1,132 @@ +:github_url: https://github.com/ros-controls/ros2_control/blob/{REPOS_FILE_BRANCH}/hardware_interface/doc/joints_userdoc.rst + +.. _joints_userdoc: + + +Joint Kinematics for ros2_control +--------------------------------------------------------- + +This page should give an overview of the joint kinematics in the context of ros2_control. It is intended to give a brief introduction to the topic and to explain the current implementation in ros2_control. + +Nomenclature +############ + +Degrees of Freedom (DoF) + From `wikipedia `__: + + In physics, the degrees of freedom (DoF) of a mechanical system is the number of independent parameters that define its configuration or state. + +Joint + A joint is a connection between two links. In the ROS ecosystem, three types are more typical: Revolute (A hinge joint with position limits), Continuous (A continuous hinge without any position limits) or Prismatic (A sliding joint that moves along the axis). + + In general, a joint can be actuated or non-actuated, also called passive. Passive joints are joints that do not have their own actuation mechanism but instead allow movement by external forces or by being passively moved by other joints. A passive joint can have a DoF of one, such as a pendulum, or it can be part of a parallel kinematic mechanism with zero DoF. + +Serial Kinematics + Serial kinematics refers to the arrangement of joints in a robotic manipulator where each joint is independent of the others, and the number of joints is equal to the DoF of the kinematic chain. + + A typical example is an industrial robot with six revolute joints, having 6-DoF. Each joint can be actuated independently, and the end-effector can be moved to any position and orientation in the workspace. + +Kinematic Loops + On the other hand, kinematic loops, also known as closed-loop mechanisms, involve several joints that are connected in a kinematic chain and being actuated together. This means that the joints are coupled and cannot be moved independently: In general, the number of DoFs is smaller than the number of joints. + This structure is typical for parallel kinematic mechanisms, where the end-effector is connected to the base by several kinematic chains. + + An example is the four-bar linkage, which consists of four links and four joints. It can have one or two actuators and consequently one or two DoFs, despite having four joints. Furthermore, we can say that we have one (two) actuated joint and three (two) passive joints, which must satisfy the kinematic constraints of the mechanism. + +URDF +############# + +URDF is the default format to describe robot kinematics in ROS. However, only serial kinematic chains are supported, except for the so-called mimic joints. See the `URDF specification `__ for more details. + +Mimic joints can be defined in the following way in the URDF + + .. code-block:: xml + + + + + + + + + + + + + + + + + +Mimic joints are an abstraction of the real world. For example, they can be used to describe + +* simple closed-loop kinematics with linear dependencies of the joint positions and velocities +* links connected with belts, like belt and pulley systems or telescope arms +* a simplified model of passive joints, e.g. a pendulum at the end-effector always pointing downwards +* abstract complex groups of actuated joints, where several joints are directly controlled by low-level control loops and move synchronously. Without giving a real-world example, this could be several motors with their individual power electronics but commanded with the same setpoint. + +Mimic joints defined in the URDF are parsed from the resource manager and stored in a class variable of type ``HardwareInfo``, which can be accessed by the hardware components. The mimic joints must not have command interfaces but can have state interfaces. + +.. code-block:: xml + + + + + + + + + + + + + + + +From the officially released packages, the following packages are already using this information: + +* :ref:`mock_components (generic system) ` +* :ref:`gazebo_ros2_control ` +* :ref:`gz_ros2_control ` + +As the URDF specifies only the kinematics, the mimic tag has to be independent of the hardware interface type used in ros2_control. This means that we interpret this info in the following way: + +* **position = multiplier * other_joint_position + offset** +* **velocity = multiplier * other_joint_velocity** + +If someone wants to deactivate the mimic joint behavior for whatever reason without changing the URDF, it can be done by setting the attribute ``mimic=false`` of the joint tag in the ```` section of the XML. + +.. code-block:: xml + + + + + + + +Transmission Interface +####################### +Mechanical transmissions transform effort/flow variables such that their product (power) remains constant. Effort variables for linear and rotational domains are force and torque; while the flow variables are respectively linear velocity and angular velocity. + +In robotics it is customary to place transmissions between actuators and joints. This interface adheres to this naming to identify the input and output spaces of the transformation. The provided interfaces allow bidirectional mappings between actuator and joint spaces for effort, velocity and position. Position is not a power variable, but the mappings can be implemented using the velocity map plus an integration constant representing the offset between actuator and joint zeros. + +The ``transmission_interface`` provides a base class and some implementations for plugins, which can be integrated and loaded by custom hardware components. They are not automatically loaded by any hardware component or the gazebo plugins, each hardware component is responsible for loading the appropriate transmission interface to map the actuator readings to joint readings. + +Currently the following implementations are available: + +* ``SimpleTransmission``: A simple transmission with a constant reduction ratio and no additional dynamics. +* ``DifferentialTransmission``: A differential transmission with two actuators and two joints. +* ``FourBarLinkageTransmission``: A four-bar-linkage transmission with two actuators and two joints. + +For more information, see :ref:`example_8 ` or the `transmission_interface `__ documentation. + +Simulating Closed-Loop Kinematic Chains +####################################### +Depending on the simulation plugin, different approaches can be used to simulate closed-loop kinematic chains. The following list gives an overview of the available simulation plugins and their capabilities: + +gazebo_ros2_control: + * mimic joints + * closed-loop kinematics are supported with ```` tags in the URDF, see, e.g., `here `__. + +gz_ros2_control: + * mimic joints + * closed-loop kinematics are not directly supported yet, but can be implemented by using a ``DetachableJoint`` via custom plugins. Follow `this issue `__ for updates on this topic. From 1ed61efcf82dd19e0946bb199a113429df96c507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Fr=C3=B6hlich?= Date: Mon, 3 Jun 2024 13:09:32 +0200 Subject: [PATCH 9/9] Fix update `period` for the first update after activation (#1551) --- controller_manager/src/controller_manager.cpp | 17 +++++++++++------ .../test/test_controller/test_controller.cpp | 2 +- .../test/test_controller/test_controller.hpp | 2 +- .../test/test_controller_manager.cpp | 19 ++++++++++++++----- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/controller_manager/src/controller_manager.cpp b/controller_manager/src/controller_manager.cpp index 41fe1ad95d..3b84fc07e4 100644 --- a/controller_manager/src/controller_manager.cpp +++ b/controller_manager/src/controller_manager.cpp @@ -2147,6 +2147,17 @@ controller_interface::return_type ControllerManager::update( run_controller_at_cm_rate ? period : rclcpp::Duration::from_seconds((1.0 / controller_update_rate)); + if ( + *loaded_controller.next_update_cycle_time == + rclcpp::Time(0, 0, this->get_node_clock_interface()->get_clock()->get_clock_type())) + { + // it is zero after activation + RCLCPP_DEBUG( + get_logger(), "Setting next_update_cycle_time to %fs for the controller : %s", + time.seconds(), loaded_controller.info.name.c_str()); + *loaded_controller.next_update_cycle_time = time; + } + bool controller_go = (time == rclcpp::Time(0, 0, this->get_node_clock_interface()->get_clock()->get_clock_type())) || @@ -2182,12 +2193,6 @@ controller_interface::return_type ControllerManager::update( controller_ret = controller_interface::return_type::ERROR; } - if ( - *loaded_controller.next_update_cycle_time == - rclcpp::Time(0, 0, this->get_node_clock_interface()->get_clock()->get_clock_type())) - { - *loaded_controller.next_update_cycle_time = time; - } *loaded_controller.next_update_cycle_time += controller_period; if (controller_ret != controller_interface::return_type::OK) diff --git a/controller_manager/test/test_controller/test_controller.cpp b/controller_manager/test/test_controller/test_controller.cpp index df41ebf34e..8f120bbd47 100644 --- a/controller_manager/test/test_controller/test_controller.cpp +++ b/controller_manager/test/test_controller/test_controller.cpp @@ -62,7 +62,7 @@ controller_interface::InterfaceConfiguration TestController::state_interface_con controller_interface::return_type TestController::update( const rclcpp::Time & /*time*/, const rclcpp::Duration & period) { - update_period_ = period.seconds(); + update_period_ = period; ++internal_counter; // set value to hardware to produce and test different behaviors there diff --git a/controller_manager/test/test_controller/test_controller.hpp b/controller_manager/test/test_controller/test_controller.hpp index 14ad753803..368aeae4a8 100644 --- a/controller_manager/test/test_controller/test_controller.hpp +++ b/controller_manager/test/test_controller/test_controller.hpp @@ -80,7 +80,7 @@ class TestController : public controller_interface::ControllerInterface // enables external setting of values to command interfaces - used for simulation of hardware // errors double set_first_command_interface_value_to; - double update_period_ = 0; + rclcpp::Duration update_period_ = rclcpp::Duration::from_seconds(0.); }; } // namespace test_controller diff --git a/controller_manager/test/test_controller_manager.cpp b/controller_manager/test/test_controller_manager.cpp index e88b41f222..3e2f91e91a 100644 --- a/controller_manager/test/test_controller_manager.cpp +++ b/controller_manager/test/test_controller_manager.cpp @@ -374,9 +374,12 @@ TEST_P(TestControllerManagerWithUpdateRates, per_controller_equal_and_higher_upd controller_interface::return_type::OK, cm_->update(time_, rclcpp::Duration::from_seconds(0.01))); // In case of a non perfect divisor, the update period should respect the rule - // [controller_update_rate, 2*controller_update_rate) - ASSERT_GE(test_controller->update_period_, 1.0 / cm_update_rate); - ASSERT_LT(test_controller->update_period_, 2.0 / cm_update_rate); + // [cm_update_rate, 2*cm_update_rate) + EXPECT_THAT( + test_controller->update_period_, + testing::AllOf( + testing::Ge(rclcpp::Duration::from_seconds(1.0 / cm_update_rate)), + testing::Lt(rclcpp::Duration::from_seconds(2.0 / cm_update_rate)))); loop_rate.sleep(); } // if we do 2 times of the controller_manager update rate, the internal counter should be @@ -445,6 +448,7 @@ TEST_P(TestControllerUpdateRates, check_the_controller_update_rate) EXPECT_EQ(lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE, test_controller->get_state().id()); // Start controller, will take effect at the end of the update function + time_ = test_controller->get_node()->now(); // set to something nonzero const auto strictness = controller_manager_msgs::srv::SwitchController::Request::STRICT; std::vector start_controllers = {test_controller::TEST_CONTROLLER_NAME}; std::vector stop_controllers = {}; @@ -472,6 +476,7 @@ TEST_P(TestControllerUpdateRates, check_the_controller_update_rate) const auto controller_update_rate = test_controller->get_update_rate(); const auto initial_counter = test_controller->internal_counter; + // don't start with zero to check if the period is correct if controller is activated anytime rclcpp::Time time = time_; for (size_t update_counter = 0; update_counter <= 10 * cm_update_rate; ++update_counter) { @@ -480,8 +485,12 @@ TEST_P(TestControllerUpdateRates, check_the_controller_update_rate) cm_->update(time, rclcpp::Duration::from_seconds(0.01))); // In case of a non perfect divisor, the update period should respect the rule // [controller_update_rate, 2*controller_update_rate) - ASSERT_GE(test_controller->update_period_, 1.0 / controller_update_rate); - ASSERT_LT(test_controller->update_period_, 2.0 / controller_update_rate); + EXPECT_THAT( + test_controller->update_period_, + testing::AllOf( + testing::Ge(rclcpp::Duration::from_seconds(1.0 / controller_update_rate)), + testing::Lt(rclcpp::Duration::from_seconds(2.0 / controller_update_rate)))) + << "update_counter: " << update_counter; time += rclcpp::Duration::from_seconds(0.01); if (update_counter % cm_update_rate == 0)