diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index aafd67c236..f5e9921f23 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -18,10 +18,3 @@ updates:
schedule:
interval: "weekly"
target-branch: "humble"
- - package-ecosystem: "github-actions"
- # Workflow files stored in the
- # default location of `.github/workflows`
- directory: "/"
- schedule:
- interval: "weekly"
- target-branch: "iron"
diff --git a/.github/workflows/README.md b/.github/workflows/README.md
index 62007ffc2d..2ad6a249fe 100644
--- a/.github/workflows/README.md
+++ b/.github/workflows/README.md
@@ -3,5 +3,4 @@ ROS2 Distro | Branch | Build status | Documentation | Released packages
:---------: | :----: | :----------: | :-----------: | :---------------:
**Rolling** | [`master`](https://github.com/ros-controls/ros2_control/tree/master) | [![Rolling Binary Build](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-binary-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-binary-build.yml?branch=master)
[![Rolling Semi-Binary Build](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-semi-binary-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-semi-binary-build.yml?branch=master)
[![Rolling Source Build](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-source-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-source-build.yml?branch=master)
[![Debian Rolling Source Build](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-debian-build.yml/badge.svg)](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-debian-build.yml)
[![RHEL Rolling Semi-Binary Build](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-rhel-binary-build.yml/badge.svg)](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-rhel-binary-build.yml) | [Documentation](https://control.ros.org/master/index.html)
[API Reference](https://control.ros.org/master/doc/api/index.html) | [ros2_control](https://index.ros.org/p/ros2_control/#rolling)
**Jazzy** | [`master`](https://github.com/ros-controls/ros2_control/tree/master) | [![Jazzy Binary Build](https://github.com/ros-controls/ros2_control/actions/workflows/jazzy-binary-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/ros2_control/actions/workflows/jazzy-binary-build.yml?branch=master)
[![Jazzy Semi-Binary Build](https://github.com/ros-controls/ros2_control/actions/workflows/jazzy-semi-binary-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/ros2_control/actions/workflows/jazzy-semi-binary-build.yml?branch=master)
[![Jazzy Source Build](https://github.com/ros-controls/ros2_control/actions/workflows/jazzy-source-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/ros2_control/actions/workflows/jazzy-source-build.yml?branch=master)
[![Debian Jazzy Source Build](https://github.com/ros-controls/ros2_control/actions/workflows/jazzy-debian-build.yml/badge.svg)](https://github.com/ros-controls/ros2_control/actions/workflows/jazzy-debian-build.yml)
[![RHEL Jazzy Semi-Binary Build](https://github.com/ros-controls/ros2_control/actions/workflows/jazzy-rhel-binary-build.yml/badge.svg)](https://github.com/ros-controls/ros2_control/actions/workflows/jazzy-rhel-binary-build.yml) | [Documentation](https://control.ros.org/jazzy/index.html)
[API Reference](https://control.ros.org/jazzy/doc/api/index.html) | [ros2_control](https://index.ros.org/p/ros2_control/#jazzy)
-**Iron** | [`iron`](https://github.com/ros-controls/ros2_control/tree/master) | [![Iron Binary Build](https://github.com/ros-controls/ros2_control/actions/workflows/iron-binary-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/ros2_control/actions/workflows/iron-binary-build.yml?branch=master)
[![Iron Semi-Binary Build](https://github.com/ros-controls/ros2_control/actions/workflows/iron-semi-binary-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/ros2_control/actions/workflows/iron-semi-binary-build.yml?branch=master)
[![Iron Source Build](https://github.com/ros-controls/ros2_control/actions/workflows/iron-source-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/ros2_control/actions/workflows/iron-source-build.yml?branch=master)
[![Debian Iron Source Build](https://github.com/ros-controls/ros2_control/actions/workflows/iron-debian-build.yml/badge.svg)](https://github.com/ros-controls/ros2_control/actions/workflows/iron-debian-build.yml)
[![RHEL Iron Semi-Binary Build](https://github.com/ros-controls/ros2_control/actions/workflows/iron-rhel-binary-build.yml/badge.svg)](https://github.com/ros-controls/ros2_control/actions/workflows/iron-rhel-binary-build.yml) | [Documentation](https://control.ros.org/iron/index.html)
[API Reference](https://control.ros.org/iron/doc/api/index.html) | [ros2_control](https://index.ros.org/p/ros2_control/#iron)
**Humble** | [`humble`](https://github.com/ros-controls/ros2_control/tree/humble) | [![Humble Binary Build](https://github.com/ros-controls/ros2_control/actions/workflows/humble-binary-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/ros2_control/actions/workflows/humble-binary-build.yml?branch=master)
[![Humble Semi-Binary Build](https://github.com/ros-controls/ros2_control/actions/workflows/humble-semi-binary-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/ros2_control/actions/workflows/humble-semi-binary-build.yml?branch=master)
[![Humble Source Build](https://github.com/ros-controls/ros2_control/actions/workflows/humble-source-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/ros2_control/actions/workflows/humble-source-build.yml?branch=master)
[![Debian Humble Source Build](https://github.com/ros-controls/ros2_control/actions/workflows/humble-debian-build.yml/badge.svg)](https://github.com/ros-controls/ros2_control/actions/workflows/humble-debian-build.yml)
[![RHEL Humble Semi-Binary Build](https://github.com/ros-controls/ros2_control/actions/workflows/humble-rhel-binary-build.yml/badge.svg)](https://github.com/ros-controls/ros2_control/actions/workflows/humble-rhel-binary-build.yml) | [Documentation](https://control.ros.org/humble/index.html)
[API Reference](https://control.ros.org/humble/doc/api/index.html) | [ros2_control](https://index.ros.org/p/ros2_control/#humble)
diff --git a/.github/workflows/iron-abi-compatibility.yml b/.github/workflows/iron-abi-compatibility.yml
deleted file mode 100644
index c2d9c19110..0000000000
--- a/.github/workflows/iron-abi-compatibility.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-name: Iron - ABI Compatibility Check
-on:
- workflow_dispatch:
- pull_request:
- branches:
- - iron
- paths:
- - '**.hpp'
- - '**.h'
- - '**.cpp'
- - '**.py'
- - '.github/workflows/iron-abi-compatibility.yml'
- - '**/package.xml'
- - '**/CMakeLists.txt'
- - 'ros2_control-not-released.iron.repos'
-
-jobs:
- abi_check:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: ros-industrial/industrial_ci@master
- env:
- ROS_DISTRO: iron
- ROS_REPO: testing
- ABICHECK_URL: github:${{ github.repository }}#${{ github.base_ref }}
- NOT_TEST_BUILD: true
diff --git a/.github/workflows/iron-binary-build.yml b/.github/workflows/iron-binary-build.yml
deleted file mode 100644
index ef90e256a0..0000000000
--- a/.github/workflows/iron-binary-build.yml
+++ /dev/null
@@ -1,47 +0,0 @@
-name: Iron Binary Build
-# author: Denis Štogl
-# description: 'Build & test all dependencies from released (binary) packages.'
-
-on:
- workflow_dispatch:
- pull_request:
- branches:
- - iron
- paths:
- - '**.hpp'
- - '**.h'
- - '**.cpp'
- - '**.py'
- - '.github/workflows/iron-binary-build.yml'
- - '**/package.xml'
- - '**/CMakeLists.txt'
- - 'ros2_control-not-released.iron.repos'
- push:
- branches:
- - iron
- paths:
- - '**.hpp'
- - '**.h'
- - '**.cpp'
- - '**.py'
- - '.github/workflows/iron-binary-build.yml'
- - '**/package.xml'
- - '**/CMakeLists.txt'
- - 'ros2_control-not-released.iron.repos'
- schedule:
- # Run every morning to detect flakiness and broken dependencies
- - cron: '03 1 * * *'
-
-jobs:
- binary:
- uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
- strategy:
- fail-fast: false
- matrix:
- ROS_DISTRO: [iron]
- ROS_REPO: [main, testing]
- with:
- ros_distro: ${{ matrix.ROS_DISTRO }}
- ros_repo: ${{ matrix.ROS_REPO }}
- upstream_workspace: ros2_control-not-released.iron.repos
- ref_for_scheduled_build: iron
diff --git a/.github/workflows/iron-check-docs.yml b/.github/workflows/iron-check-docs.yml
deleted file mode 100644
index e9295dad44..0000000000
--- a/.github/workflows/iron-check-docs.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-name: Iron Check Docs
-
-on:
- workflow_dispatch:
- pull_request:
- branches:
- - iron
- paths:
- - '**.rst'
- - '**.md'
- - '**.yaml'
-
-jobs:
- check-docs:
- name: Check Docs
- uses: ros-controls/control.ros.org/.github/workflows/reusable-sphinx-check-single-version.yml@iron
- with:
- ROS2_CONTROL_PR: ${{ github.ref }}
diff --git a/.github/workflows/iron-coverage-build.yml b/.github/workflows/iron-coverage-build.yml
deleted file mode 100644
index ff5be81d7d..0000000000
--- a/.github/workflows/iron-coverage-build.yml
+++ /dev/null
@@ -1,36 +0,0 @@
-name: Coverage Build - Iron
-on:
- workflow_dispatch:
- push:
- branches:
- - iron
- paths:
- - '**.hpp'
- - '**.h'
- - '**.cpp'
- - '**.py'
- - '.github/workflows/iron-coverage-build.yml'
- - '**/package.xml'
- - '**/CMakeLists.txt'
- - 'ros2_control.iron.repos'
- - 'codecov.yml'
- pull_request:
- branches:
- - iron
- paths:
- - '**.hpp'
- - '**.h'
- - '**.cpp'
- - '**.py'
- - '.github/workflows/iron-coverage-build.yml'
- - '**/package.xml'
- - '**/CMakeLists.txt'
- - 'ros2_control.iron.repos'
- - 'codecov.yml'
-
-jobs:
- coverage_iron:
- uses: ros-controls/ros2_control_ci/.github/workflows/reusable-build-coverage.yml@master
- secrets: inherit
- with:
- ros_distro: iron
diff --git a/.github/workflows/iron-debian-build.yml b/.github/workflows/iron-debian-build.yml
deleted file mode 100644
index 3cbe0c5127..0000000000
--- a/.github/workflows/iron-debian-build.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-name: Debian Iron Source Build
-on:
- workflow_dispatch:
- pull_request:
- branches:
- - iron
- paths:
- - '**.hpp'
- - '**.h'
- - '**.cpp'
- - '**.py'
- - '.github/workflows/iron-debian-build.yml'
- - '**/package.xml'
- - '**/CMakeLists.txt'
- - 'ros2_control.iron.repos'
- schedule:
- # Run every day to detect flakiness and broken dependencies
- - cron: '03 1 * * *'
-
-
-jobs:
- debian_source_build:
- uses: ros-controls/ros2_control_ci/.github/workflows/reusable-debian-build.yml@master
- strategy:
- fail-fast: false
- matrix:
- ROS_DISTRO: [iron]
- with:
- ros_distro: ${{ matrix.ROS_DISTRO }}
- upstream_workspace: ros2_control.${{ matrix.ROS_DISTRO }}.repos
- ref_for_scheduled_build: master
- skip_packages: rqt_controller_manager
- skip_packages_test: controller_manager_msgs
diff --git a/.github/workflows/iron-pre-commit.yml b/.github/workflows/iron-pre-commit.yml
deleted file mode 100644
index a128958031..0000000000
--- a/.github/workflows/iron-pre-commit.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-name: Pre-Commit - Iron
-
-on:
- workflow_dispatch:
- pull_request:
- branches:
- - iron
-
-jobs:
- pre-commit:
- uses: ros-controls/ros2_control_ci/.github/workflows/reusable-pre-commit.yml@master
- with:
- ros_distro: iron
diff --git a/.github/workflows/iron-rhel-binary-build.yml b/.github/workflows/iron-rhel-binary-build.yml
deleted file mode 100644
index f308c495f3..0000000000
--- a/.github/workflows/iron-rhel-binary-build.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-name: RHEL Iron Semi-Binary Build
-on:
- workflow_dispatch:
- pull_request:
- branches:
- - iron
- paths:
- - '**.hpp'
- - '**.h'
- - '**.cpp'
- - '**.py'
- - '.github/workflows/iron-rhel-binary-build.yml'
- - '**/package.xml'
- - '**/CMakeLists.txt'
- - 'ros2_control.iron.repos'
- schedule:
- # Run every day to detect flakiness and broken dependencies
- - cron: '03 1 * * *'
-
-jobs:
- rhel_semi_binary_build:
- uses: ros-controls/ros2_control_ci/.github/workflows/reusable-rhel-binary-build.yml@master
- strategy:
- fail-fast: false
- matrix:
- ROS_DISTRO: [iron]
- with:
- ros_distro: ${{ matrix.ROS_DISTRO }}
- upstream_workspace: ros2_control.${{ matrix.ROS_DISTRO }}.repos
- ref_for_scheduled_build: iron
- skip_packages: rqt_controller_manager
diff --git a/.github/workflows/iron-semi-binary-build.yml b/.github/workflows/iron-semi-binary-build.yml
deleted file mode 100644
index c2d137bde0..0000000000
--- a/.github/workflows/iron-semi-binary-build.yml
+++ /dev/null
@@ -1,47 +0,0 @@
-name: Iron Semi-Binary Build
-# author: Denis Štogl
-# description: 'Build & test all dependencies from released (binary) packages.'
-
-on:
- workflow_dispatch:
- pull_request:
- branches:
- - iron
- paths:
- - '**.hpp'
- - '**.h'
- - '**.cpp'
- - '**.py'
- - '.github/workflows/iron-semi-binary-build.yml'
- - '**/package.xml'
- - '**/CMakeLists.txt'
- - 'ros2_control.iron.repos'
- push:
- branches:
- - iron
- paths:
- - '**.hpp'
- - '**.h'
- - '**.cpp'
- - '**.py'
- - '.github/workflows/iron-semi-binary-build.yml'
- - '**/package.xml'
- - '**/CMakeLists.txt'
- - 'ros2_control.iron.repos'
- schedule:
- # Run every morning to detect flakiness and broken dependencies
- - cron: '03 1 * * *'
-
-jobs:
- binary:
- uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
- strategy:
- fail-fast: false
- matrix:
- ROS_DISTRO: [iron]
- ROS_REPO: [testing]
- with:
- ros_distro: ${{ matrix.ROS_DISTRO }}
- ros_repo: ${{ matrix.ROS_REPO }}
- upstream_workspace: ros2_control.iron.repos
- ref_for_scheduled_build: iron
diff --git a/.github/workflows/iron-source-build.yml b/.github/workflows/iron-source-build.yml
deleted file mode 100644
index 3b7c53f6ff..0000000000
--- a/.github/workflows/iron-source-build.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-name: Iron Source Build
-on:
- workflow_dispatch:
- push:
- branches:
- - iron
- paths:
- - '**.hpp'
- - '**.h'
- - '**.cpp'
- - '**.py'
- - '.github/workflows/iron-source-build.yml'
- - '**/package.xml'
- - '**/CMakeLists.txt'
- - 'ros2_control.iron.repos'
- schedule:
- # Run every day to detect flakiness and broken dependencies
- - cron: '03 3 * * *'
-
-jobs:
- source:
- uses: ros-controls/ros2_control_ci/.github/workflows/reusable-ros-tooling-source-build.yml@master
- with:
- ros_distro: iron
- ref: iron
- ros2_repo_branch: iron
- os_name: ubuntu-22.04
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 205e0f63ab..75c5402ffe 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -63,7 +63,7 @@ repos:
# CPP hooks
- repo: https://github.com/pre-commit/mirrors-clang-format
- rev: v19.1.3
+ rev: v19.1.4
hooks:
- id: clang-format
args: ['-fallback-style=none', '-i']
@@ -133,7 +133,7 @@ repos:
exclude: CHANGELOG\.rst|\.(svg|pyc|drawio)$
- repo: https://github.com/python-jsonschema/check-jsonschema
- rev: 0.29.4
+ rev: 0.30.0
hooks:
- id: check-github-workflows
args: ["--verbose"]
diff --git a/README.md b/README.md
index 40d2a3c189..47c6ad1523 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,6 @@ ROS2 Distro | Branch | Build status | Documentation | Released packages
:---------: | :----: | :----------: | :-----------: | :---------------:
**Rolling** | [`master`](https://github.com/ros-controls/ros2_control/tree/master) | [![Rolling Binary Build](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-binary-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-binary-build.yml?branch=master)
[![Rolling Semi-Binary Build](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-semi-binary-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-semi-binary-build.yml?branch=master) | [Documentation](https://control.ros.org/master/index.html)
[API Reference](https://control.ros.org/master/doc/api/index.html) | [ros2_control](https://index.ros.org/p/ros2_control/#rolling)
**Jazzy** | [`master`](https://github.com/ros-controls/ros2_control/tree/master) | [![Rolling Binary Build](https://github.com/ros-controls/ros2_control/actions/workflows/jazzy-binary-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/ros2_control/actions/workflows/jazzy-binary-build.yml?branch=master)
[![Rolling Semi-Binary Build](https://github.com/ros-controls/ros2_control/actions/workflows/jazzy-semi-binary-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/ros2_control/actions/workflows/jazzy-semi-binary-build.yml?branch=master) | [Documentation](https://control.ros.org/jazzy/index.html)
[API Reference](https://control.ros.org/jazzy/doc/api/index.html) | [ros2_control](https://index.ros.org/p/ros2_control/#jazzy)
-**Iron** | [`iron`](https://github.com/ros-controls/ros2_control/tree/iron) | [![Iron Binary Build](https://github.com/ros-controls/ros2_control/actions/workflows/iron-binary-build.yml/badge.svg?branch=iron)](https://github.com/ros-controls/ros2_control/actions/workflows/iron-binary-build.yml?branch=iron)
[![Iron Semi-Binary Build](https://github.com/ros-controls/ros2_control/actions/workflows/iron-semi-binary-build.yml/badge.svg?branch=iron)](https://github.com/ros-controls/ros2_control/actions/workflows/iron-semi-binary-build.yml?branch=iron) | [Documentation](https://control.ros.org/iron/index.html)
[API Reference](https://control.ros.org/iron/doc/api/index.html) | [ros2_control](https://index.ros.org/p/ros2_control/#iron)
**Humble** | [`humble`](https://github.com/ros-controls/ros2_control/tree/humble) | [![Humble Binary Build](https://github.com/ros-controls/ros2_control/actions/workflows/humble-binary-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/ros2_control/actions/workflows/humble-binary-build.yml?branch=master)
[![Humble Semi-Binary Build](https://github.com/ros-controls/ros2_control/actions/workflows/humble-semi-binary-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/ros2_control/actions/workflows/humble-semi-binary-build.yml?branch=master) | [Documentation](https://control.ros.org/humble/index.html)
[API Reference](https://control.ros.org/humble/doc/api/index.html) | [ros2_control](https://index.ros.org/p/ros2_control/#humble)
[Detailed build status](.github/workflows/README.md)
diff --git a/controller_manager/CMakeLists.txt b/controller_manager/CMakeLists.txt
index 5035f17ec0..dba9531e45 100644
--- a/controller_manager/CMakeLists.txt
+++ b/controller_manager/CMakeLists.txt
@@ -219,6 +219,9 @@ if(BUILD_TESTING)
install(FILES test/test_controller_spawner_with_type.yaml
DESTINATION test)
+ install(FILES test/test_controller_overriding_parameters.yaml
+ DESTINATION test)
+
ament_add_gmock(test_hardware_management_srvs
test/test_hardware_management_srvs.cpp
)
diff --git a/controller_manager/controller_manager/__init__.py b/controller_manager/controller_manager/__init__.py
index 4a8d7daee5..638a28ce86 100644
--- a/controller_manager/controller_manager/__init__.py
+++ b/controller_manager/controller_manager/__init__.py
@@ -23,9 +23,9 @@
set_hardware_component_state,
switch_controllers,
unload_controller,
- get_parameter_from_param_file,
+ get_parameter_from_param_files,
set_controller_parameters,
- set_controller_parameters_from_param_file,
+ set_controller_parameters_from_param_files,
bcolors,
)
@@ -40,8 +40,8 @@
"set_hardware_component_state",
"switch_controllers",
"unload_controller",
- "get_parameter_from_param_file",
+ "get_parameter_from_param_files",
"set_controller_parameters",
- "set_controller_parameters_from_param_file",
+ "set_controller_parameters_from_param_files",
"bcolors",
]
diff --git a/controller_manager/controller_manager/controller_manager_services.py b/controller_manager/controller_manager/controller_manager_services.py
index 909e681ce6..16dee623e7 100644
--- a/controller_manager/controller_manager/controller_manager_services.py
+++ b/controller_manager/controller_manager/controller_manager_services.py
@@ -123,7 +123,9 @@ def service_caller(
)
-def configure_controller(node, controller_manager_name, controller_name, service_timeout=0.0):
+def configure_controller(
+ node, controller_manager_name, controller_name, service_timeout=0.0, call_timeout=10.0
+):
request = ConfigureController.Request()
request.name = controller_name
return service_caller(
@@ -132,10 +134,11 @@ def configure_controller(node, controller_manager_name, controller_name, service
ConfigureController,
request,
service_timeout,
+ call_timeout,
)
-def list_controllers(node, controller_manager_name, service_timeout=0.0):
+def list_controllers(node, controller_manager_name, service_timeout=0.0, call_timeout=10.0):
request = ListControllers.Request()
return service_caller(
node,
@@ -143,10 +146,11 @@ def list_controllers(node, controller_manager_name, service_timeout=0.0):
ListControllers,
request,
service_timeout,
+ call_timeout,
)
-def list_controller_types(node, controller_manager_name, service_timeout=0.0):
+def list_controller_types(node, controller_manager_name, service_timeout=0.0, call_timeout=10.0):
request = ListControllerTypes.Request()
return service_caller(
node,
@@ -154,10 +158,13 @@ def list_controller_types(node, controller_manager_name, service_timeout=0.0):
ListControllerTypes,
request,
service_timeout,
+ call_timeout,
)
-def list_hardware_components(node, controller_manager_name, service_timeout=0.0):
+def list_hardware_components(
+ node, controller_manager_name, service_timeout=0.0, call_timeout=10.0
+):
request = ListHardwareComponents.Request()
return service_caller(
node,
@@ -165,10 +172,13 @@ def list_hardware_components(node, controller_manager_name, service_timeout=0.0)
ListHardwareComponents,
request,
service_timeout,
+ call_timeout,
)
-def list_hardware_interfaces(node, controller_manager_name, service_timeout=0.0):
+def list_hardware_interfaces(
+ node, controller_manager_name, service_timeout=0.0, call_timeout=10.0
+):
request = ListHardwareInterfaces.Request()
return service_caller(
node,
@@ -176,10 +186,13 @@ def list_hardware_interfaces(node, controller_manager_name, service_timeout=0.0)
ListHardwareInterfaces,
request,
service_timeout,
+ call_timeout,
)
-def load_controller(node, controller_manager_name, controller_name, service_timeout=0.0):
+def load_controller(
+ node, controller_manager_name, controller_name, service_timeout=0.0, call_timeout=10.0
+):
request = LoadController.Request()
request.name = controller_name
return service_caller(
@@ -188,10 +201,13 @@ def load_controller(node, controller_manager_name, controller_name, service_time
LoadController,
request,
service_timeout,
+ call_timeout,
)
-def reload_controller_libraries(node, controller_manager_name, force_kill, service_timeout=0.0):
+def reload_controller_libraries(
+ node, controller_manager_name, force_kill, service_timeout=0.0, call_timeout=10.0
+):
request = ReloadControllerLibraries.Request()
request.force_kill = force_kill
return service_caller(
@@ -200,11 +216,17 @@ def reload_controller_libraries(node, controller_manager_name, force_kill, servi
ReloadControllerLibraries,
request,
service_timeout,
+ call_timeout,
)
def set_hardware_component_state(
- node, controller_manager_name, component_name, lifecyle_state, service_timeout=0.0
+ node,
+ controller_manager_name,
+ component_name,
+ lifecyle_state,
+ service_timeout=0.0,
+ call_timeout=10.0,
):
request = SetHardwareComponentState.Request()
request.name = component_name
@@ -215,6 +237,7 @@ def set_hardware_component_state(
SetHardwareComponentState,
request,
service_timeout,
+ call_timeout,
)
@@ -226,6 +249,7 @@ def switch_controllers(
strict,
activate_asap,
timeout,
+ call_timeout=10.0,
):
request = SwitchController.Request()
request.activate_controllers = activate_controllers
@@ -237,11 +261,17 @@ def switch_controllers(
request.activate_asap = activate_asap
request.timeout = rclpy.duration.Duration(seconds=timeout).to_msg()
return service_caller(
- node, f"{controller_manager_name}/switch_controller", SwitchController, request
+ node,
+ f"{controller_manager_name}/switch_controller",
+ SwitchController,
+ request,
+ call_timeout=call_timeout,
)
-def unload_controller(node, controller_manager_name, controller_name, service_timeout=0.0):
+def unload_controller(
+ node, controller_manager_name, controller_name, service_timeout=0.0, call_timeout=10.0
+):
request = UnloadController.Request()
request.name = controller_name
return service_caller(
@@ -250,60 +280,94 @@ def unload_controller(node, controller_manager_name, controller_name, service_ti
UnloadController,
request,
service_timeout,
+ call_timeout,
)
-def get_parameter_from_param_file(
- node, controller_name, namespace, parameter_file, parameter_name
+def get_params_files_with_controller_parameters(
+ node, controller_name: str, namespace: str, parameter_files: list
):
- with open(parameter_file) as f:
- namespaced_controller = (
- f"/{controller_name}" if namespace == "/" else f"{namespace}/{controller_name}"
- )
- WILDCARD_KEY = "/**"
- ROS_PARAMS_KEY = "ros__parameters"
- parameters = yaml.safe_load(f)
- controller_param_dict = None
- # check for the parameter in 'controller_name' or 'namespaced_controller' or '/**/namespaced_controller' or '/**/controller_name'
- for key in [
- controller_name,
- namespaced_controller,
- f"{WILDCARD_KEY}/{controller_name}",
- f"{WILDCARD_KEY}{namespaced_controller}",
- ]:
- if key in parameters:
- if key == controller_name and namespace != "/":
- node.get_logger().fatal(
- f"{bcolors.FAIL}Missing namespace : {namespace} or wildcard in parameter file for controller : {controller_name}{bcolors.ENDC}"
+ controller_parameter_files = []
+ for parameter_file in parameter_files:
+ if parameter_file in controller_parameter_files:
+ continue
+ with open(parameter_file) as f:
+ namespaced_controller = (
+ f"/{controller_name}" if namespace == "/" else f"{namespace}/{controller_name}"
+ )
+ WILDCARD_KEY = "/**"
+ parameters = yaml.safe_load(f)
+ # check for the parameter in 'controller_name' or 'namespaced_controller' or '/**/namespaced_controller' or '/**/controller_name'
+ for key in [
+ controller_name,
+ namespaced_controller,
+ f"{WILDCARD_KEY}/{controller_name}",
+ f"{WILDCARD_KEY}{namespaced_controller}",
+ ]:
+ if key in parameters:
+ if key == controller_name and namespace != "/":
+ node.get_logger().fatal(
+ f"{bcolors.FAIL}Missing namespace : {namespace} or wildcard in parameter file for controller : {controller_name}{bcolors.ENDC}"
+ )
+ break
+ controller_parameter_files.append(parameter_file)
+
+ if WILDCARD_KEY in parameters and key in parameters[WILDCARD_KEY]:
+ controller_parameter_files.append(parameter_file)
+ return controller_parameter_files
+
+
+def get_parameter_from_param_files(
+ node, controller_name: str, namespace: str, parameter_files: list, parameter_name: str
+):
+ for parameter_file in parameter_files:
+ with open(parameter_file) as f:
+ namespaced_controller = (
+ f"/{controller_name}" if namespace == "/" else f"{namespace}/{controller_name}"
+ )
+ WILDCARD_KEY = "/**"
+ ROS_PARAMS_KEY = "ros__parameters"
+ parameters = yaml.safe_load(f)
+ controller_param_dict = None
+ # check for the parameter in 'controller_name' or 'namespaced_controller' or '/**/namespaced_controller' or '/**/controller_name'
+ for key in [
+ controller_name,
+ namespaced_controller,
+ f"{WILDCARD_KEY}/{controller_name}",
+ f"{WILDCARD_KEY}{namespaced_controller}",
+ ]:
+ if key in parameters:
+ if key == controller_name and namespace != "/":
+ node.get_logger().fatal(
+ f"{bcolors.FAIL}Missing namespace : {namespace} or wildcard in parameter file for controller : {controller_name}{bcolors.ENDC}"
+ )
+ break
+ controller_param_dict = parameters[key]
+
+ if WILDCARD_KEY in parameters and key in parameters[WILDCARD_KEY]:
+ controller_param_dict = parameters[WILDCARD_KEY][key]
+
+ if controller_param_dict and (
+ not isinstance(controller_param_dict, dict)
+ or ROS_PARAMS_KEY not in controller_param_dict
+ ):
+ raise RuntimeError(
+ f"YAML file : {parameter_file} is not a valid ROS parameter file for controller node : {namespaced_controller}"
)
+ if (
+ controller_param_dict
+ and ROS_PARAMS_KEY in controller_param_dict
+ and parameter_name in controller_param_dict[ROS_PARAMS_KEY]
+ ):
break
- controller_param_dict = parameters[key]
-
- if WILDCARD_KEY in parameters and key in parameters[WILDCARD_KEY]:
- controller_param_dict = parameters[WILDCARD_KEY][key]
-
- if controller_param_dict and (
- not isinstance(controller_param_dict, dict)
- or ROS_PARAMS_KEY not in controller_param_dict
- ):
- raise RuntimeError(
- f"YAML file : {parameter_file} is not a valid ROS parameter file for controller node : {namespaced_controller}"
- )
- if (
- controller_param_dict
- and ROS_PARAMS_KEY in controller_param_dict
- and parameter_name in controller_param_dict[ROS_PARAMS_KEY]
- ):
- break
-
- if controller_param_dict is None:
- node.get_logger().fatal(
- f"{bcolors.FAIL}Controller : {namespaced_controller} parameters not found in parameter file : {parameter_file}{bcolors.ENDC}"
- )
- if parameter_name in controller_param_dict[ROS_PARAMS_KEY]:
- return controller_param_dict[ROS_PARAMS_KEY][parameter_name]
- return None
+ if controller_param_dict and parameter_name in controller_param_dict[ROS_PARAMS_KEY]:
+ return controller_param_dict[ROS_PARAMS_KEY][parameter_name]
+ if controller_param_dict is None:
+ node.get_logger().fatal(
+ f"{bcolors.FAIL}Controller : {namespaced_controller} parameters not found in parameter files : {parameter_files}{bcolors.ENDC}"
+ )
+ return None
def set_controller_parameters(
@@ -347,26 +411,36 @@ def set_controller_parameters(
return True
-def set_controller_parameters_from_param_file(
- node, controller_manager_name, controller_name, parameter_file, namespace=None
+def set_controller_parameters_from_param_files(
+ node, controller_manager_name: str, controller_name: str, parameter_files: list, namespace=None
):
- if parameter_file:
- spawner_namespace = namespace if namespace else node.get_namespace()
+ spawner_namespace = namespace if namespace else node.get_namespace()
+ controller_parameter_files = get_params_files_with_controller_parameters(
+ node, controller_name, spawner_namespace, parameter_files
+ )
+ if controller_parameter_files:
set_controller_parameters(
- node, controller_manager_name, controller_name, "params_file", parameter_file
+ node,
+ controller_manager_name,
+ controller_name,
+ "params_file",
+ controller_parameter_files,
)
- controller_type = get_parameter_from_param_file(
- node, controller_name, spawner_namespace, parameter_file, "type"
+ controller_type = get_parameter_from_param_files(
+ node, controller_name, spawner_namespace, controller_parameter_files, "type"
)
- if controller_type:
- if not set_controller_parameters(
- node, controller_manager_name, controller_name, "type", controller_type
- ):
- return False
+ if controller_type and not set_controller_parameters(
+ node, controller_manager_name, controller_name, "type", controller_type
+ ):
+ return False
- fallback_controllers = get_parameter_from_param_file(
- node, controller_name, spawner_namespace, parameter_file, "fallback_controllers"
+ fallback_controllers = get_parameter_from_param_files(
+ node,
+ controller_name,
+ spawner_namespace,
+ controller_parameter_files,
+ "fallback_controllers",
)
if fallback_controllers:
if not set_controller_parameters(
diff --git a/controller_manager/controller_manager/launch_utils.py b/controller_manager/controller_manager/launch_utils.py
index c64b893156..e10096b675 100644
--- a/controller_manager/controller_manager/launch_utils.py
+++ b/controller_manager/controller_manager/launch_utils.py
@@ -20,7 +20,7 @@
def generate_controllers_spawner_launch_description(
- controller_names: list, controller_params_file=None, extra_spawner_args=[]
+ controller_names: list, controller_params_files=None, extra_spawner_args=[]
):
"""
Generate launch description for loading a controller using spawner.
@@ -37,8 +37,8 @@ def generate_controllers_spawner_launch_description(
# Passing controller parameter file to load the controller (Controller type is retrieved from config file)
generate_controllers_spawner_launch_description(
['joint_state_broadcaster'],
- controller_params_file=os.path.join(get_package_share_directory('my_pkg'),
- 'config', 'controller_params.yaml'),
+ controller_params_files=[os.path.join(get_package_share_directory('my_pkg'),
+ 'config', 'controller_params.yaml')],
extra_spawner_args=[--load-only]
)
@@ -62,8 +62,10 @@ def generate_controllers_spawner_launch_description(
]
)
- if controller_params_file:
- spawner_arguments += ["--param-file", controller_params_file]
+ if controller_params_files:
+ for controller_params_file in controller_params_files:
+ if controller_params_file:
+ spawner_arguments += ["--param-file", controller_params_file]
# Setting --unload-on-kill if launch arg unload_on_kill is "true"
# See https://github.com/ros2/launch/issues/290
@@ -98,11 +100,51 @@ def generate_controllers_spawner_launch_description(
)
+def generate_controllers_spawner_launch_description_from_dict(
+ controller_info_dict: dict, extra_spawner_args=[]
+):
+ """
+ Generate launch description for loading a controller using spawner.
+
+ controller_info_dict: dict
+ A dictionary with the following info:
+ - controller_name: str
+ The name of the controller to load as the key
+ - controller_params_file: str or list or None
+ The path to the controller parameter file or a list of paths to multiple parameter files
+ or None if no parameter file is needed as the value of the key
+ If a list is passed, the controller parameters will be overloaded in same order
+ extra_spawner_args: list
+ A list of extra arguments to pass to the controller spawner
+ """
+ if not type(controller_info_dict) is dict:
+ raise ValueError(f"Invalid controller_info_dict type parsed {controller_info_dict}")
+ controller_names = controller_info_dict.keys()
+ controller_params_files = []
+ for controller_name in controller_names:
+ controller_params_file = controller_info_dict[controller_name]
+ if controller_params_file:
+ if type(controller_params_file) is list:
+ controller_params_files.extend(controller_params_file)
+ elif type(controller_params_file) is str:
+ controller_params_files.append(controller_params_file)
+ else:
+ raise ValueError(
+ f"Invalid controller_params_file type parsed in the dict {controller_params_file}"
+ )
+ return generate_controllers_spawner_launch_description(
+ controller_names=controller_names,
+ controller_params_files=controller_params_files,
+ extra_spawner_args=extra_spawner_args,
+ )
+
+
def generate_load_controller_launch_description(
controller_name: str, controller_params_file=None, extra_spawner_args=[]
):
+ controller_params_files = [controller_params_file] if controller_params_file else None
return generate_controllers_spawner_launch_description(
controller_names=[controller_name],
- controller_params_file=controller_params_file,
+ controller_params_file=controller_params_files,
extra_spawner_args=extra_spawner_args,
)
diff --git a/controller_manager/controller_manager/spawner.py b/controller_manager/controller_manager/spawner.py
index d6df3be01f..c5a23defe4 100644
--- a/controller_manager/controller_manager/spawner.py
+++ b/controller_manager/controller_manager/spawner.py
@@ -26,7 +26,7 @@
load_controller,
switch_controllers,
unload_controller,
- set_controller_parameters_from_param_file,
+ set_controller_parameters_from_param_files,
bcolors,
)
from controller_manager.controller_manager_services import ServiceNotFoundError
@@ -60,8 +60,12 @@ def has_service_names(node, node_name, node_namespace, service_names):
return all(service in client_names for service in service_names)
-def is_controller_loaded(node, controller_manager, controller_name, service_timeout=0.0):
- controllers = list_controllers(node, controller_manager, service_timeout).controller
+def is_controller_loaded(
+ node, controller_manager, controller_name, service_timeout=0.0, call_timeout=10.0
+):
+ controllers = list_controllers(
+ node, controller_manager, service_timeout, call_timeout
+ ).controller
return any(c.name == controller_name for c in controllers)
@@ -79,8 +83,11 @@ def main(args=None):
parser.add_argument(
"-p",
"--param-file",
- help="Controller param file to be loaded into controller node before configure",
+ help="Controller param file to be loaded into controller node before configure. "
+ "Pass multiple times to load different files for different controllers or to "
+ "override the parameters of the same controller.",
default=None,
+ action="append",
required=False,
)
parser.add_argument(
@@ -110,9 +117,25 @@ def main(args=None):
)
parser.add_argument(
"--controller-manager-timeout",
- help="Time to wait for the controller manager",
+ help="Time to wait for the controller manager service to be available",
required=False,
- default=0,
+ default=0.0,
+ type=float,
+ )
+ parser.add_argument(
+ "--switch-timeout",
+ help="Time to wait for a successful state switch of controllers."
+ " Useful when switching cannot be performed immediately, e.g.,"
+ " paused simulations at startup",
+ required=False,
+ default=5.0,
+ type=float,
+ )
+ parser.add_argument(
+ "--service-call-timeout",
+ help="Time to wait for the service response from the controller manager",
+ required=False,
+ default=10.0,
type=float,
)
parser.add_argument(
@@ -127,11 +150,15 @@ def main(args=None):
args = parser.parse_args(command_line_args)
controller_names = args.controller_names
controller_manager_name = args.controller_manager
- param_file = args.param_file
+ param_files = args.param_file
controller_manager_timeout = args.controller_manager_timeout
+ service_call_timeout = args.service_call_timeout
+ switch_timeout = args.switch_timeout
- if param_file and not os.path.isfile(param_file):
- raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), param_file)
+ if param_files:
+ for param_file in param_files:
+ if not os.path.isfile(param_file):
+ raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), param_file)
node = Node("spawner_" + controller_names[0])
@@ -164,7 +191,11 @@ def main(args=None):
for controller_name in controller_names:
if is_controller_loaded(
- node, controller_manager_name, controller_name, controller_manager_timeout
+ node,
+ controller_manager_name,
+ controller_name,
+ controller_manager_timeout,
+ service_call_timeout,
):
node.get_logger().warn(
bcolors.WARNING
@@ -172,12 +203,12 @@ def main(args=None):
+ bcolors.ENDC
)
else:
- if param_file:
- if not set_controller_parameters_from_param_file(
+ if param_files:
+ if not set_controller_parameters_from_param_files(
node,
controller_manager_name,
controller_name,
- param_file,
+ param_files,
spawner_namespace,
):
return 1
@@ -197,7 +228,13 @@ def main(args=None):
)
if not args.load_only:
- ret = configure_controller(node, controller_manager_name, controller_name)
+ ret = configure_controller(
+ node,
+ controller_manager_name,
+ controller_name,
+ controller_manager_timeout,
+ service_call_timeout,
+ )
if not ret.ok:
node.get_logger().error(
bcolors.FAIL + "Failed to configure controller" + bcolors.ENDC
@@ -206,7 +243,14 @@ def main(args=None):
if not args.inactive and not args.activate_as_group:
ret = switch_controllers(
- node, controller_manager_name, [], [controller_name], True, True, 5.0
+ node,
+ controller_manager_name,
+ [],
+ [controller_name],
+ True,
+ True,
+ switch_timeout,
+ service_call_timeout,
)
if not ret.ok:
node.get_logger().error(
@@ -224,7 +268,14 @@ def main(args=None):
if not args.inactive and args.activate_as_group:
ret = switch_controllers(
- node, controller_manager_name, [], controller_names, True, True, 5.0
+ node,
+ controller_manager_name,
+ [],
+ controller_names,
+ True,
+ True,
+ switch_timeout,
+ service_call_timeout,
)
if not ret.ok:
node.get_logger().error(
@@ -250,7 +301,14 @@ def main(args=None):
node.get_logger().info("Interrupt captured, deactivating and unloading controller")
# TODO(saikishor) we might have an issue in future, if any of these controllers is in chained mode
ret = switch_controllers(
- node, controller_manager_name, controller_names, [], True, True, 5.0
+ node,
+ controller_manager_name,
+ controller_names,
+ [],
+ True,
+ True,
+ switch_timeout,
+ service_call_timeout,
)
if not ret.ok:
node.get_logger().error(
diff --git a/controller_manager/controller_manager/unspawner.py b/controller_manager/controller_manager/unspawner.py
index e42d85aee9..9e380f5086 100644
--- a/controller_manager/controller_manager/unspawner.py
+++ b/controller_manager/controller_manager/unspawner.py
@@ -36,17 +36,33 @@ def main(args=None):
default="/controller_manager",
required=False,
)
+ parser.add_argument(
+ "--switch-timeout",
+ help="Time to wait for a successful state switch of controllers."
+ " Useful when switching cannot be performed immediately, e.g.,"
+ " paused simulations at startup",
+ required=False,
+ default=5.0,
+ type=float,
+ )
command_line_args = rclpy.utilities.remove_ros_args(args=sys.argv)[1:]
args = parser.parse_args(command_line_args)
controller_names = args.controller_names
controller_manager_name = args.controller_manager
+ switch_timeout = args.switch_timeout
node = Node("unspawner_" + controller_names[0])
try:
# Ignore returncode, because message is already printed and we'll try to unload anyway
ret = switch_controllers(
- node, controller_manager_name, controller_names, [], True, True, 5.0
+ node,
+ controller_manager_name,
+ controller_names,
+ [],
+ True,
+ True,
+ switch_timeout,
)
node.get_logger().info("Deactivated controller")
diff --git a/controller_manager/doc/userdoc.rst b/controller_manager/doc/userdoc.rst
index 9285d3ecd8..872a695d0f 100644
--- a/controller_manager/doc/userdoc.rst
+++ b/controller_manager/doc/userdoc.rst
@@ -141,9 +141,9 @@ There are two scripts to interact with controller manager from launch files:
.. code-block:: console
$ ros2 run controller_manager spawner -h
- usage: spawner [-h] [-c CONTROLLER_MANAGER] [-p PARAM_FILE] [-n NAMESPACE] [--load-only] [--inactive] [-t CONTROLLER_TYPE] [-u]
- [--controller-manager-timeout CONTROLLER_MANAGER_TIMEOUT]
- controller_name
+ usage: spawner [-h] [-c CONTROLLER_MANAGER] [-p PARAM_FILE] [-n NAMESPACE] [--load-only] [--inactive] [-u] [--controller-manager-timeout CONTROLLER_MANAGER_TIMEOUT]
+ [--switch-timeout SWITCH_TIMEOUT] [--activate-as-group] [--service-call-timeout SERVICE_CALL_TIMEOUT]
+ controller_names [controller_names ...]
positional arguments:
controller_names List of controllers
@@ -153,18 +153,20 @@ There are two scripts to interact with controller manager from launch files:
-c CONTROLLER_MANAGER, --controller-manager CONTROLLER_MANAGER
Name of the controller manager ROS node
-p PARAM_FILE, --param-file PARAM_FILE
- Controller param file to be loaded into controller node before configure
+ Controller param file to be loaded into controller node before configure. Pass multiple times to load different files for different controllers or to override the parameters of the same controller.
-n NAMESPACE, --namespace NAMESPACE
DEPRECATED Namespace for the controller_manager and the controller(s)
--load-only Only load the controller and leave unconfigured.
--inactive Load and configure the controller, however do not activate them
-u, --unload-on-kill Wait until this application is interrupted and unload controller
--controller-manager-timeout CONTROLLER_MANAGER_TIMEOUT
- Time to wait for the controller manager
+ Time to wait for the controller manager service to be available
+ --service-call-timeout SERVICE_CALL_TIMEOUT
+ Time to wait for the service response from the controller manager
+ --switch-timeout SWITCH_TIMEOUT
+ Time to wait for a successful state switch of controllers. Useful if controllers cannot be switched immediately, e.g., paused
+ simulations at startup
--activate-as-group Activates all the parsed controllers list together instead of one by one. Useful for activating all chainable controllers altogether
- --fallback_controllers FALLBACK_CONTROLLERS [FALLBACK_CONTROLLERS ...]
- Fallback controllers list are activated as a fallback strategy when the spawned controllers fail. When the argument is provided, it takes precedence over the fallback_controllers list in the
- param file
The parsed controller config file can follow the same conventions as the typical ROS 2 parameter file format. Now, the spawner can handle config files with wildcard entries and also the controller name in the absolute namespace. See the following examples on the config files:
@@ -227,15 +229,18 @@ The parsed controller config file can follow the same conventions as the typical
.. code-block:: console
$ ros2 run controller_manager unspawner -h
- usage: unspawner [-h] [-c CONTROLLER_MANAGER] controller_name
+ usage: unspawner [-h] [-c CONTROLLER_MANAGER] [--switch-timeout SWITCH_TIMEOUT] controller_names [controller_names ...]
positional arguments:
- controller_name Name of the controller
+ controller_names Name of the controller
- optional arguments:
+ options:
-h, --help show this help message and exit
-c CONTROLLER_MANAGER, --controller-manager CONTROLLER_MANAGER
Name of the controller manager ROS node
+ --switch-timeout SWITCH_TIMEOUT
+ Time to wait for a successful state switch of controllers. Useful if controllers cannot be switched immediately, e.g., paused
+ simulations at startup
``hardware_spawner``
^^^^^^^^^^^^^^^^^^^^^^
@@ -304,6 +309,38 @@ The workaround for this is to specify another node name remap rule in the ``Node
auto cm = std::make_shared(
executor, "_target_node_name", "some_optional_namespace", options);
+Launching controller_manager with ros2_control_node
+---------------------------------------------------
+
+The controller_manager can be launched with the ros2_control_node executable. The following example shows how to launch the controller_manager with the ros2_control_node executable:
+
+.. code-block:: python
+
+ control_node = Node(
+ package="controller_manager",
+ executable="ros2_control_node",
+ parameters=[robot_controllers],
+ output="both",
+ )
+
+The ros2_control_node executable uses the following parameters from the ``controller_manager`` node:
+
+lock_memory (optional; bool; default: false)
+ Locks the memory of the ``controller_manager`` node at startup to physical RAM in order to avoid page faults
+ and to prevent the node from being swapped out to disk.
+ Find more information about the setup for memory locking in the following link : `How to set ulimit values `_
+ The following command can be used to set the memory locking limit temporarily : ``ulimit -l unlimited``.
+
+cpu_affinity (optional; int; default: -1)
+ Sets the CPU affinity of the ``controller_manager`` node to the specified CPU core.
+ The value of -1 means that the CPU affinity is not set.
+
+thread_priority (optional; int; default: 50)
+ Sets the thread priority of the ``controller_manager`` node to the specified value. The value must be between 0 and 99.
+
+use_sim_time (optional; bool; default: false)
+ Enables the use of simulation time in the ``controller_manager`` node.
+
Concepts
-----------
diff --git a/controller_manager/src/controller_manager.cpp b/controller_manager/src/controller_manager.cpp
index d9de54ea38..d469346c22 100644
--- a/controller_manager/src/controller_manager.cpp
+++ b/controller_manager/src/controller_manager.cpp
@@ -572,16 +572,28 @@ controller_interface::ControllerInterfaceBaseSharedPtr ControllerManager::load_c
// read_only params, dynamic maps lists etc
// Now check if the parameters_file parameter exist
const std::string param_name = controller_name + ".params_file";
- std::string parameters_file;
+ controller_spec.info.parameters_files.clear();
- // Check if parameter has been declared
- if (!has_parameter(param_name))
+ // get_parameter checks if parameter has been declared/set
+ rclcpp::Parameter params_files_parameter;
+ if (get_parameter(param_name, params_files_parameter))
{
- declare_parameter(param_name, rclcpp::ParameterType::PARAMETER_STRING);
- }
- if (get_parameter(param_name, parameters_file) && !parameters_file.empty())
- {
- controller_spec.info.parameters_file = parameters_file;
+ if (params_files_parameter.get_type() == rclcpp::ParameterType::PARAMETER_STRING_ARRAY)
+ {
+ controller_spec.info.parameters_files = params_files_parameter.as_string_array();
+ }
+ else if (params_files_parameter.get_type() == rclcpp::ParameterType::PARAMETER_STRING)
+ {
+ controller_spec.info.parameters_files.push_back(params_files_parameter.as_string());
+ }
+ else
+ {
+ RCLCPP_ERROR(
+ get_logger(),
+ "The 'params_file' param needs to be a string or a string array for '%s', but it is of "
+ "type %s",
+ controller_name.c_str(), params_files_parameter.get_type_name().c_str());
+ }
}
const std::string fallback_ctrl_param = controller_name + ".fallback_controllers";
@@ -3422,14 +3434,14 @@ rclcpp::NodeOptions ControllerManager::determine_controller_node_options(
node_options_arguments.push_back(arg);
}
- if (controller.info.parameters_file.has_value())
+ for (const auto & parameters_file : controller.info.parameters_files)
{
if (!check_for_element(node_options_arguments, RCL_ROS_ARGS_FLAG))
{
node_options_arguments.push_back(RCL_ROS_ARGS_FLAG);
}
node_options_arguments.push_back(RCL_PARAM_FILE_FLAG);
- node_options_arguments.push_back(controller.info.parameters_file.value());
+ node_options_arguments.push_back(parameters_file);
}
// ensure controller's `use_sim_time` parameter matches controller_manager's
diff --git a/controller_manager/src/ros2_control_node.cpp b/controller_manager/src/ros2_control_node.cpp
index 7e6f4e3260..da79de76a3 100644
--- a/controller_manager/src/ros2_control_node.cpp
+++ b/controller_manager/src/ros2_control_node.cpp
@@ -59,7 +59,7 @@ int main(int argc, char ** argv)
const bool use_sim_time = cm->get_parameter_or("use_sim_time", false);
- const bool lock_memory = cm->get_parameter_or("lock_memory", true);
+ const bool lock_memory = cm->get_parameter_or("lock_memory", false);
std::string message;
if (lock_memory && !realtime_tools::lock_memory(message))
{
diff --git a/controller_manager/test/test_controller_overriding_parameters.yaml b/controller_manager/test/test_controller_overriding_parameters.yaml
new file mode 100644
index 0000000000..115d0ed993
--- /dev/null
+++ b/controller_manager/test/test_controller_overriding_parameters.yaml
@@ -0,0 +1,5 @@
+ctrl_with_parameters_and_type:
+ ros__parameters:
+ interface_name: "impedance"
+ joint_offset: 0.2
+ joint_names: ["joint10"]
diff --git a/controller_manager/test/test_controller_spawner_with_type.yaml b/controller_manager/test/test_controller_spawner_with_type.yaml
index 087994bd23..23fd69b216 100644
--- a/controller_manager/test/test_controller_spawner_with_type.yaml
+++ b/controller_manager/test/test_controller_spawner_with_type.yaml
@@ -2,6 +2,7 @@ ctrl_with_parameters_and_type:
ros__parameters:
type: "controller_manager/test_controller"
joint_names: ["joint0"]
+ interface_name: "position"
/**:
chainable_ctrl_with_parameters_and_type:
diff --git a/controller_manager/test/test_spawner_unspawner.cpp b/controller_manager/test/test_spawner_unspawner.cpp
index 74e1efeeed..70a5480a06 100644
--- a/controller_manager/test/test_spawner_unspawner.cpp
+++ b/controller_manager/test/test_spawner_unspawner.cpp
@@ -255,6 +255,50 @@ TEST_F(TestLoadController, multi_ctrls_test_type_in_param)
}
}
+TEST_F(TestLoadController, spawner_test_with_params_file_string_parameter)
+{
+ const std::string test_file_path = ament_index_cpp::get_package_prefix("controller_manager") +
+ "/test/test_controller_spawner_with_type.yaml";
+
+ cm_->set_parameter(rclcpp::Parameter(
+ "ctrl_with_parameters_and_type.type", test_controller::TEST_CONTROLLER_CLASS_NAME));
+ cm_->set_parameter(
+ rclcpp::Parameter("ctrl_with_parameters_and_type.params_file", test_file_path));
+
+ ControllerManagerRunner cm_runner(this);
+ // Provide controller type via the parsed file
+ EXPECT_EQ(
+ call_spawner("ctrl_with_parameters_and_type --load-only -c test_controller_manager"), 0);
+
+ ASSERT_EQ(cm_->get_loaded_controllers().size(), 1ul);
+
+ auto ctrl_with_parameters_and_type = cm_->get_loaded_controllers()[0];
+ ASSERT_EQ(ctrl_with_parameters_and_type.info.name, "ctrl_with_parameters_and_type");
+ ASSERT_EQ(ctrl_with_parameters_and_type.info.type, test_controller::TEST_CONTROLLER_CLASS_NAME);
+ ASSERT_EQ(
+ ctrl_with_parameters_and_type.c->get_lifecycle_state().id(),
+ lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+ ASSERT_EQ(
+ cm_->get_parameter("ctrl_with_parameters_and_type.params_file").as_string(), test_file_path);
+ auto ctrl_node = ctrl_with_parameters_and_type.c->get_node();
+ ASSERT_THAT(
+ ctrl_with_parameters_and_type.info.parameters_files,
+ std::vector({test_file_path}));
+ if (!ctrl_node->has_parameter("joint_names"))
+ {
+ ctrl_node->declare_parameter("joint_names", std::vector({"random_joint"}));
+ }
+ ASSERT_THAT(
+ ctrl_node->get_parameter("joint_names").as_string_array(),
+ std::vector({"joint0"}));
+
+ if (!ctrl_node->has_parameter("interface_name"))
+ {
+ ctrl_node->declare_parameter("interface_name", "invalid_interface");
+ }
+ ASSERT_EQ(ctrl_node->get_parameter("interface_name").as_string(), "position");
+}
+
TEST_F(TestLoadController, spawner_test_type_in_params_file)
{
const std::string test_file_path = ament_index_cpp::get_package_prefix("controller_manager") +
@@ -278,7 +322,8 @@ TEST_F(TestLoadController, spawner_test_type_in_params_file)
ctrl_with_parameters_and_type.c->get_lifecycle_state().id(),
lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
ASSERT_EQ(
- cm_->get_parameter("ctrl_with_parameters_and_type.params_file").as_string(), test_file_path);
+ cm_->get_parameter("ctrl_with_parameters_and_type.params_file").as_string_array()[0],
+ test_file_path);
auto chain_ctrl_with_parameters_and_type = cm_->get_loaded_controllers()[1];
ASSERT_EQ(
@@ -290,7 +335,7 @@ TEST_F(TestLoadController, spawner_test_type_in_params_file)
chain_ctrl_with_parameters_and_type.c->get_lifecycle_state().id(),
lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
ASSERT_EQ(
- cm_->get_parameter("chainable_ctrl_with_parameters_and_type.params_file").as_string(),
+ cm_->get_parameter("chainable_ctrl_with_parameters_and_type.params_file").as_string_array()[0],
test_file_path);
EXPECT_EQ(
@@ -308,7 +353,8 @@ TEST_F(TestLoadController, spawner_test_type_in_params_file)
ASSERT_EQ(
ctrl_1.c->get_lifecycle_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
ASSERT_EQ(
- cm_->get_parameter("ctrl_with_parameters_and_type.params_file").as_string(), test_file_path);
+ cm_->get_parameter("ctrl_with_parameters_and_type.params_file").as_string_array()[0],
+ test_file_path);
auto ctrl_2 = cm_->get_loaded_controllers()[1];
ASSERT_EQ(ctrl_2.info.name, "chainable_ctrl_with_parameters_and_type");
@@ -316,7 +362,7 @@ TEST_F(TestLoadController, spawner_test_type_in_params_file)
ASSERT_EQ(
ctrl_2.c->get_lifecycle_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
ASSERT_EQ(
- cm_->get_parameter("chainable_ctrl_with_parameters_and_type.params_file").as_string(),
+ cm_->get_parameter("chainable_ctrl_with_parameters_and_type.params_file").as_string_array()[0],
test_file_path);
}
@@ -357,6 +403,114 @@ TEST_F(TestLoadController, unload_on_kill_activate_as_group)
ASSERT_EQ(cm_->get_loaded_controllers().size(), 0ul);
}
+TEST_F(TestLoadController, spawner_test_to_check_parameter_overriding)
+{
+ const std::string main_test_file_path =
+ ament_index_cpp::get_package_prefix("controller_manager") +
+ "/test/test_controller_spawner_with_type.yaml";
+ const std::string overriding_test_file_path =
+ ament_index_cpp::get_package_prefix("controller_manager") +
+ "/test/test_controller_overriding_parameters.yaml";
+
+ ControllerManagerRunner cm_runner(this);
+ EXPECT_EQ(
+ call_spawner(
+ "ctrl_with_parameters_and_type --load-only -c "
+ "test_controller_manager -p " +
+ main_test_file_path + " -p " + overriding_test_file_path),
+ 0);
+
+ ASSERT_EQ(cm_->get_loaded_controllers().size(), 1ul);
+
+ auto ctrl_with_parameters_and_type = cm_->get_loaded_controllers()[0];
+ ASSERT_EQ(ctrl_with_parameters_and_type.info.name, "ctrl_with_parameters_and_type");
+ ASSERT_EQ(ctrl_with_parameters_and_type.info.type, test_controller::TEST_CONTROLLER_CLASS_NAME);
+ ASSERT_EQ(
+ ctrl_with_parameters_and_type.c->get_lifecycle_state().id(),
+ lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+ ASSERT_THAT(
+ cm_->get_parameter("ctrl_with_parameters_and_type.params_file").as_string_array(),
+ std::vector({main_test_file_path, overriding_test_file_path}));
+ auto ctrl_node = ctrl_with_parameters_and_type.c->get_node();
+ ASSERT_THAT(
+ ctrl_with_parameters_and_type.info.parameters_files,
+ std::vector({main_test_file_path, overriding_test_file_path}));
+ if (!ctrl_node->has_parameter("joint_names"))
+ {
+ ctrl_node->declare_parameter("joint_names", std::vector({"random_joint"}));
+ }
+ ASSERT_THAT(
+ ctrl_node->get_parameter("joint_names").as_string_array(),
+ std::vector({"joint10"}));
+
+ if (!ctrl_node->has_parameter("interface_name"))
+ {
+ ctrl_node->declare_parameter("interface_name", "invalid_interface");
+ }
+ ASSERT_EQ(ctrl_node->get_parameter("interface_name").as_string(), "impedance")
+ << "The parameter should be overridden";
+
+ if (!ctrl_node->has_parameter("joint_offset"))
+ {
+ ctrl_node->declare_parameter("joint_offset", -M_PI);
+ }
+ ASSERT_EQ(ctrl_node->get_parameter("joint_offset").as_double(), 0.2);
+}
+
+TEST_F(TestLoadController, spawner_test_to_check_parameter_overriding_reverse)
+{
+ const std::string main_test_file_path =
+ ament_index_cpp::get_package_prefix("controller_manager") +
+ "/test/test_controller_overriding_parameters.yaml";
+ const std::string overriding_test_file_path =
+ ament_index_cpp::get_package_prefix("controller_manager") +
+ "/test/test_controller_spawner_with_type.yaml";
+
+ ControllerManagerRunner cm_runner(this);
+ EXPECT_EQ(
+ call_spawner(
+ "ctrl_with_parameters_and_type --load-only -c "
+ "test_controller_manager -p " +
+ main_test_file_path + " -p " + overriding_test_file_path),
+ 0);
+
+ ASSERT_EQ(cm_->get_loaded_controllers().size(), 1ul);
+
+ auto ctrl_with_parameters_and_type = cm_->get_loaded_controllers()[0];
+ ASSERT_EQ(ctrl_with_parameters_and_type.info.name, "ctrl_with_parameters_and_type");
+ ASSERT_EQ(ctrl_with_parameters_and_type.info.type, test_controller::TEST_CONTROLLER_CLASS_NAME);
+ ASSERT_EQ(
+ ctrl_with_parameters_and_type.c->get_lifecycle_state().id(),
+ lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+ ASSERT_THAT(
+ cm_->get_parameter("ctrl_with_parameters_and_type.params_file").as_string_array(),
+ std::vector({main_test_file_path, overriding_test_file_path}));
+ auto ctrl_node = ctrl_with_parameters_and_type.c->get_node();
+ ASSERT_THAT(
+ ctrl_with_parameters_and_type.info.parameters_files,
+ std::vector({main_test_file_path, overriding_test_file_path}));
+ if (!ctrl_node->has_parameter("joint_names"))
+ {
+ ctrl_node->declare_parameter("joint_names", std::vector({"random_joint"}));
+ }
+ ASSERT_THAT(
+ ctrl_node->get_parameter("joint_names").as_string_array(),
+ std::vector({"joint0"}));
+
+ if (!ctrl_node->has_parameter("interface_name"))
+ {
+ ctrl_node->declare_parameter("interface_name", "invalid_interface");
+ }
+ ASSERT_EQ(ctrl_node->get_parameter("interface_name").as_string(), "position")
+ << "The parameter should be overridden";
+
+ if (!ctrl_node->has_parameter("joint_offset"))
+ {
+ ctrl_node->declare_parameter("joint_offset", -M_PI);
+ }
+ ASSERT_EQ(ctrl_node->get_parameter("joint_offset").as_double(), 0.2);
+}
+
TEST_F(TestLoadController, spawner_test_fallback_controllers)
{
const std::string test_file_path = ament_index_cpp::get_package_prefix("controller_manager") +
@@ -377,7 +531,7 @@ TEST_F(TestLoadController, spawner_test_fallback_controllers)
ASSERT_TRUE(ctrl_1.info.fallback_controllers_names.empty());
ASSERT_EQ(
ctrl_1.c->get_lifecycle_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
- ASSERT_EQ(cm_->get_parameter("ctrl_1.params_file").as_string(), test_file_path);
+ ASSERT_EQ(cm_->get_parameter("ctrl_1.params_file").as_string_array()[0], test_file_path);
}
// Try to spawn now the controller with fallback controllers inside the yaml
@@ -392,7 +546,7 @@ TEST_F(TestLoadController, spawner_test_fallback_controllers)
ASSERT_TRUE(ctrl_1.info.fallback_controllers_names.empty());
ASSERT_EQ(
ctrl_1.c->get_lifecycle_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
- ASSERT_EQ(cm_->get_parameter("ctrl_1.params_file").as_string(), test_file_path);
+ ASSERT_EQ(cm_->get_parameter("ctrl_1.params_file").as_string_array()[0], test_file_path);
auto ctrl_2 = cm_->get_loaded_controllers()[1];
ASSERT_EQ(ctrl_2.info.name, "ctrl_2");
@@ -401,7 +555,7 @@ TEST_F(TestLoadController, spawner_test_fallback_controllers)
ctrl_2.info.fallback_controllers_names, testing::ElementsAre("ctrl_6", "ctrl_7", "ctrl_8"));
ASSERT_EQ(
ctrl_2.c->get_lifecycle_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
- ASSERT_EQ(cm_->get_parameter("ctrl_2.params_file").as_string(), test_file_path);
+ ASSERT_EQ(cm_->get_parameter("ctrl_2.params_file").as_string_array()[0], test_file_path);
auto ctrl_3 = cm_->get_loaded_controllers()[2];
ASSERT_EQ(ctrl_3.info.name, "ctrl_3");
@@ -409,7 +563,7 @@ TEST_F(TestLoadController, spawner_test_fallback_controllers)
ASSERT_THAT(ctrl_3.info.fallback_controllers_names, testing::ElementsAre("ctrl_9"));
ASSERT_EQ(
ctrl_3.c->get_lifecycle_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
- ASSERT_EQ(cm_->get_parameter("ctrl_3.params_file").as_string(), test_file_path);
+ ASSERT_EQ(cm_->get_parameter("ctrl_3.params_file").as_string_array()[0], test_file_path);
}
}
@@ -695,7 +849,8 @@ TEST_F(TestLoadControllerWithNamespacedCM, spawner_test_type_in_params_file)
ctrl_with_parameters_and_type.c->get_lifecycle_state().id(),
lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
ASSERT_EQ(
- cm_->get_parameter(ctrl_with_parameters_and_type.info.name + ".params_file").as_string(),
+ cm_->get_parameter(ctrl_with_parameters_and_type.info.name + ".params_file")
+ .as_string_array()[0],
test_file_path);
auto chain_ctrl_with_parameters_and_type = cm_->get_loaded_controllers()[1];
@@ -708,7 +863,8 @@ TEST_F(TestLoadControllerWithNamespacedCM, spawner_test_type_in_params_file)
chain_ctrl_with_parameters_and_type.c->get_lifecycle_state().id(),
lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
ASSERT_EQ(
- cm_->get_parameter(chain_ctrl_with_parameters_and_type.info.name + ".params_file").as_string(),
+ cm_->get_parameter(chain_ctrl_with_parameters_and_type.info.name + ".params_file")
+ .as_string_array()[0],
test_file_path);
EXPECT_EQ(
@@ -725,14 +881,16 @@ TEST_F(TestLoadControllerWithNamespacedCM, spawner_test_type_in_params_file)
ASSERT_EQ(ctrl_1.info.type, test_controller::TEST_CONTROLLER_CLASS_NAME);
ASSERT_EQ(
ctrl_1.c->get_lifecycle_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
- ASSERT_EQ(cm_->get_parameter(ctrl_1.info.name + ".params_file").as_string(), test_file_path);
+ ASSERT_EQ(
+ cm_->get_parameter(ctrl_1.info.name + ".params_file").as_string_array()[0], test_file_path);
auto ctrl_2 = cm_->get_loaded_controllers()[1];
ASSERT_EQ(ctrl_2.info.name, "ns_chainable_ctrl_with_parameters_and_type");
ASSERT_EQ(ctrl_2.info.type, test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
ASSERT_EQ(
ctrl_2.c->get_lifecycle_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
- ASSERT_EQ(cm_->get_parameter(ctrl_2.info.name + ".params_file").as_string(), test_file_path);
+ ASSERT_EQ(
+ cm_->get_parameter(ctrl_2.info.name + ".params_file").as_string_array()[0], test_file_path);
}
TEST_F(
@@ -781,7 +939,8 @@ TEST_F(
ctrl_with_parameters_and_type.c->get_lifecycle_state().id(),
lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
ASSERT_EQ(
- cm_->get_parameter(ctrl_with_parameters_and_type.info.name + ".params_file").as_string(),
+ cm_->get_parameter(ctrl_with_parameters_and_type.info.name + ".params_file")
+ .as_string_array()[0],
test_file_path);
auto chain_ctrl_with_parameters_and_type = cm_->get_loaded_controllers()[1];
@@ -794,7 +953,8 @@ TEST_F(
chain_ctrl_with_parameters_and_type.c->get_lifecycle_state().id(),
lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
ASSERT_EQ(
- cm_->get_parameter(chain_ctrl_with_parameters_and_type.info.name + ".params_file").as_string(),
+ cm_->get_parameter(chain_ctrl_with_parameters_and_type.info.name + ".params_file")
+ .as_string_array()[0],
test_file_path);
EXPECT_EQ(
@@ -812,14 +972,16 @@ TEST_F(
ASSERT_EQ(ctrl_1.info.type, test_controller::TEST_CONTROLLER_CLASS_NAME);
ASSERT_EQ(
ctrl_1.c->get_lifecycle_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
- ASSERT_EQ(cm_->get_parameter(ctrl_1.info.name + ".params_file").as_string(), test_file_path);
+ ASSERT_EQ(
+ cm_->get_parameter(ctrl_1.info.name + ".params_file").as_string_array()[0], test_file_path);
auto ctrl_2 = cm_->get_loaded_controllers()[1];
ASSERT_EQ(ctrl_2.info.name, "ns_chainable_ctrl_with_parameters_and_type");
ASSERT_EQ(ctrl_2.info.type, test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
ASSERT_EQ(
ctrl_2.c->get_lifecycle_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
- ASSERT_EQ(cm_->get_parameter(ctrl_2.info.name + ".params_file").as_string(), test_file_path);
+ ASSERT_EQ(
+ cm_->get_parameter(ctrl_2.info.name + ".params_file").as_string_array()[0], test_file_path);
}
TEST_F(TestLoadControllerWithNamespacedCM, spawner_test_with_wildcard_entries_in_params_file)
@@ -928,3 +1090,138 @@ TEST_F(
ctrl_with_parameters_and_type.c->get_lifecycle_state().id(),
lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
}
+
+TEST_F(TestLoadController, spawner_test_parsing_multiple_params_file)
+{
+ const std::string test_file_path = ament_index_cpp::get_package_prefix("controller_manager") +
+ "/test/test_controller_spawner_with_type.yaml";
+ const std::string fallback_test_file_path =
+ ament_index_cpp::get_package_prefix("controller_manager") +
+ "/test/test_controller_spawner_with_fallback_controllers.yaml";
+
+ cm_->set_parameter(rclcpp::Parameter("ctrl_1.type", test_controller::TEST_CONTROLLER_CLASS_NAME));
+ cm_->set_parameter(rclcpp::Parameter("ctrl_2.type", test_controller::TEST_CONTROLLER_CLASS_NAME));
+
+ ControllerManagerRunner cm_runner(this);
+ // Provide controller type via the parsed file
+ EXPECT_EQ(
+ call_spawner(
+ "ctrl_with_parameters_and_type chainable_ctrl_with_parameters_and_type ctrl_2 ctrl_1 "
+ "--load-only -c "
+ "test_controller_manager -p " +
+ test_file_path + " -p" + fallback_test_file_path),
+ 0);
+
+ ASSERT_EQ(cm_->get_loaded_controllers().size(), 4ul);
+
+ auto ctrl_with_parameters_and_type = cm_->get_loaded_controllers()[0];
+ ASSERT_EQ(ctrl_with_parameters_and_type.info.name, "ctrl_with_parameters_and_type");
+ ASSERT_EQ(ctrl_with_parameters_and_type.info.type, test_controller::TEST_CONTROLLER_CLASS_NAME);
+ ASSERT_EQ(
+ ctrl_with_parameters_and_type.c->get_lifecycle_state().id(),
+ lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+ auto params_file_info =
+ cm_->get_parameter("ctrl_with_parameters_and_type.params_file").as_string_array();
+ ASSERT_EQ(params_file_info.size(), 1ul);
+ ASSERT_EQ(params_file_info[0], test_file_path);
+
+ auto chain_ctrl_with_parameters_and_type = cm_->get_loaded_controllers()[1];
+ ASSERT_EQ(
+ chain_ctrl_with_parameters_and_type.info.name, "chainable_ctrl_with_parameters_and_type");
+ ASSERT_EQ(
+ chain_ctrl_with_parameters_and_type.info.type,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ ASSERT_EQ(
+ chain_ctrl_with_parameters_and_type.c->get_lifecycle_state().id(),
+ lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+ params_file_info =
+ cm_->get_parameter("chainable_ctrl_with_parameters_and_type.params_file").as_string_array();
+ ASSERT_EQ(params_file_info.size(), 1ul);
+ ASSERT_EQ(params_file_info[0], test_file_path);
+
+ auto ctrl_2 = cm_->get_loaded_controllers()[2];
+ ASSERT_EQ(ctrl_2.info.name, "ctrl_2");
+ ASSERT_EQ(ctrl_2.info.type, test_controller::TEST_CONTROLLER_CLASS_NAME);
+ ASSERT_EQ(
+ ctrl_2.c->get_lifecycle_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+ params_file_info = cm_->get_parameter("ctrl_2.params_file").as_string_array();
+ ASSERT_EQ(params_file_info.size(), 1ul);
+ ASSERT_EQ(params_file_info[0], fallback_test_file_path);
+
+ auto ctrl_1 = cm_->get_loaded_controllers()[3];
+ ASSERT_EQ(ctrl_1.info.name, "ctrl_1");
+ ASSERT_EQ(ctrl_1.info.type, test_controller::TEST_CONTROLLER_CLASS_NAME);
+ ASSERT_EQ(
+ ctrl_1.c->get_lifecycle_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+ params_file_info = cm_->get_parameter("ctrl_1.params_file").as_string_array();
+ ASSERT_EQ(params_file_info.size(), 1ul);
+ ASSERT_EQ(params_file_info[0], fallback_test_file_path);
+}
+
+TEST_F(TestLoadController, spawner_test_parsing_same_params_file_multiple_times)
+{
+ const std::string test_file_path = ament_index_cpp::get_package_prefix("controller_manager") +
+ "/test/test_controller_spawner_with_type.yaml";
+ const std::string fallback_test_file_path =
+ ament_index_cpp::get_package_prefix("controller_manager") +
+ "/test/test_controller_spawner_with_fallback_controllers.yaml";
+
+ cm_->set_parameter(rclcpp::Parameter("ctrl_1.type", test_controller::TEST_CONTROLLER_CLASS_NAME));
+ cm_->set_parameter(rclcpp::Parameter("ctrl_2.type", test_controller::TEST_CONTROLLER_CLASS_NAME));
+
+ ControllerManagerRunner cm_runner(this);
+ // Provide controller type via the parsed file
+ EXPECT_EQ(
+ call_spawner(
+ "ctrl_with_parameters_and_type chainable_ctrl_with_parameters_and_type ctrl_2 ctrl_1 "
+ "--load-only -c "
+ "test_controller_manager -p " +
+ test_file_path + " -p" + fallback_test_file_path + " -p" + fallback_test_file_path + " -p" +
+ test_file_path),
+ 0);
+
+ ASSERT_EQ(cm_->get_loaded_controllers().size(), 4ul);
+
+ auto ctrl_with_parameters_and_type = cm_->get_loaded_controllers()[0];
+ ASSERT_EQ(ctrl_with_parameters_and_type.info.name, "ctrl_with_parameters_and_type");
+ ASSERT_EQ(ctrl_with_parameters_and_type.info.type, test_controller::TEST_CONTROLLER_CLASS_NAME);
+ ASSERT_EQ(
+ ctrl_with_parameters_and_type.c->get_lifecycle_state().id(),
+ lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+ auto params_file_info =
+ cm_->get_parameter("ctrl_with_parameters_and_type.params_file").as_string_array();
+ ASSERT_EQ(params_file_info.size(), 1ul);
+ ASSERT_EQ(params_file_info[0], test_file_path);
+
+ auto chain_ctrl_with_parameters_and_type = cm_->get_loaded_controllers()[1];
+ ASSERT_EQ(
+ chain_ctrl_with_parameters_and_type.info.name, "chainable_ctrl_with_parameters_and_type");
+ ASSERT_EQ(
+ chain_ctrl_with_parameters_and_type.info.type,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ ASSERT_EQ(
+ chain_ctrl_with_parameters_and_type.c->get_lifecycle_state().id(),
+ lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+ params_file_info =
+ cm_->get_parameter("chainable_ctrl_with_parameters_and_type.params_file").as_string_array();
+ ASSERT_EQ(params_file_info.size(), 1ul);
+ ASSERT_EQ(params_file_info[0], test_file_path);
+
+ auto ctrl_2 = cm_->get_loaded_controllers()[2];
+ ASSERT_EQ(ctrl_2.info.name, "ctrl_2");
+ ASSERT_EQ(ctrl_2.info.type, test_controller::TEST_CONTROLLER_CLASS_NAME);
+ ASSERT_EQ(
+ ctrl_2.c->get_lifecycle_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+ params_file_info = cm_->get_parameter("ctrl_2.params_file").as_string_array();
+ ASSERT_EQ(params_file_info.size(), 1ul);
+ ASSERT_EQ(params_file_info[0], fallback_test_file_path);
+
+ auto ctrl_1 = cm_->get_loaded_controllers()[3];
+ ASSERT_EQ(ctrl_1.info.name, "ctrl_1");
+ ASSERT_EQ(ctrl_1.info.type, test_controller::TEST_CONTROLLER_CLASS_NAME);
+ ASSERT_EQ(
+ ctrl_1.c->get_lifecycle_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+ params_file_info = cm_->get_parameter("ctrl_1.params_file").as_string_array();
+ ASSERT_EQ(params_file_info.size(), 1ul);
+ ASSERT_EQ(params_file_info[0], fallback_test_file_path);
+}
diff --git a/doc/release_notes.rst b/doc/release_notes.rst
index a6ebb16e5f..b7c4b934c4 100644
--- a/doc/release_notes.rst
+++ b/doc/release_notes.rst
@@ -76,10 +76,13 @@ controller_manager
* The ``--controller-type`` or ``-t`` spawner arg is removed. Now the controller type is defined in the controller configuration file with ``type`` field (`#1639 `_).
* The ``--namespace`` or ``-n`` spawner arg is deprecated. Now the spawner namespace can be defined using the ROS 2 standard way (`#1640 `_).
* Added support for the wildcard entries for the controller configuration files (`#1724 `_).
+* The spawner now supports parsing multiple ``-p`` or ``--param-file`` arguments, this should help in loading multiple parameter files for a controller or for multiple controllers (`#1805 `_).
+* ``--switch-timeout`` was added as parameter to the helper scripts ``spawner.py`` and ``unspawner.py``. Useful if controllers cannot be switched immediately, e.g., paused simulations at startup (`#1790 `_).
* ``ros2_control_node`` can now handle the sim time used by different simulators, when ``use_sim_time`` is set to true (`#1810 `_).
* The ``ros2_control_node`` node now accepts the ``thread_priority`` parameter to set the scheduler priority of the controller_manager's RT thread (`#1820 `_).
* The ``ros2_control_node`` node has a new ``lock_memory`` parameter to lock memory at startup to physical RAM in order to avoid page faults (`#1822 `_).
* The ``ros2_control_node`` node has a new ``cpu_affinity`` parameter to bind the process to a specific CPU core. By default, this is not enabled. (`#1852 `_).
+* The ``--service-call-timeout`` was added as parameter to the helper scripts ``spawner.py``. Useful when the CPU load is high at startup and the service call does not return immediately (`#1808 `_).
hardware_interface
******************
diff --git a/hardware_interface/include/hardware_interface/controller_info.hpp b/hardware_interface/include/hardware_interface/controller_info.hpp
index a38fb99cb3..3ad89551d5 100644
--- a/hardware_interface/include/hardware_interface/controller_info.hpp
+++ b/hardware_interface/include/hardware_interface/controller_info.hpp
@@ -34,7 +34,7 @@ struct ControllerInfo
std::string type;
/// Controller param file
- std::optional parameters_file;
+ std::vector parameters_files;
/// List of claimed interfaces by the controller.
std::vector claimed_interfaces;
diff --git a/hardware_interface/include/hardware_interface/system_interface.hpp b/hardware_interface/include/hardware_interface/system_interface.hpp
index 18da0e4012..32d0b8a48a 100644
--- a/hardware_interface/include/hardware_interface/system_interface.hpp
+++ b/hardware_interface/include/hardware_interface/system_interface.hpp
@@ -178,7 +178,7 @@ class SystemInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNodeI
*
* \return vector of shared pointers to the created and stored StateInterfaces
*/
- std::vector on_export_state_interfaces()
+ virtual std::vector on_export_state_interfaces()
{
// import the unlisted interfaces
std::vector unlisted_interface_descriptions =
@@ -270,7 +270,7 @@ class SystemInterface : public rclcpp_lifecycle::node_interfaces::LifecycleNodeI
*
* \return vector of shared pointers to the created and stored CommandInterfaces
*/
- std::vector on_export_command_interfaces()
+ virtual std::vector on_export_command_interfaces()
{
// import the unlisted interfaces
std::vector unlisted_interface_descriptions =
diff --git a/ros2_control-not-released.iron.repos b/ros2_control-not-released.iron.repos
deleted file mode 100644
index 56f46b6f79..0000000000
--- a/ros2_control-not-released.iron.repos
+++ /dev/null
@@ -1 +0,0 @@
-repositories:
diff --git a/ros2_control.iron.repos b/ros2_control.iron.repos
deleted file mode 100644
index c93d8f4ef6..0000000000
--- a/ros2_control.iron.repos
+++ /dev/null
@@ -1,9 +0,0 @@
-repositories:
- ros-controls/realtime_tools:
- type: git
- url: https://github.com/ros-controls/realtime_tools.git
- version: master
- ros-controls/control_msgs:
- type: git
- url: https://github.com/ros-controls/control_msgs.git
- version: master
diff --git a/ros2controlcli/ros2controlcli/verb/load_controller.py b/ros2controlcli/ros2controlcli/verb/load_controller.py
index e47540a1df..5e29058a31 100644
--- a/ros2controlcli/ros2controlcli/verb/load_controller.py
+++ b/ros2controlcli/ros2controlcli/verb/load_controller.py
@@ -17,7 +17,7 @@
load_controller,
list_controllers,
switch_controllers,
- set_controller_parameters_from_param_file,
+ set_controller_parameters_from_param_files,
bcolors,
)
@@ -68,11 +68,11 @@ def main(self, *, args):
if not os.path.isabs(args.param_file):
args.param_file = os.path.join(os.getcwd(), args.param_file)
- if not set_controller_parameters_from_param_file(
+ if not set_controller_parameters_from_param_files(
node,
args.controller_manager,
args.controller_name,
- args.param_file,
+ [args.param_file],
node.get_namespace(),
):
return 1