From 4523ef63e4c74d7ba72239240ce4f0845dce442c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 1 Sep 2024 22:16:55 +0200 Subject: [PATCH 01/10] Bump version of pre-commit hooks (#1737) (#1739) Co-authored-by: christophfroehlich <3367244+christophfroehlich@users.noreply.github.com> (cherry picked from commit ce58faea325335206ab3be356c64a7868b72a338) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 49ad7afbb7..4d5446133a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -49,13 +49,13 @@ repos: args: ["--ignore=D100,D101,D102,D103,D104,D105,D106,D107,D203,D212,D404"] - repo: https://github.com/psf/black - rev: 24.4.2 + rev: 24.8.0 hooks: - id: black args: ["--line-length=99"] - repo: https://github.com/pycqa/flake8 - rev: 7.1.0 + rev: 7.1.1 hooks: - id: flake8 args: ["--extend-ignore=E501"] @@ -132,7 +132,7 @@ repos: exclude: CHANGELOG\.rst|\.(svg|pyc|drawio)$ - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.29.1 + rev: 0.29.2 hooks: - id: check-github-workflows args: ["--verbose"] From 47a5dcd47311e437227fcbe3fee8b9c4e41f49b0 Mon Sep 17 00:00:00 2001 From: roscan-tech Date: Mon, 9 Sep 2024 04:32:46 +0800 Subject: [PATCH 02/10] controller_manager: Add space to string literal concatenation (#1245) (#1747) Signed-off-by: roscan-tech Co-authored-by: Yasushi SHOJI --- controller_manager/src/controller_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller_manager/src/controller_manager.cpp b/controller_manager/src/controller_manager.cpp index 7ec2216588..85aee476a8 100644 --- a/controller_manager/src/controller_manager.cpp +++ b/controller_manager/src/controller_manager.cpp @@ -973,7 +973,7 @@ controller_interface::return_type ControllerManager::switch_controller( { RCLCPP_WARN( get_logger(), - "Controller with name '%s' is not inactive so its following" + "Controller with name '%s' is not inactive so its following " "controllers do not have to be checked, because it cannot be activated.", controller_it->info.name.c_str()); ret = controller_interface::return_type::ERROR; From 407c377764cacd18b9c3c828534f736de5cac488 Mon Sep 17 00:00:00 2001 From: Bence Magyar Date: Wed, 11 Sep 2024 12:29:08 +0100 Subject: [PATCH 03/10] Update changelogs --- controller_interface/CHANGELOG.rst | 3 +++ controller_manager/CHANGELOG.rst | 6 ++++++ controller_manager_msgs/CHANGELOG.rst | 3 +++ hardware_interface/CHANGELOG.rst | 3 +++ hardware_interface_testing/CHANGELOG.rst | 3 +++ joint_limits/CHANGELOG.rst | 3 +++ ros2_control/CHANGELOG.rst | 3 +++ ros2_control_test_assets/CHANGELOG.rst | 3 +++ ros2controlcli/CHANGELOG.rst | 5 +++++ rqt_controller_manager/CHANGELOG.rst | 3 +++ transmission_interface/CHANGELOG.rst | 3 +++ 11 files changed, 38 insertions(+) diff --git a/controller_interface/CHANGELOG.rst b/controller_interface/CHANGELOG.rst index 8d8e51dd0f..f8772ef88d 100644 --- a/controller_interface/CHANGELOG.rst +++ b/controller_interface/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package controller_interface ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- + 2.43.0 (2024-08-22) ------------------- diff --git a/controller_manager/CHANGELOG.rst b/controller_manager/CHANGELOG.rst index 3d484754a3..016edb953e 100644 --- a/controller_manager/CHANGELOG.rst +++ b/controller_manager/CHANGELOG.rst @@ -2,6 +2,12 @@ Changelog for package controller_manager ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- +* controller_manager: Add space to string literal concatenation (`#1245 `_) (`#1747 `_) +* fix: the print of the information in control node was in wrong order (`#1726 `_) +* Contributors: Manuel Muth, roscan-tech + 2.43.0 (2024-08-22) ------------------- * Infrom user why rt policy could not be set, infrom if is set. (backport `#1705 `_) (`#1708 `_) diff --git a/controller_manager_msgs/CHANGELOG.rst b/controller_manager_msgs/CHANGELOG.rst index 1826f5214b..1eb502bd2f 100644 --- a/controller_manager_msgs/CHANGELOG.rst +++ b/controller_manager_msgs/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package controller_manager_msgs ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- + 2.43.0 (2024-08-22) ------------------- diff --git a/hardware_interface/CHANGELOG.rst b/hardware_interface/CHANGELOG.rst index 258831fde5..3742896dba 100644 --- a/hardware_interface/CHANGELOG.rst +++ b/hardware_interface/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package hardware_interface ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- + 2.43.0 (2024-08-22) ------------------- diff --git a/hardware_interface_testing/CHANGELOG.rst b/hardware_interface_testing/CHANGELOG.rst index 24ddb8d46a..bf1d6b7409 100644 --- a/hardware_interface_testing/CHANGELOG.rst +++ b/hardware_interface_testing/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package hardware_interface_testing ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- + 2.43.0 (2024-08-22) ------------------- diff --git a/joint_limits/CHANGELOG.rst b/joint_limits/CHANGELOG.rst index bf0c0f8880..4f5f9d2248 100644 --- a/joint_limits/CHANGELOG.rst +++ b/joint_limits/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package joint_limits ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- + 2.43.0 (2024-08-22) ------------------- diff --git a/ros2_control/CHANGELOG.rst b/ros2_control/CHANGELOG.rst index 3e655410e4..d2051cb961 100644 --- a/ros2_control/CHANGELOG.rst +++ b/ros2_control/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package ros2_control ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- + 2.43.0 (2024-08-22) ------------------- diff --git a/ros2_control_test_assets/CHANGELOG.rst b/ros2_control_test_assets/CHANGELOG.rst index 86245b9084..5608faae41 100644 --- a/ros2_control_test_assets/CHANGELOG.rst +++ b/ros2_control_test_assets/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package ros2_control_test_assets ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- + 2.43.0 (2024-08-22) ------------------- diff --git a/ros2controlcli/CHANGELOG.rst b/ros2controlcli/CHANGELOG.rst index 1a8615b220..991cb86ca7 100644 --- a/ros2controlcli/CHANGELOG.rst +++ b/ros2controlcli/CHANGELOG.rst @@ -2,6 +2,11 @@ Changelog for package ros2controlcli ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- +* [ros2controlcli] fix list_controllers when no controllers are loaded (`#1721 `_) (`#1722 `_) +* Contributors: mergify[bot] + 2.43.0 (2024-08-22) ------------------- * Make list controller and list hardware components immediately visualize the state. (backport `#1606 `_) (`#1690 `_) diff --git a/rqt_controller_manager/CHANGELOG.rst b/rqt_controller_manager/CHANGELOG.rst index f9bc94e0b8..b088bc41cb 100644 --- a/rqt_controller_manager/CHANGELOG.rst +++ b/rqt_controller_manager/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package rqt_controller_manager ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- + 2.43.0 (2024-08-22) ------------------- diff --git a/transmission_interface/CHANGELOG.rst b/transmission_interface/CHANGELOG.rst index 011605cc38..f821930c80 100644 --- a/transmission_interface/CHANGELOG.rst +++ b/transmission_interface/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog for package transmission_interface ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Forthcoming +----------- + 2.43.0 (2024-08-22) ------------------- * Fix flaky transmission_interface tests by making them deterministic. (backport `#1665 `_) (`#1670 `_) From 5d839e60321bdd73124311c54fae2d2fff5966ec Mon Sep 17 00:00:00 2001 From: Bence Magyar Date: Wed, 11 Sep 2024 12:29:47 +0100 Subject: [PATCH 04/10] 2.43.1 --- 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 f8772ef88d..28619a8cfa 100644 --- a/controller_interface/CHANGELOG.rst +++ b/controller_interface/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package controller_interface ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +2.43.1 (2024-09-11) +------------------- 2.43.0 (2024-08-22) ------------------- diff --git a/controller_interface/package.xml b/controller_interface/package.xml index 9651b160b1..ca23197bf5 100644 --- a/controller_interface/package.xml +++ b/controller_interface/package.xml @@ -2,7 +2,7 @@ controller_interface - 2.43.0 + 2.43.1 Description of controller_interface Bence Magyar Denis Štogl diff --git a/controller_manager/CHANGELOG.rst b/controller_manager/CHANGELOG.rst index 016edb953e..7f6778a2eb 100644 --- a/controller_manager/CHANGELOG.rst +++ b/controller_manager/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package controller_manager ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +2.43.1 (2024-09-11) +------------------- * controller_manager: Add space to string literal concatenation (`#1245 `_) (`#1747 `_) * fix: the print of the information in control node was in wrong order (`#1726 `_) * Contributors: Manuel Muth, roscan-tech diff --git a/controller_manager/package.xml b/controller_manager/package.xml index 4c240ffc0e..d31b4ee6b5 100644 --- a/controller_manager/package.xml +++ b/controller_manager/package.xml @@ -2,7 +2,7 @@ controller_manager - 2.43.0 + 2.43.1 Description of controller_manager Bence Magyar Denis Štogl diff --git a/controller_manager_msgs/CHANGELOG.rst b/controller_manager_msgs/CHANGELOG.rst index 1eb502bd2f..1098951237 100644 --- a/controller_manager_msgs/CHANGELOG.rst +++ b/controller_manager_msgs/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package controller_manager_msgs ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +2.43.1 (2024-09-11) +------------------- 2.43.0 (2024-08-22) ------------------- diff --git a/controller_manager_msgs/package.xml b/controller_manager_msgs/package.xml index 5d76157cfc..b772a87641 100644 --- a/controller_manager_msgs/package.xml +++ b/controller_manager_msgs/package.xml @@ -2,7 +2,7 @@ controller_manager_msgs - 2.43.0 + 2.43.1 Messages and services for the controller manager. Bence Magyar Denis Štogl diff --git a/hardware_interface/CHANGELOG.rst b/hardware_interface/CHANGELOG.rst index 3742896dba..91f48fbc75 100644 --- a/hardware_interface/CHANGELOG.rst +++ b/hardware_interface/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package hardware_interface ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +2.43.1 (2024-09-11) +------------------- 2.43.0 (2024-08-22) ------------------- diff --git a/hardware_interface/package.xml b/hardware_interface/package.xml index d05e55630a..063b00c3e2 100644 --- a/hardware_interface/package.xml +++ b/hardware_interface/package.xml @@ -1,7 +1,7 @@ hardware_interface - 2.43.0 + 2.43.1 ros2_control hardware interface Bence Magyar Denis Štogl diff --git a/hardware_interface_testing/CHANGELOG.rst b/hardware_interface_testing/CHANGELOG.rst index bf1d6b7409..413d923684 100644 --- a/hardware_interface_testing/CHANGELOG.rst +++ b/hardware_interface_testing/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package hardware_interface_testing ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +2.43.1 (2024-09-11) +------------------- 2.43.0 (2024-08-22) ------------------- diff --git a/hardware_interface_testing/package.xml b/hardware_interface_testing/package.xml index 05e4c47316..492f5293da 100644 --- a/hardware_interface_testing/package.xml +++ b/hardware_interface_testing/package.xml @@ -1,7 +1,7 @@ hardware_interface_testing - 2.43.0 + 2.43.1 ros2_control hardware interface testing Bence Magyar Denis Štogl diff --git a/joint_limits/CHANGELOG.rst b/joint_limits/CHANGELOG.rst index 4f5f9d2248..aef6977b5e 100644 --- a/joint_limits/CHANGELOG.rst +++ b/joint_limits/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package joint_limits ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +2.43.1 (2024-09-11) +------------------- 2.43.0 (2024-08-22) ------------------- diff --git a/joint_limits/package.xml b/joint_limits/package.xml index dfb3d50487..c6319ee015 100644 --- a/joint_limits/package.xml +++ b/joint_limits/package.xml @@ -1,6 +1,6 @@ joint_limits - 2.43.0 + 2.43.1 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 d2051cb961..eadc046016 100644 --- a/ros2_control/CHANGELOG.rst +++ b/ros2_control/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package ros2_control ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +2.43.1 (2024-09-11) +------------------- 2.43.0 (2024-08-22) ------------------- diff --git a/ros2_control/package.xml b/ros2_control/package.xml index 8eb9286650..1ce35b5610 100644 --- a/ros2_control/package.xml +++ b/ros2_control/package.xml @@ -1,7 +1,7 @@ ros2_control - 2.43.0 + 2.43.1 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 5608faae41..62e7f19644 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 ------------ +2.43.1 (2024-09-11) +------------------- 2.43.0 (2024-08-22) ------------------- diff --git a/ros2_control_test_assets/package.xml b/ros2_control_test_assets/package.xml index 53b236d7a8..fa19627bc1 100644 --- a/ros2_control_test_assets/package.xml +++ b/ros2_control_test_assets/package.xml @@ -2,7 +2,7 @@ ros2_control_test_assets - 2.43.0 + 2.43.1 The package provides shared test resources for ros2_control stack Bence Magyar diff --git a/ros2controlcli/CHANGELOG.rst b/ros2controlcli/CHANGELOG.rst index 991cb86ca7..755cebc640 100644 --- a/ros2controlcli/CHANGELOG.rst +++ b/ros2controlcli/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package ros2controlcli ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +2.43.1 (2024-09-11) +------------------- * [ros2controlcli] fix list_controllers when no controllers are loaded (`#1721 `_) (`#1722 `_) * Contributors: mergify[bot] diff --git a/ros2controlcli/package.xml b/ros2controlcli/package.xml index 40ff514c42..413b3a0359 100644 --- a/ros2controlcli/package.xml +++ b/ros2controlcli/package.xml @@ -2,7 +2,7 @@ ros2controlcli - 2.43.0 + 2.43.1 The ROS 2 command line tools for ROS2 Control. diff --git a/ros2controlcli/setup.py b/ros2controlcli/setup.py index 6d2b528819..5c0fc844de 100644 --- a/ros2controlcli/setup.py +++ b/ros2controlcli/setup.py @@ -19,7 +19,7 @@ setup( name=package_name, - version="2.43.0", + version="2.43.1", 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 b088bc41cb..459d0a3e0f 100644 --- a/rqt_controller_manager/CHANGELOG.rst +++ b/rqt_controller_manager/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package rqt_controller_manager ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +2.43.1 (2024-09-11) +------------------- 2.43.0 (2024-08-22) ------------------- diff --git a/rqt_controller_manager/package.xml b/rqt_controller_manager/package.xml index 0aa437f211..f1d1495286 100644 --- a/rqt_controller_manager/package.xml +++ b/rqt_controller_manager/package.xml @@ -2,7 +2,7 @@ rqt_controller_manager - 2.43.0 + 2.43.1 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 fced5c39d1..160d62cfbf 100644 --- a/rqt_controller_manager/setup.py +++ b/rqt_controller_manager/setup.py @@ -20,7 +20,7 @@ setup( name=package_name, - version="2.43.0", + version="2.43.1", 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 f821930c80..c322fd03da 100644 --- a/transmission_interface/CHANGELOG.rst +++ b/transmission_interface/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog for package transmission_interface ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Forthcoming ------------ +2.43.1 (2024-09-11) +------------------- 2.43.0 (2024-08-22) ------------------- diff --git a/transmission_interface/package.xml b/transmission_interface/package.xml index ade79bcf7e..6eecbfb0b1 100644 --- a/transmission_interface/package.xml +++ b/transmission_interface/package.xml @@ -2,7 +2,7 @@ transmission_interface - 2.43.0 + 2.43.1 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 f115f4f3907d3894b23b8495cc358aa2ad80d72c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 20:06:54 +0200 Subject: [PATCH 05/10] Bump version of pre-commit hooks (#1770) (#1772) (cherry picked from commit ab84b74b079a9b6df887d36a84d8965b5342d19c) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4d5446133a..b3b9424576 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.8 + rev: v19.1.0 hooks: - id: clang-format args: ['-fallback-style=none', '-i'] @@ -108,7 +108,7 @@ repos: # Docs - RestructuredText hooks - repo: https://github.com/PyCQA/doc8 - rev: v1.1.1 + rev: v1.1.2 hooks: - id: doc8 args: ['--max-line-length=100', '--ignore=D001'] @@ -132,7 +132,7 @@ repos: exclude: CHANGELOG\.rst|\.(svg|pyc|drawio)$ - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.29.2 + rev: 0.29.3 hooks: - id: check-github-workflows args: ["--verbose"] From b00988134ddfe2e3fbd8178124fd41981b84ae41 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 10:23:51 +0200 Subject: [PATCH 06/10] Fix spawner tests timeout on source builds (backport #1692) (#1697) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix spawner tests timeout (#1692) (cherry picked from commit 079392b94867c3372c0050492327001057118090) # Conflicts: # controller_manager/test/test_spawner_unspawner.cpp * Fix merge conflicts --------- Co-authored-by: Sai Kishor Kothakota Co-authored-by: Christoph Fröhlich --- controller_manager/test/test_spawner_unspawner.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/controller_manager/test/test_spawner_unspawner.cpp b/controller_manager/test/test_spawner_unspawner.cpp index 2bda6d6431..c83f777cfa 100644 --- a/controller_manager/test/test_spawner_unspawner.cpp +++ b/controller_manager/test/test_spawner_unspawner.cpp @@ -221,6 +221,7 @@ TEST_F(TestLoadController, multi_ctrls_test_type_in_param) TEST_F(TestLoadController, spawner_test_type_in_arg) { + ControllerManagerRunner cm_runner(this); // Provide controller type via -t argument EXPECT_EQ( call_spawner( @@ -239,6 +240,7 @@ TEST_F(TestLoadController, unload_on_kill) { // Launch spawner with unload on kill // timeout command will kill it after the specified time with signal SIGINT + ControllerManagerRunner cm_runner(this); std::stringstream ss; ss << "timeout --signal=INT 5 " << std::string(coveragepy_script) + @@ -507,6 +509,7 @@ TEST_F(TestLoadControllerWithNamespacedCM, spawner_test_type_in_params_file) const std::string test_file_path = ament_index_cpp::get_package_prefix("controller_manager") + "/test/test_controller_spawner_with_type.yaml"; + ControllerManagerRunner cm_runner(this); // Provide controller type via the parsed file EXPECT_EQ( call_spawner( @@ -567,6 +570,7 @@ TEST_F( const std::string test_file_path = ament_index_cpp::get_package_prefix("controller_manager") + "/test/test_controller_spawner_with_type.yaml"; + ControllerManagerRunner cm_runner(this); // Provide controller type via the parsed file EXPECT_EQ( call_spawner( From 5bf0da5b58930cba57a314d598c2cd8c66e930eb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 17:28:52 +0100 Subject: [PATCH 07/10] [CM] Throw an exception when the components initially fail to be in the required state (backport #1729) (#1777) --- controller_manager/src/controller_manager.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/controller_manager/src/controller_manager.cpp b/controller_manager/src/controller_manager.cpp index 85aee476a8..ab89c8760c 100644 --- a/controller_manager/src/controller_manager.cpp +++ b/controller_manager/src/controller_manager.cpp @@ -395,7 +395,14 @@ void ControllerManager::init_resource_manager(const std::string & robot_descript RCLCPP_INFO( get_logger(), "Setting component '%s' to '%s' state.", component.c_str(), state.label().c_str()); - resource_manager_->set_component_state(component, state); + if ( + resource_manager_->set_component_state(component, state) == + hardware_interface::return_type::ERROR) + { + throw std::runtime_error( + "Failed to set the initial state of the component : " + component + " to " + + state.label()); + } components_to_activate.erase(component); } } @@ -459,7 +466,14 @@ void ControllerManager::init_resource_manager(const std::string & robot_descript { rclcpp_lifecycle::State active_state( State::PRIMARY_STATE_ACTIVE, hardware_interface::lifecycle_state_names::ACTIVE); - resource_manager_->set_component_state(component, active_state); + if ( + resource_manager_->set_component_state(component, active_state) == + hardware_interface::return_type::ERROR) + { + throw std::runtime_error( + "Failed to set the initial state of the component : " + component + " to " + + active_state.label()); + } } } } From b6ab06eef024c90128acc9514a4115a8845f27dc Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 08:54:15 +0100 Subject: [PATCH 08/10] Add `PoseSensor` semantic component (#1775) (#1785) --- controller_interface/CMakeLists.txt | 8 ++ .../semantic_components/pose_sensor.hpp | 110 ++++++++++++++++++ controller_interface/package.xml | 2 + .../test/test_pose_sensor.cpp | 98 ++++++++++++++++ .../test/test_pose_sensor.hpp | 59 ++++++++++ doc/release_notes.rst | 5 + 6 files changed, 282 insertions(+) create mode 100644 controller_interface/include/semantic_components/pose_sensor.hpp create mode 100644 controller_interface/test/test_pose_sensor.cpp create mode 100644 controller_interface/test/test_pose_sensor.hpp diff --git a/controller_interface/CMakeLists.txt b/controller_interface/CMakeLists.txt index 4f28622bd6..7486ee3414 100644 --- a/controller_interface/CMakeLists.txt +++ b/controller_interface/CMakeLists.txt @@ -44,6 +44,7 @@ if(BUILD_TESTING) find_package(hardware_interface REQUIRED) find_package(sensor_msgs REQUIRED) + find_package(geometry_msgs REQUIRED) ament_add_gmock(test_controller_interface test/test_controller_interface.cpp) target_link_libraries(test_controller_interface ${PROJECT_NAME}) @@ -88,6 +89,13 @@ if(BUILD_TESTING) hardware_interface sensor_msgs ) + + ament_add_gmock(test_pose_sensor test/test_pose_sensor.cpp) + target_include_directories(test_pose_sensor PRIVATE include) + ament_target_dependencies(test_pose_sensor + hardware_interface + geometry_msgs + ) endif() ament_export_dependencies( diff --git a/controller_interface/include/semantic_components/pose_sensor.hpp b/controller_interface/include/semantic_components/pose_sensor.hpp new file mode 100644 index 0000000000..60dbecd718 --- /dev/null +++ b/controller_interface/include/semantic_components/pose_sensor.hpp @@ -0,0 +1,110 @@ +// Copyright 2024 FZI Forschungszentrum Informatik +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef SEMANTIC_COMPONENTS__POSE_SENSOR_HPP_ +#define SEMANTIC_COMPONENTS__POSE_SENSOR_HPP_ + +#include +#include +#include + +#include "geometry_msgs/msg/pose.hpp" +#include "semantic_components/semantic_component_interface.hpp" + +namespace semantic_components +{ + +class PoseSensor : public SemanticComponentInterface +{ +public: + /// Constructor for a standard pose sensor with interface names set based on sensor name. + explicit PoseSensor(const std::string & name) : SemanticComponentInterface{name, 7} + { + // Use standard interface names + interface_names_.emplace_back(name_ + '/' + "position.x"); + interface_names_.emplace_back(name_ + '/' + "position.y"); + interface_names_.emplace_back(name_ + '/' + "position.z"); + interface_names_.emplace_back(name_ + '/' + "orientation.x"); + interface_names_.emplace_back(name_ + '/' + "orientation.y"); + interface_names_.emplace_back(name_ + '/' + "orientation.z"); + interface_names_.emplace_back(name_ + '/' + "orientation.w"); + + // Set all sensor values to default value NaN + std::fill(position_.begin(), position_.end(), std::numeric_limits::quiet_NaN()); + std::fill(orientation_.begin(), orientation_.end(), std::numeric_limits::quiet_NaN()); + } + + virtual ~PoseSensor() = default; + + /// Update and return position. + /*! + * Update and return current pose position from state interfaces. + * + * \return Array of position coordinates. + */ + std::array get_position() + { + for (size_t i = 0; i < 3; ++i) + { + position_[i] = state_interfaces_[i].get().get_value(); + } + + return position_; + } + + /// Update and return orientation + /*! + * Update and return current pose orientation from state interfaces. + * + * \return Array of orientation coordinates in xyzw convention. + */ + std::array get_orientation() + { + for (size_t i = 3; i < 7; ++i) + { + orientation_[i - 3] = state_interfaces_[i].get().get_value(); + } + + return orientation_; + } + + /// Fill pose message with current values. + /** + * Fill a pose message with current position and orientation from the state interfaces. + */ + bool get_values_as_message(geometry_msgs::msg::Pose & message) + { + // Update state from state interfaces + get_position(); + get_orientation(); + + // Set message values from current state + message.position.x = position_[0]; + message.position.y = position_[1]; + message.position.z = position_[2]; + message.orientation.x = orientation_[0]; + message.orientation.y = orientation_[1]; + message.orientation.z = orientation_[2]; + message.orientation.w = orientation_[3]; + + return true; + } + +protected: + std::array position_; + std::array orientation_; +}; + +} // namespace semantic_components + +#endif // SEMANTIC_COMPONENTS__POSE_SENSOR_HPP_ diff --git a/controller_interface/package.xml b/controller_interface/package.xml index ca23197bf5..3aca05b65f 100644 --- a/controller_interface/package.xml +++ b/controller_interface/package.xml @@ -22,6 +22,8 @@ rclcpp_lifecycle ament_cmake_gmock + geometry_msgs + sensor_msgs ament_cmake diff --git a/controller_interface/test/test_pose_sensor.cpp b/controller_interface/test/test_pose_sensor.cpp new file mode 100644 index 0000000000..1ceb7c32a6 --- /dev/null +++ b/controller_interface/test/test_pose_sensor.cpp @@ -0,0 +1,98 @@ +// Copyright (c) 2024, FZI Forschungszentrum Informatik +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "test_pose_sensor.hpp" + +void PoseSensorTest::SetUp() +{ + full_interface_names_.reserve(size_); + for (const auto & interface_name : interface_names_) + { + full_interface_names_.emplace_back(sensor_name_ + '/' + interface_name); + } +} + +void PoseSensorTest::TearDown() { pose_sensor_.reset(nullptr); } + +TEST_F(PoseSensorTest, validate_all) +{ + // Create sensor + pose_sensor_ = std::make_unique(sensor_name_); + EXPECT_EQ(pose_sensor_->name_, sensor_name_); + + // Validate reserved space for interface_names_ and state_interfaces_ + // As state_interfaces_ are not defined yet, use capacity() + ASSERT_EQ(pose_sensor_->interface_names_.size(), size_); + ASSERT_EQ(pose_sensor_->state_interfaces_.capacity(), size_); + + // Validate default interface_names_ + EXPECT_TRUE(std::equal( + pose_sensor_->interface_names_.cbegin(), pose_sensor_->interface_names_.cend(), + full_interface_names_.cbegin(), full_interface_names_.cend())); + + // Get interface names + std::vector interface_names = pose_sensor_->get_state_interface_names(); + + // Assign values to position + hardware_interface::StateInterface position_x{ + sensor_name_, interface_names_[0], &position_values_[0]}; + hardware_interface::StateInterface position_y{ + sensor_name_, interface_names_[1], &position_values_[1]}; + hardware_interface::StateInterface position_z{ + sensor_name_, interface_names_[2], &position_values_[2]}; + + // Assign values to orientation + hardware_interface::StateInterface orientation_x{ + sensor_name_, interface_names_[3], &orientation_values_[0]}; + hardware_interface::StateInterface orientation_y{ + sensor_name_, interface_names_[4], &orientation_values_[1]}; + hardware_interface::StateInterface orientation_z{ + sensor_name_, interface_names_[5], &orientation_values_[2]}; + hardware_interface::StateInterface orientation_w{ + sensor_name_, interface_names_[6], &orientation_values_[3]}; + + // Create state interface vector in jumbled order + std::vector temp_state_interfaces; + temp_state_interfaces.reserve(7); + + temp_state_interfaces.emplace_back(position_z); + temp_state_interfaces.emplace_back(orientation_y); + temp_state_interfaces.emplace_back(orientation_x); + temp_state_interfaces.emplace_back(position_x); + temp_state_interfaces.emplace_back(orientation_w); + temp_state_interfaces.emplace_back(position_y); + temp_state_interfaces.emplace_back(orientation_z); + + // Assign interfaces + pose_sensor_->assign_loaned_state_interfaces(temp_state_interfaces); + EXPECT_EQ(pose_sensor_->state_interfaces_.size(), size_); + + // Validate correct position and orientation + EXPECT_EQ(pose_sensor_->get_position(), position_values_); + EXPECT_EQ(pose_sensor_->get_orientation(), orientation_values_); + + // Validate generated message + geometry_msgs::msg::Pose temp_message; + ASSERT_TRUE(pose_sensor_->get_values_as_message(temp_message)); + EXPECT_EQ(temp_message.position.x, position_values_[0]); + EXPECT_EQ(temp_message.position.y, position_values_[1]); + EXPECT_EQ(temp_message.position.z, position_values_[2]); + EXPECT_EQ(temp_message.orientation.x, orientation_values_[0]); + EXPECT_EQ(temp_message.orientation.y, orientation_values_[1]); + EXPECT_EQ(temp_message.orientation.z, orientation_values_[2]); + EXPECT_EQ(temp_message.orientation.w, orientation_values_[3]); + + // Release state interfaces + pose_sensor_->release_interfaces(); + ASSERT_EQ(pose_sensor_->state_interfaces_.size(), 0); +} diff --git a/controller_interface/test/test_pose_sensor.hpp b/controller_interface/test/test_pose_sensor.hpp new file mode 100644 index 0000000000..c2344caaa2 --- /dev/null +++ b/controller_interface/test/test_pose_sensor.hpp @@ -0,0 +1,59 @@ +// Copyright (c) 2024, FZI Forschungszentrum Informatik +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef TEST_POSE_SENSOR_HPP_ +#define TEST_POSE_SENSOR_HPP_ + +#include + +#include +#include +#include +#include + +#include "semantic_components/pose_sensor.hpp" + +class TestablePoseSensor : public semantic_components::PoseSensor +{ + FRIEND_TEST(PoseSensorTest, validate_all); + +public: + // Use default interface names + explicit TestablePoseSensor(const std::string & name) : PoseSensor{name} {} + + virtual ~TestablePoseSensor() = default; +}; + +class PoseSensorTest : public ::testing::Test +{ +public: + void SetUp(); + void TearDown(); + +protected: + const size_t size_ = 7; + const std::string sensor_name_ = "test_pose_sensor"; + + std::vector full_interface_names_; + const std::vector interface_names_ = { + "position.x", "position.y", "position.z", "orientation.x", + "orientation.y", "orientation.z", "orientation.w"}; + + std::array position_values_ = {{1.1, 2.2, 3.3}}; + std::array orientation_values_ = {{4.4, 5.5, 6.6, 7.7}}; + + std::unique_ptr pose_sensor_; +}; + +#endif // TEST_POSE_SENSOR_HPP_ diff --git a/doc/release_notes.rst b/doc/release_notes.rst index a1c76f6caf..a945ff668e 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -7,3 +7,8 @@ This list summarizes the changes between Galactic (previous) and Humble (current .. note:: This list was created in July 2024, earlier changes may not be included. + +controller_interface +******************** + +* The new ``PoseSensor`` semantic component provides a standard interface for hardware providing cartesian poses (`#1775 `_) From f3e7db62503a02843d223b5437097486758774a5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 09:01:20 +0100 Subject: [PATCH 09/10] [CM] Handle other exceptions while loading the controller plugin (#1731) (#1733) --- controller_manager/src/controller_manager.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/controller_manager/src/controller_manager.cpp b/controller_manager/src/controller_manager.cpp index ab89c8760c..6d8bae1f28 100644 --- a/controller_manager/src/controller_manager.cpp +++ b/controller_manager/src/controller_manager.cpp @@ -570,13 +570,21 @@ controller_interface::ControllerInterfaceBaseSharedPtr ControllerManager::load_c controller = chainable_loader_->createSharedInstance(controller_type); } } - catch (const pluginlib::CreateClassException & e) + catch (const std::exception & e) { RCLCPP_ERROR( - get_logger(), "Error happened during creation of controller '%s' with type '%s':\n%s", + get_logger(), "Caught exception while loading the controller '%s' of plugin type '%s':\n%s", controller_name.c_str(), controller_type.c_str(), e.what()); return nullptr; } + catch (...) + { + RCLCPP_ERROR( + get_logger(), + "Caught unknown exception while loading the controller '%s' of plugin type '%s'", + controller_name.c_str(), controller_type.c_str()); + throw; + } ControllerSpec controller_spec; controller_spec.c = controller; From 4ed2ee01505e340cb268211fd9a61fc50dd11946 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 11 Oct 2024 09:48:38 +0100 Subject: [PATCH 10/10] [rqt_controller_manager] Add hardware components (#1455) (#1586) --- .../doc/images/rqt_controller_manager.png | Bin 0 -> 49114 bytes controller_manager/doc/userdoc.rst | 16 + rqt_controller_manager/package.xml | 5 +- .../resource/controller_manager.ui | 36 ++- .../{controller_info.ui => popup_info.ui} | 3 - .../controller_manager.py | 305 +++++++++++++++--- 6 files changed, 316 insertions(+), 49 deletions(-) create mode 100644 controller_manager/doc/images/rqt_controller_manager.png rename rqt_controller_manager/resource/{controller_info.ui => popup_info.ui} (96%) diff --git a/controller_manager/doc/images/rqt_controller_manager.png b/controller_manager/doc/images/rqt_controller_manager.png new file mode 100644 index 0000000000000000000000000000000000000000..01c4f55bdf1021623377c893c95e6240d67b24ed GIT binary patch literal 49114 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 K+s`yT68m4dwC~dZ literal 0 HcmV?d00001 diff --git a/controller_manager/doc/userdoc.rst b/controller_manager/doc/userdoc.rst index f5e962047f..8f00bef4b9 100644 --- a/controller_manager/doc/userdoc.rst +++ b/controller_manager/doc/userdoc.rst @@ -148,6 +148,22 @@ There are two scripts to interact with controller manager from launch files: --configure Configures the given components. +rqt_controller_manager +---------------------- +A GUI tool to interact with the controller manager services to be able to switch the lifecycle states of the controllers as well as the hardware components. + +.. image:: images/rqt_controller_manager.png + +It can be launched independently using the following command or as rqt plugin. + +.. code-block:: console + + ros2 run rqt_controller_manager rqt_controller_manager + + * Double-click on a controller or hardware component to show the additional info. + * Right-click on a controller or hardware component to show a context menu with options for lifecycle management. + + Using the Controller Manager in a Process ----------------------------------------- diff --git a/rqt_controller_manager/package.xml b/rqt_controller_manager/package.xml index f1d1495286..2972336cbe 100644 --- a/rqt_controller_manager/package.xml +++ b/rqt_controller_manager/package.xml @@ -12,11 +12,12 @@ https://github.com/ros-controls/ros2_control/issues https://github.com/ros-controls/ros2_control + Adolfo Rodríguez Tsouroukdissian Bence Magyar + Christoph Froehlich Enrique Fernandez - Mathias Lüdtke Kelsey Hawkins - Adolfo Rodríguez Tsouroukdissian + Mathias Lüdtke controller_manager controller_manager_msgs diff --git a/rqt_controller_manager/resource/controller_manager.ui b/rqt_controller_manager/resource/controller_manager.ui index 2ede975248..17031d9044 100644 --- a/rqt_controller_manager/resource/controller_manager.ui +++ b/rqt_controller_manager/resource/controller_manager.ui @@ -37,7 +37,7 @@ - + 0 @@ -70,6 +70,40 @@ + + + + + 0 + 0 + + + + true + + + QAbstractItemView::NoSelection + + + QAbstractItemView::SelectRows + + + false + + + false + + + false + + + true + + + false + + + diff --git a/rqt_controller_manager/resource/controller_info.ui b/rqt_controller_manager/resource/popup_info.ui similarity index 96% rename from rqt_controller_manager/resource/controller_info.ui rename to rqt_controller_manager/resource/popup_info.ui index b8c03799a9..b910397a8d 100644 --- a/rqt_controller_manager/resource/controller_info.ui +++ b/rqt_controller_manager/resource/popup_info.ui @@ -16,9 +16,6 @@ 0 - - Controller Information - diff --git a/rqt_controller_manager/rqt_controller_manager/controller_manager.py b/rqt_controller_manager/rqt_controller_manager/controller_manager.py index d142ea847b..3b4a9a7de1 100644 --- a/rqt_controller_manager/rqt_controller_manager/controller_manager.py +++ b/rqt_controller_manager/rqt_controller_manager/controller_manager.py @@ -20,12 +20,16 @@ from controller_manager.controller_manager_services import ( configure_controller, list_controllers, + list_hardware_components, + set_hardware_component_state, load_controller, switch_controllers, unload_controller, ) + from controller_manager_msgs.msg import ControllerState from controller_manager_msgs.srv import SwitchController +from lifecycle_msgs.msg import State from python_qt_binding import loadUi from python_qt_binding.QtCore import QAbstractTableModel, Qt, QTimer from python_qt_binding.QtGui import QCursor, QFont, QIcon, QStandardItem, QStandardItemModel @@ -60,7 +64,7 @@ def __init__(self, context): # Pop-up that displays controller information self._popup_widget = QWidget() ui_file = os.path.join( - get_package_share_directory("rqt_controller_manager"), "resource", "controller_info.ui" + get_package_share_directory("rqt_controller_manager"), "resource", "popup_info.ui" ) loadUi(ui_file, self._popup_widget) self._popup_widget.setObjectName("ControllerInfoUi") @@ -78,7 +82,9 @@ def __init__(self, context): # Initialize members self._cm_name = "" # Name of the selected controller manager's node self._controllers = [] # State of each controller - self._table_model = None + self._hw_components = [] # State of each hw component + self._ctrl_table_model = None + self._hw_table_model = None # Store reference to node self._node = context.node @@ -93,16 +99,26 @@ def __init__(self, context): } # Controllers display - table_view = self._widget.table_view - table_view.setContextMenuPolicy(Qt.CustomContextMenu) - table_view.customContextMenuRequested.connect(self._on_ctrl_menu) - - table_view.doubleClicked.connect(self._on_ctrl_info) - - header = table_view.horizontalHeader() - header.setSectionResizeMode(QHeaderView.ResizeToContents) - header.setContextMenuPolicy(Qt.CustomContextMenu) - header.customContextMenuRequested.connect(self._on_header_menu) + ctrl_table_view = self._widget.ctrl_table_view + ctrl_table_view.setContextMenuPolicy(Qt.CustomContextMenu) + ctrl_table_view.customContextMenuRequested.connect(self._on_ctrl_menu) + ctrl_table_view.doubleClicked.connect(self._on_ctrl_info) + + ctrl_header = ctrl_table_view.horizontalHeader() + ctrl_header.setSectionResizeMode(QHeaderView.ResizeToContents) + ctrl_header.setContextMenuPolicy(Qt.CustomContextMenu) + ctrl_header.customContextMenuRequested.connect(self._on_ctrl_header_menu) + + # Hardware components display + hw_table_view = self._widget.hw_table_view + hw_table_view.setContextMenuPolicy(Qt.CustomContextMenu) + hw_table_view.customContextMenuRequested.connect(self._on_hw_menu) + hw_table_view.doubleClicked.connect(self._on_hw_info) + + hw_header = hw_table_view.horizontalHeader() + hw_header.setSectionResizeMode(QHeaderView.ResizeToContents) + hw_header.setContextMenuPolicy(Qt.CustomContextMenu) + hw_header.customContextMenuRequested.connect(self._on_hw_header_menu) # Timer for controller manager updates self._update_cm_list_timer = QTimer(self) @@ -116,6 +132,12 @@ def __init__(self, context): self._update_ctrl_list_timer.timeout.connect(self._update_controllers) self._update_ctrl_list_timer.start() + # Timer for running hw components updates + self._update_hw_components_list_timer = QTimer(self) + self._update_hw_components_list_timer.setInterval(int(1000.0 / self._cm_update_freq)) + self._update_hw_components_list_timer.timeout.connect(self._update_hw_components) + self._update_hw_components_list_timer.start() + # Signal connections w = self._widget w.cm_combo.currentIndexChanged[str].connect(self._on_cm_change) @@ -148,6 +170,7 @@ def _on_cm_change(self, cm_name): if cm_name: self._update_controllers() + self._update_hw_components() def _update_controllers(self): @@ -191,20 +214,20 @@ def _list_controllers(self): return [] def _show_controllers(self): - table_view = self._widget.table_view - self._table_model = ControllerTable(self._controllers, self._icons) - table_view.setModel(self._table_model) + ctrl_table_view = self._widget.ctrl_table_view + self._ctrl_table_model = ControllerTable(self._controllers, self._icons) + ctrl_table_view.setModel(self._ctrl_table_model) def _on_ctrl_menu(self, pos): # Get data of selected controller - row = self._widget.table_view.rowAt(pos.y()) + row = self._widget.ctrl_table_view.rowAt(pos.y()) if row < 0: return # Cursor is not under a valid item ctrl = self._controllers[row] # Show context menu - menu = QMenu(self._widget.table_view) + menu = QMenu(self._widget.ctrl_table_view) if ctrl.state == "active": action_deactivate = menu.addAction(self._icons["inactive"], "Deactivate") action_kill = menu.addAction(self._icons["finalized"], "Deactivate and Unload") @@ -220,7 +243,7 @@ def _on_ctrl_menu(self, pos): action_configure = menu.addAction(self._icons["inactive"], "Load and Configure") action_activate = menu.addAction(self._icons["active"], "Load, Configure and Activate") - action = menu.exec_(self._widget.table_view.mapToGlobal(pos)) + action = menu.exec_(self._widget.ctrl_table_view.mapToGlobal(pos)) # Evaluate user action if ctrl.state == "active": @@ -254,6 +277,7 @@ def _on_ctrl_menu(self, pos): def _on_ctrl_info(self, index): popup = self._popup_widget + popup.setWindowTitle("Controller Information") ctrl = self._controllers[index.row()] popup.ctrl_name.setText(ctrl.name) @@ -272,42 +296,186 @@ def _on_ctrl_info(self, index): popup.move(QCursor.pos()) popup.show() - def _on_header_menu(self, pos): - header = self._widget.table_view.horizontalHeader() + def _on_ctrl_header_menu(self, pos): + ctrl_header = self._widget.ctrl_table_view.horizontalHeader() + + # Show context menu + menu = QMenu(self._widget.ctrl_table_view) + action_toggle_auto_resize = menu.addAction("Toggle Auto-Resize") + action = menu.exec_(ctrl_header.mapToGlobal(pos)) + + # Evaluate user action + if action is action_toggle_auto_resize: + if ctrl_header.resizeMode(0) == QHeaderView.ResizeToContents: + ctrl_header.setSectionResizeMode(QHeaderView.Interactive) + else: + ctrl_header.setSectionResizeMode(QHeaderView.ResizeToContents) + + def _update_hw_components(self): + if not self._cm_name: + return + + # Find hw_components associated to the selected controller manager + hw_components = self._list_hw_components() + + # Update controller display, if necessary + if self._hw_components != hw_components: + self._hw_components = hw_components + self._show_hw_components() # NOTE: Model is recomputed from scratch + + def _list_hw_components(self): + """ + List the hw_components associated to a controller manager node. + + @return List of hw_components associated to a controller manager + node. Contains both stopped/running hw_components, as returned by + the C{list_hardware_components} service + @rtype [str] + """ + # Add loaded hw_components first + try: + hw_components = list_hardware_components( + self._node, self._cm_name, 2.0 / self._cm_update_freq + ).component + return hw_components + except RuntimeError as e: + print(e) + return [] + + def _show_hw_components(self): + hw_table_view = self._widget.hw_table_view + self._hw_table_model = HwComponentTable(self._hw_components, self._icons) + hw_table_view.setModel(self._hw_table_model) + + def _on_hw_menu(self, pos): + # Get data of selected controller + row = self._widget.hw_table_view.rowAt(pos.y()) + if row < 0: + return # Cursor is not under a valid item + + hw_component = self._hw_components[row] + + # Show context menu + menu = QMenu(self._widget.hw_table_view) + if hw_component.state.label == "active": + action_deactivate = menu.addAction(self._icons["inactive"], "Deactivate") + action_cleanup = menu.addAction(self._icons["finalized"], "Deactivate and Cleanup") + elif hw_component.state.label == "inactive": + action_activate = menu.addAction(self._icons["active"], "Activate") + action_cleanup = menu.addAction(self._icons["unconfigured"], "Cleanup") + elif hw_component.state.label == "unconfigured": + action_configure = menu.addAction(self._icons["inactive"], "Configure") + action_spawn = menu.addAction(self._icons["active"], "Configure and Activate") + + action = menu.exec_(self._widget.hw_table_view.mapToGlobal(pos)) + + # Evaluate user action + if hw_component.state.label == "active": + if action is action_deactivate: + self._set_inactive_hw_component(hw_component.name) + elif action is action_cleanup: + self._set_unconfigured_hw_component(hw_component.name) + elif hw_component.state.label == "inactive": + if action is action_activate: + self._set_active_hw_component(hw_component.name) + elif action is action_cleanup: + self._set_unconfigured_hw_component(hw_component.name) + elif hw_component.state.label == "unconfigured": + if action is action_configure: + self._set_inactive_hw_component(hw_component.name) + elif action is action_spawn: + self._set_active_hw_component(hw_component.name) + + def _on_hw_info(self, index): + popup = self._popup_widget + popup.setWindowTitle("Hardware Component Info") + + hw_component = self._hw_components[index.row()] + popup.ctrl_name.setText(hw_component.name) + popup.ctrl_type.setText(hw_component.type) + + res_model = QStandardItemModel() + model_root = QStandardItem("Command Interfaces") + res_model.appendRow(model_root) + for command_interface in hw_component.command_interfaces: + hw_iface_item = QStandardItem(command_interface.name) + model_root.appendRow(hw_iface_item) + + model_root = QStandardItem("State Interfaces") + res_model.appendRow(model_root) + for state_interface in hw_component.state_interfaces: + hw_iface_item = QStandardItem(state_interface.name) + model_root.appendRow(hw_iface_item) + + popup.resource_tree.setModel(res_model) + popup.resource_tree.setItemDelegate(FontDelegate(popup.resource_tree)) + popup.resource_tree.expandAll() + popup.move(QCursor.pos()) + popup.show() + + def _on_hw_header_menu(self, pos): + hw_header = self._widget.hw_table_view.horizontalHeader() # Show context menu - menu = QMenu(self._widget.table_view) + menu = QMenu(self._widget.hw_table_view) action_toggle_auto_resize = menu.addAction("Toggle Auto-Resize") - action = menu.exec_(header.mapToGlobal(pos)) + action = menu.exec_(hw_header.mapToGlobal(pos)) # Evaluate user action if action is action_toggle_auto_resize: - if header.resizeMode(0) == QHeaderView.ResizeToContents: - header.setSectionResizeMode(QHeaderView.Interactive) + if hw_header.resizeMode(0) == QHeaderView.ResizeToContents: + hw_header.setSectionResizeMode(QHeaderView.Interactive) else: - header.setSectionResizeMode(QHeaderView.ResizeToContents) + hw_header.setSectionResizeMode(QHeaderView.ResizeToContents) def _activate_controller(self, name): - switch_controllers( - node=self._node, - controller_manager_name=self._cm_name, - deactivate_controllers=[], - activate_controllers=[name], - strict=SwitchController.Request.STRICT, - activate_asap=False, - timeout=0.3, - ) + self._switch_controllers([name], []) def _deactivate_controller(self, name): - switch_controllers( - node=self._node, - controller_manager_name=self._cm_name, - deactivate_controllers=[name], - activate_controllers=[], - strict=SwitchController.Request.STRICT, - activate_asap=False, - timeout=0.3, - ) + self._switch_controllers([], [name]) + + def _switch_controllers(self, activate, deactivate): + try: + switch_controllers( + node=self._node, + controller_manager_name=self._cm_name, + activate_controllers=activate, + deactivate_controllers=deactivate, + strict=SwitchController.Request.STRICT, + activate_asap=False, + timeout=0.3, + ) + except Exception as e: + print(e) + + def _set_active_hw_component(self, name): + active_state = State() + active_state.id = State.PRIMARY_STATE_ACTIVE + active_state.label = "active" + self._set_state_hw_component(name, active_state) + + def _set_inactive_hw_component(self, name): + inactive_state = State() + inactive_state.id = State.PRIMARY_STATE_INACTIVE + inactive_state.label = "inactive" + self._set_state_hw_component(name, inactive_state) + + def _set_unconfigured_hw_component(self, name): + unconfigure_state = State() + unconfigure_state.id = State.PRIMARY_STATE_UNCONFIGURED + unconfigure_state.label = "unconfigured" + self._set_state_hw_component(name, unconfigure_state) + + def _set_state_hw_component(self, name, state): + try: + set_hardware_component_state( + node=self._node, + controller_manager_name=self._cm_name, + component_name=name, + lifecyle_state=state, + ) + except Exception as e: + print(e) class ControllerTable(QAbstractTableModel): @@ -361,6 +529,57 @@ def data(self, index, role): return Qt.AlignCenter +class HwComponentTable(QAbstractTableModel): + """ + Model containing hardware component information for tabular display. + + The model allows display of basic read-only information like component + name and state. + """ + + def __init__(self, hw_component_info, icons, parent=None): + QAbstractTableModel.__init__(self, parent) + self._data = hw_component_info + self._icons = icons + + def rowCount(self, parent): + return len(self._data) + + def columnCount(self, parent): + return 2 + + def headerData(self, col, orientation, role): + if orientation != Qt.Horizontal or role != Qt.DisplayRole: + return None + if col == 0: + return "component" + elif col == 1: + return "state" + + def data(self, index, role): + if not index.isValid(): + return None + + hw_component = self._data[index.row()] + + if role == Qt.DisplayRole: + if index.column() == 0: + return hw_component.name + elif index.column() == 1: + return hw_component.state.label or "not loaded" + + if role == Qt.DecorationRole and index.column() == 0: + return self._icons.get(hw_component.state.label) + + if role == Qt.FontRole and index.column() == 0: + bf = QFont() + bf.setBold(True) + return bf + + if role == Qt.TextAlignmentRole and index.column() == 1: + return Qt.AlignCenter + + class FontDelegate(QStyledItemDelegate): """ Simple delegate for customizing font weight and italization.