diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000000..f5e9921f23
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,20 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ # Workflow files stored in the
+ # default location of `.github/workflows`
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ - package-ecosystem: "github-actions"
+ # Workflow files stored in the
+ # default location of `.github/workflows`
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ target-branch: "humble"
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/humble-abi-compatibility.yml b/.github/workflows/humble-abi-compatibility.yml
index 61a5f1f29c..844a4559e2 100644
--- a/.github/workflows/humble-abi-compatibility.yml
+++ b/.github/workflows/humble-abi-compatibility.yml
@@ -14,6 +14,11 @@ on:
- '**/CMakeLists.txt'
- 'ros2_control-not-released.humble.repos'
+concurrency:
+ # cancel previous runs of the same workflow, except for pushes on humble branch
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ !startsWith(github.ref, '/refs/heads') }}
+
jobs:
abi_check:
runs-on: ubuntu-latest
diff --git a/.github/workflows/humble-binary-build.yml b/.github/workflows/humble-binary-build.yml
index 4e9abfffe6..0fabfda92f 100644
--- a/.github/workflows/humble-binary-build.yml
+++ b/.github/workflows/humble-binary-build.yml
@@ -32,10 +32,16 @@ on:
# Run every morning to detect flakiness and broken dependencies
- cron: '03 1 * * *'
+concurrency:
+ # cancel previous runs of the same workflow, except for pushes on humble branch
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ !startsWith(github.ref, '/refs/heads') }}
+
jobs:
binary:
uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
strategy:
+ fail-fast: false
matrix:
ROS_DISTRO: [humble]
ROS_REPO: [main, testing]
diff --git a/.github/workflows/humble-check-docs.yml b/.github/workflows/humble-check-docs.yml
index f3c31703cd..078a541bf0 100644
--- a/.github/workflows/humble-check-docs.yml
+++ b/.github/workflows/humble-check-docs.yml
@@ -10,6 +10,10 @@ on:
- '**.md'
- '**.yaml'
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
jobs:
check-docs:
name: Check Docs
diff --git a/.github/workflows/humble-coverage-build.yml b/.github/workflows/humble-coverage-build.yml
index 209c931d4e..8255b02bb4 100644
--- a/.github/workflows/humble-coverage-build.yml
+++ b/.github/workflows/humble-coverage-build.yml
@@ -28,6 +28,11 @@ on:
- 'ros2_control.humble.repos'
- 'codecov.yml'
+concurrency:
+ # cancel previous runs of the same workflow, except for pushes on humble branch
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ !startsWith(github.ref, '/refs/heads') }}
+
jobs:
coverage_humble:
uses: ros-controls/ros2_control_ci/.github/workflows/reusable-build-coverage.yml@master
diff --git a/.github/workflows/humble-debian-build.yml b/.github/workflows/humble-debian-build.yml
index ad044f9343..301befbcab 100644
--- a/.github/workflows/humble-debian-build.yml
+++ b/.github/workflows/humble-debian-build.yml
@@ -17,11 +17,16 @@ on:
# Run every day to detect flakiness and broken dependencies
- cron: '03 1 * * *'
+concurrency:
+ # cancel previous runs of the same workflow, except for pushes on humble branch
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ !startsWith(github.ref, '/refs/heads') }}
jobs:
debian_source_build:
uses: ros-controls/ros2_control_ci/.github/workflows/reusable-debian-build.yml@master
strategy:
+ fail-fast: false
matrix:
ROS_DISTRO: [humble]
with:
diff --git a/.github/workflows/humble-pre-commit.yml b/.github/workflows/humble-pre-commit.yml
index 5bb2408578..38a76ee025 100644
--- a/.github/workflows/humble-pre-commit.yml
+++ b/.github/workflows/humble-pre-commit.yml
@@ -6,6 +6,10 @@ on:
branches:
- humble
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
jobs:
pre-commit:
uses: ros-controls/ros2_control_ci/.github/workflows/reusable-pre-commit.yml@master
diff --git a/.github/workflows/humble-rhel-binary-build.yml b/.github/workflows/humble-rhel-binary-build.yml
index 4a4313cedb..e986bf361e 100644
--- a/.github/workflows/humble-rhel-binary-build.yml
+++ b/.github/workflows/humble-rhel-binary-build.yml
@@ -17,10 +17,16 @@ on:
# Run every day to detect flakiness and broken dependencies
- cron: '03 1 * * *'
+concurrency:
+ # cancel previous runs of the same workflow, except for pushes on humble branch
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ !startsWith(github.ref, '/refs/heads') }}
+
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: [humble]
with:
diff --git a/.github/workflows/humble-semi-binary-build.yml b/.github/workflows/humble-semi-binary-build.yml
index c1be728490..7b0c5f1fd3 100644
--- a/.github/workflows/humble-semi-binary-build.yml
+++ b/.github/workflows/humble-semi-binary-build.yml
@@ -1,6 +1,6 @@
name: Humble Semi-Binary Build
# author: Denis Štogl
-# description: 'Build & test all dependencies from released (binary) packages.'
+# description: 'Build & test all ros2_control dependencies from source.'
on:
workflow_dispatch:
@@ -32,15 +32,32 @@ on:
# Run every morning to detect flakiness and broken dependencies
- cron: '03 1 * * *'
+concurrency:
+ # cancel previous runs of the same workflow, except for pushes on humble branch
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ !startsWith(github.ref, '/refs/heads') }}
+
jobs:
- binary:
+ semi-binary:
uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
strategy:
+ fail-fast: false
matrix:
ROS_DISTRO: [humble]
- ROS_REPO: [main, testing]
+ ROS_REPO: [testing]
with:
ros_distro: ${{ matrix.ROS_DISTRO }}
ros_repo: ${{ matrix.ROS_REPO }}
upstream_workspace: ros2_control.${{ matrix.ROS_DISTRO }}.repos
ref_for_scheduled_build: humble
+ semi-binary-clang:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
+ with:
+ ros_distro: humble
+ ros_repo: testing
+ upstream_workspace: ros2_control.humble.repos
+ ref_for_scheduled_build: humble
+ additional_debs: clang
+ c_compiler: clang
+ cxx_compiler: clang++
+ not_test_build: true
diff --git a/.github/workflows/humble-semi-binary-downstream-build.yml b/.github/workflows/humble-semi-binary-downstream-build.yml
new file mode 100644
index 0000000000..f0bc3a9fd2
--- /dev/null
+++ b/.github/workflows/humble-semi-binary-downstream-build.yml
@@ -0,0 +1,49 @@
+name: Humble Downstream Build
+# description: 'Build & test downstream packages from source.'
+# author: Christoph Froehlich
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - humble
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '**.yaml'
+ - '.github/workflows/humble-semi-binary-downstream-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros_controls.humble.repos'
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build-downstream:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
+ with:
+ ros_distro: humble
+ ros_repo: testing
+ ref_for_scheduled_build: humble
+ upstream_workspace: ros2_control.humble.repos
+ # we don't test this repository, we just build it
+ not_test_build: true
+ # we test the downstream packages, which are part of our organization
+ downstream_workspace: ros_controls.humble.repos
+ not_test_downstream: false
+ build-downstream-3rd-party:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
+ with:
+ ros_distro: humble
+ ros_repo: testing
+ ref_for_scheduled_build: humble
+ upstream_workspace: ros2_control.humble.repos
+ # we don't test this repository, we just build it
+ not_test_build: true
+ # we don't test the downstream packages, which are outside of our organization
+ downstream_workspace: downstream.humble.repos
+ not_test_downstream: true
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 831ab1737e..0000000000
--- a/.github/workflows/iron-binary-build.yml
+++ /dev/null
@@ -1,46 +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:
- 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 cd610b7960..0000000000
--- a/.github/workflows/iron-debian-build.yml
+++ /dev/null
@@ -1,32 +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:
- 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 control_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 fa30df0e09..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:
- 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 b45a544766..0000000000
--- a/.github/workflows/iron-semi-binary-build.yml
+++ /dev/null
@@ -1,46 +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:
- matrix:
- ROS_DISTRO: [iron]
- ROS_REPO: [main, 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/.github/workflows/jazzy-abi-compatibility.yml b/.github/workflows/jazzy-abi-compatibility.yml
index 367b3736fb..aa0fe81e63 100644
--- a/.github/workflows/jazzy-abi-compatibility.yml
+++ b/.github/workflows/jazzy-abi-compatibility.yml
@@ -14,6 +14,11 @@ on:
- '**/CMakeLists.txt'
- 'ros2_control-not-released.jazzy.repos'
+concurrency:
+ # cancel previous runs of the same workflow, except for pushes on master branch
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ !startsWith(github.ref, '/refs/heads') }}
+
jobs:
abi_check:
runs-on: ubuntu-latest
diff --git a/.github/workflows/jazzy-binary-build.yml b/.github/workflows/jazzy-binary-build.yml
index 5be853ebfc..599a075d9a 100644
--- a/.github/workflows/jazzy-binary-build.yml
+++ b/.github/workflows/jazzy-binary-build.yml
@@ -32,6 +32,11 @@ on:
# Run every morning to detect flakiness and broken dependencies
- cron: '03 1 * * *'
+concurrency:
+ # cancel previous runs of the same workflow, except for pushes on master branch
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ !startsWith(github.ref, '/refs/heads') }}
+
jobs:
binary:
uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
diff --git a/.github/workflows/jazzy-check-docs.yml b/.github/workflows/jazzy-check-docs.yml
index cbdf6c30bd..7910cde0b5 100644
--- a/.github/workflows/jazzy-check-docs.yml
+++ b/.github/workflows/jazzy-check-docs.yml
@@ -10,6 +10,10 @@ on:
- '**.md'
- '**.yaml'
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
jobs:
check-docs:
name: Check Docs
diff --git a/.github/workflows/jazzy-debian-build.yml b/.github/workflows/jazzy-debian-build.yml
index 4ec6a29fff..0f695ed8bc 100644
--- a/.github/workflows/jazzy-debian-build.yml
+++ b/.github/workflows/jazzy-debian-build.yml
@@ -17,6 +17,10 @@ on:
# Run every day to detect flakiness and broken dependencies
- cron: '03 1 * * *'
+concurrency:
+ # cancel previous runs of the same workflow, except for pushes on master branch
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ !startsWith(github.ref, '/refs/heads') }}
jobs:
debian_source_build:
diff --git a/.github/workflows/jazzy-pre-commit.yml b/.github/workflows/jazzy-pre-commit.yml
index d9ec610bbc..aab5ba52ac 100644
--- a/.github/workflows/jazzy-pre-commit.yml
+++ b/.github/workflows/jazzy-pre-commit.yml
@@ -6,6 +6,10 @@ on:
branches:
- master
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
jobs:
pre-commit:
uses: ros-controls/ros2_control_ci/.github/workflows/reusable-pre-commit.yml@master
diff --git a/.github/workflows/jazzy-rhel-binary-build.yml b/.github/workflows/jazzy-rhel-binary-build.yml
index 0dcc912dab..a6404d2dbd 100644
--- a/.github/workflows/jazzy-rhel-binary-build.yml
+++ b/.github/workflows/jazzy-rhel-binary-build.yml
@@ -17,6 +17,11 @@ on:
# Run every day to detect flakiness and broken dependencies
- cron: '03 1 * * *'
+concurrency:
+ # cancel previous runs of the same workflow, except for pushes on master branch
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ !startsWith(github.ref, '/refs/heads') }}
+
jobs:
rhel_semi_binary_build:
uses: ros-controls/ros2_control_ci/.github/workflows/reusable-rhel-binary-build.yml@master
diff --git a/.github/workflows/jazzy-semi-binary-build.yml b/.github/workflows/jazzy-semi-binary-build.yml
index 9634732cf9..d5e3a96835 100644
--- a/.github/workflows/jazzy-semi-binary-build.yml
+++ b/.github/workflows/jazzy-semi-binary-build.yml
@@ -1,6 +1,6 @@
name: Jazzy Semi-Binary Build
# author: Denis Štogl
-# description: 'Build & test all dependencies from released (binary) packages.'
+# description: 'Build & test all ros2_control dependencies from source.'
on:
workflow_dispatch:
@@ -32,16 +32,32 @@ on:
# Run every morning to detect flakiness and broken dependencies
- cron: '03 1 * * *'
+concurrency:
+ # cancel previous runs of the same workflow, except for pushes on master branch
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ !startsWith(github.ref, '/refs/heads') }}
+
jobs:
- binary:
+ semi-binary:
uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
strategy:
fail-fast: false
matrix:
ROS_DISTRO: [jazzy]
- ROS_REPO: [main, testing]
+ ROS_REPO: [testing]
with:
ros_distro: ${{ matrix.ROS_DISTRO }}
ros_repo: ${{ matrix.ROS_REPO }}
upstream_workspace: ros2_control.${{ matrix.ROS_DISTRO }}.repos
ref_for_scheduled_build: master
+ semi-binary-clang:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
+ with:
+ ros_distro: jazzy
+ ros_repo: testing
+ upstream_workspace: ros2_control.jazzy.repos
+ ref_for_scheduled_build: master
+ additional_debs: clang
+ c_compiler: clang
+ cxx_compiler: clang++
+ not_test_build: true
diff --git a/.github/workflows/jazzy-semi-binary-downstream-build.yml b/.github/workflows/jazzy-semi-binary-downstream-build.yml
new file mode 100644
index 0000000000..3bcf417887
--- /dev/null
+++ b/.github/workflows/jazzy-semi-binary-downstream-build.yml
@@ -0,0 +1,49 @@
+name: Jazzy Downstream Build
+# description: 'Build & test downstream packages from source.'
+# author: Christoph Froehlich
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '**.yaml'
+ - '.github/workflows/jazzy-semi-binary-downstream-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros_controls.jazzy.repos'
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build-downstream:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
+ with:
+ ros_distro: jazzy
+ ros_repo: testing
+ ref_for_scheduled_build: master
+ upstream_workspace: ros2_control.jazzy.repos
+ # we don't test this repository, we just build it
+ not_test_build: true
+ # we test the downstream packages, which are part of our organization
+ downstream_workspace: ros_controls.jazzy.repos
+ not_test_downstream: false
+ build-downstream-3rd-party:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
+ with:
+ ros_distro: jazzy
+ ros_repo: testing
+ ref_for_scheduled_build: master
+ upstream_workspace: ros2_control.jazzy.repos
+ # we don't test this repository, we just build it
+ not_test_build: true
+ # we don't test the downstream packages, which are outside of our organization
+ downstream_workspace: downstream.jazzy.repos
+ not_test_downstream: true
diff --git a/.github/workflows/rolling-abi-compatibility.yml b/.github/workflows/rolling-abi-compatibility.yml
index a07fecc660..3e30571ea1 100644
--- a/.github/workflows/rolling-abi-compatibility.yml
+++ b/.github/workflows/rolling-abi-compatibility.yml
@@ -14,6 +14,11 @@ on:
- '**/CMakeLists.txt'
- 'ros2_control-not-released.rolling.repos'
+concurrency:
+ # cancel previous runs of the same workflow, except for pushes on master branch
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ !startsWith(github.ref, '/refs/heads') }}
+
jobs:
abi_check:
runs-on: ubuntu-latest
diff --git a/.github/workflows/rolling-binary-build.yml b/.github/workflows/rolling-binary-build.yml
index 25f2a95b7b..0859169b15 100644
--- a/.github/workflows/rolling-binary-build.yml
+++ b/.github/workflows/rolling-binary-build.yml
@@ -32,10 +32,16 @@ on:
# Run every morning to detect flakiness and broken dependencies
- cron: '03 1 * * *'
+concurrency:
+ # cancel previous runs of the same workflow, except for pushes on master branch
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ !startsWith(github.ref, '/refs/heads') }}
+
jobs:
binary:
uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
strategy:
+ fail-fast: false
matrix:
ROS_DISTRO: [rolling]
ROS_REPO: [main, testing]
diff --git a/.github/workflows/rolling-check-docs.yml b/.github/workflows/rolling-check-docs.yml
index 80e8287abd..8dc02f65c1 100644
--- a/.github/workflows/rolling-check-docs.yml
+++ b/.github/workflows/rolling-check-docs.yml
@@ -10,6 +10,10 @@ on:
- '**.md'
- '**.yaml'
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
jobs:
check-docs:
name: Check Docs
diff --git a/.github/workflows/rolling-compatibility-humble-binary-build.yml b/.github/workflows/rolling-compatibility-humble-binary-build.yml
new file mode 100644
index 0000000000..35957bbc0e
--- /dev/null
+++ b/.github/workflows/rolling-compatibility-humble-binary-build.yml
@@ -0,0 +1,49 @@
+name: Check Rolling Compatibility on Humble
+# author: Christoph Froehlich
+# description: 'Build & test the rolling version on Humble distro.'
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/rolling-compatibility-humble-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.rolling.repos'
+ push:
+ branches:
+ - master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/rolling-compatibility-humble-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.rolling.repos'
+
+concurrency:
+ # cancel previous runs of the same workflow, except for pushes on master branch
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ !startsWith(github.ref, '/refs/heads') }}
+
+jobs:
+ build-on-humble:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
+ strategy:
+ fail-fast: false
+ matrix:
+ ROS_DISTRO: [humble]
+ ROS_REPO: [testing]
+ with:
+ ros_distro: ${{ matrix.ROS_DISTRO }}
+ ros_repo: ${{ matrix.ROS_REPO }}
+ upstream_workspace: ros2_control.rolling.repos
+ ref_for_scheduled_build: master
diff --git a/.github/workflows/rolling-compatibility-jazzy-binary-build.yml b/.github/workflows/rolling-compatibility-jazzy-binary-build.yml
new file mode 100644
index 0000000000..bddbd14878
--- /dev/null
+++ b/.github/workflows/rolling-compatibility-jazzy-binary-build.yml
@@ -0,0 +1,49 @@
+name: Check Rolling Compatibility on Jazzy
+# author: Christoph Froehlich
+# description: 'Build & test the rolling version on Jazzy distro.'
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/rolling-compatibility-jazzy-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.rolling.repos'
+ push:
+ branches:
+ - master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/rolling-compatibility-jazzy-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.rolling.repos'
+
+concurrency:
+ # cancel previous runs of the same workflow, except for pushes on master branch
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ !startsWith(github.ref, '/refs/heads') }}
+
+jobs:
+ build-on-jazzy:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
+ strategy:
+ fail-fast: false
+ matrix:
+ ROS_DISTRO: [jazzy]
+ ROS_REPO: [testing]
+ with:
+ ros_distro: ${{ matrix.ROS_DISTRO }}
+ ros_repo: ${{ matrix.ROS_REPO }}
+ upstream_workspace: ros2_control.rolling.repos
+ ref_for_scheduled_build: master
diff --git a/.github/workflows/rolling-coverage-build.yml b/.github/workflows/rolling-coverage-build.yml
index 45b10876e7..ad4e467606 100644
--- a/.github/workflows/rolling-coverage-build.yml
+++ b/.github/workflows/rolling-coverage-build.yml
@@ -28,6 +28,11 @@ on:
- 'ros2_control.rolling.repos'
- 'codecov.yml'
+concurrency:
+ # cancel previous runs of the same workflow, except for pushes on master branch
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ !startsWith(github.ref, '/refs/heads') }}
+
jobs:
coverage_rolling:
uses: ros-controls/ros2_control_ci/.github/workflows/reusable-build-coverage.yml@master
diff --git a/.github/workflows/rolling-debian-build.yml b/.github/workflows/rolling-debian-build.yml
index 6e4f49191c..a54e7ad0d1 100644
--- a/.github/workflows/rolling-debian-build.yml
+++ b/.github/workflows/rolling-debian-build.yml
@@ -17,11 +17,17 @@ on:
# Run every day to detect flakiness and broken dependencies
- cron: '03 1 * * *'
+concurrency:
+ # cancel previous runs of the same workflow, except for pushes on master branch
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ !startsWith(github.ref, '/refs/heads') }}
+
jobs:
debian_source_build:
uses: ros-controls/ros2_control_ci/.github/workflows/reusable-debian-build.yml@master
strategy:
+ fail-fast: false
matrix:
ROS_DISTRO: [rolling]
with:
diff --git a/.github/workflows/rolling-pre-commit.yml b/.github/workflows/rolling-pre-commit.yml
index 7bc07ba802..792278d6d2 100644
--- a/.github/workflows/rolling-pre-commit.yml
+++ b/.github/workflows/rolling-pre-commit.yml
@@ -6,6 +6,10 @@ on:
branches:
- master
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
jobs:
pre-commit:
uses: ros-controls/ros2_control_ci/.github/workflows/reusable-pre-commit.yml@master
diff --git a/.github/workflows/rolling-rhel-binary-build.yml b/.github/workflows/rolling-rhel-binary-build.yml
index a28ca921bb..95d6759da6 100644
--- a/.github/workflows/rolling-rhel-binary-build.yml
+++ b/.github/workflows/rolling-rhel-binary-build.yml
@@ -17,11 +17,16 @@ on:
# Run every day to detect flakiness and broken dependencies
- cron: '03 1 * * *'
+concurrency:
+ # cancel previous runs of the same workflow, except for pushes on master branch
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ !startsWith(github.ref, '/refs/heads') }}
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: [rolling]
with:
diff --git a/.github/workflows/rolling-semi-binary-build.yml b/.github/workflows/rolling-semi-binary-build.yml
index 6b75e58b5e..6431b9f4fa 100644
--- a/.github/workflows/rolling-semi-binary-build.yml
+++ b/.github/workflows/rolling-semi-binary-build.yml
@@ -1,6 +1,6 @@
name: Rolling Semi-Binary Build
# author: Denis Štogl
-# description: 'Build & test all dependencies from released (binary) packages.'
+# description: 'Build & test all ros2_control dependencies from source.'
on:
workflow_dispatch:
@@ -32,15 +32,33 @@ on:
# Run every morning to detect flakiness and broken dependencies
- cron: '03 1 * * *'
+concurrency:
+ # cancel previous runs of the same workflow, except for pushes on master branch
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ !startsWith(github.ref, '/refs/heads') }}
+
jobs:
- binary:
+ semi-binary:
uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
strategy:
+ fail-fast: false
matrix:
ROS_DISTRO: [rolling]
- ROS_REPO: [main, testing]
+ ROS_REPO: [testing]
with:
ros_distro: ${{ matrix.ROS_DISTRO }}
ros_repo: ${{ matrix.ROS_REPO }}
upstream_workspace: ros2_control.${{ matrix.ROS_DISTRO }}.repos
ref_for_scheduled_build: master
+ semi-binary-clang:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
+ with:
+ # job for building only, no tests -> one distro is enough
+ ros_distro: rolling
+ ros_repo: testing
+ upstream_workspace: ros2_control.rolling.repos
+ ref_for_scheduled_build: master
+ additional_debs: clang
+ c_compiler: clang
+ cxx_compiler: clang++
+ not_test_build: true
diff --git a/.github/workflows/rolling-semi-binary-downstream-build.yml b/.github/workflows/rolling-semi-binary-downstream-build.yml
new file mode 100644
index 0000000000..57db2ae7ba
--- /dev/null
+++ b/.github/workflows/rolling-semi-binary-downstream-build.yml
@@ -0,0 +1,57 @@
+name: Rolling Downstream Build
+# description: 'Build & test downstream packages from source.'
+# author: Christoph Froehlich
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '**.yaml'
+ - '.github/workflows/rolling-semi-binary-downstream-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros_controls.rolling.repos'
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build-downstream:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
+ strategy:
+ fail-fast: false
+ matrix:
+ ROS_DISTRO: [rolling]
+ with:
+ ros_distro: ${{ matrix.ROS_DISTRO }}
+ ros_repo: testing
+ ref_for_scheduled_build: master
+ upstream_workspace: ros2_control.${{ matrix.ROS_DISTRO }}.repos
+ # we don't test this repository, we just build it
+ not_test_build: true
+ # we test the downstream packages, which are part of our organization
+ downstream_workspace: ros_controls.${{ matrix.ROS_DISTRO }}.repos
+ not_test_downstream: false
+ build-downstream-3rd-party:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
+ strategy:
+ fail-fast: false
+ matrix:
+ ROS_DISTRO: [rolling]
+ with:
+ ros_distro: ${{ matrix.ROS_DISTRO }}
+ ros_repo: testing
+ ref_for_scheduled_build: master
+ upstream_workspace: ros2_control.${{ matrix.ROS_DISTRO }}.repos
+ # we don't test this repository, we just build it
+ not_test_build: true
+ # we don't test the downstream packages, which are outside of our organization
+ downstream_workspace: downstream.${{ matrix.ROS_DISTRO }}.repos
+ not_test_downstream: true
diff --git a/.github/workflows/rosdoc2.yml b/.github/workflows/rosdoc2.yml
new file mode 100644
index 0000000000..3452796f55
--- /dev/null
+++ b/.github/workflows/rosdoc2.yml
@@ -0,0 +1,17 @@
+name: rosdoc2
+
+on:
+ workflow_dispatch:
+ pull_request:
+ paths:
+ - ros2_control/doc/**
+ - ros2_control/rosdoc2.yaml
+ - ros2_control/package.xml
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ check:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-rosdoc2.yml@master
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index b3b9424576..75c5402ffe 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@
repos:
# Standard hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.6.0
+ rev: v5.0.0
hooks:
- id: check-added-large-files
- id: check-ast
@@ -26,6 +26,7 @@ repos:
- id: check-symlinks
- id: check-xml
- id: check-yaml
+ args: ["--allow-multiple-documents"]
- id: debug-statements
- id: end-of-file-fixer
- id: mixed-line-ending
@@ -36,7 +37,7 @@ repos:
# Python hooks
- repo: https://github.com/asottile/pyupgrade
- rev: v3.17.0
+ rev: v3.19.0
hooks:
- id: pyupgrade
args: [--py36-plus]
@@ -49,7 +50,7 @@ repos:
args: ["--ignore=D100,D101,D102,D103,D104,D105,D106,D107,D203,D212,D404"]
- repo: https://github.com/psf/black
- rev: 24.8.0
+ rev: 24.10.0
hooks:
- id: black
args: ["--line-length=99"]
@@ -62,7 +63,7 @@ repos:
# CPP hooks
- repo: https://github.com/pre-commit/mirrors-clang-format
- rev: v19.1.0
+ rev: v19.1.4
hooks:
- id: clang-format
args: ['-fallback-style=none', '-i']
@@ -132,7 +133,7 @@ repos:
exclude: CHANGELOG\.rst|\.(svg|pyc|drawio)$
- repo: https://github.com/python-jsonschema/check-jsonschema
- rev: 0.29.3
+ rev: 0.30.0
hooks:
- id: check-github-workflows
args: ["--verbose"]
diff --git a/README.md b/README.md
index e333585d70..c7e74dd506 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_interface/CHANGELOG.rst b/controller_interface/CHANGELOG.rst
index 28619a8cfa..f72fadc0b2 100644
--- a/controller_interface/CHANGELOG.rst
+++ b/controller_interface/CHANGELOG.rst
@@ -2,6 +2,22 @@
Changelog for package controller_interface
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+2.46.0 (2024-12-16)
+-------------------
+* generate version.h file per package using the ament_generate_version_header (backport `#1449 `_) (`#1938 `_)
+* [CI] Add clang job, setup concurrency, use rt_tools humble branch (backport `#1910 `_) (`#1924 `_)
+* Contributors: mergify[bot]
+
+2.45.0 (2024-12-03)
+-------------------
+
+2.44.0 (2024-11-09)
+-------------------
+* Add few warning compiler options to error (backport `#1181 `_) (`#1816 `_)
+* Add -Wconversion flag to protect future developments (`#1053 `_) (`#1815 `_)
+* Add `PoseSensor` semantic component (`#1775 `_) (`#1785 `_)
+* Contributors: mergify[bot]
+
2.43.1 (2024-09-11)
-------------------
diff --git a/controller_interface/CMakeLists.txt b/controller_interface/CMakeLists.txt
index 4f28622bd6..0a380d5a13 100644
--- a/controller_interface/CMakeLists.txt
+++ b/controller_interface/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.5)
project(controller_interface)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
- add_compile_options(-Wall -Wextra)
+ add_compile_options(-Wall -Wextra -Werror=conversion -Werror=unused-but-set-variable -Werror=return-type -Werror=shadow)
endif()
find_package(ament_cmake REQUIRED)
@@ -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(
@@ -102,3 +110,4 @@ ament_export_libraries(
${PROJECT_NAME}
)
ament_package()
+ament_generate_version_header(${PROJECT_NAME})
diff --git a/controller_interface/include/semantic_components/force_torque_sensor.hpp b/controller_interface/include/semantic_components/force_torque_sensor.hpp
index ab55a537ad..c749ed1db8 100644
--- a/controller_interface/include/semantic_components/force_torque_sensor.hpp
+++ b/controller_interface/include/semantic_components/force_torque_sensor.hpp
@@ -62,7 +62,7 @@ class ForceTorqueSensor : public SemanticComponentInterface(std::count(existing_axes_.begin(), existing_axes_.begin() + 3, true));
for (size_t i = 3; i < 6; ++i)
{
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..bbc6963d51 100644
--- a/controller_interface/package.xml
+++ b/controller_interface/package.xml
@@ -2,13 +2,14 @@
controller_interface
- 2.43.1
+ 2.46.0Description of controller_interfaceBence MagyarDenis ŠtoglApache License 2.0ament_cmake
+ ament_cmake_gen_version_hhardware_interfacerclcpp_lifecycle
@@ -22,6 +23,8 @@
rclcpp_lifecycleament_cmake_gmock
+ geometry_msgs
+ sensor_msgsament_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/controller_manager/CHANGELOG.rst b/controller_manager/CHANGELOG.rst
index 7f6778a2eb..a20f1a62d3 100644
--- a/controller_manager/CHANGELOG.rst
+++ b/controller_manager/CHANGELOG.rst
@@ -2,6 +2,44 @@
Changelog for package controller_manager
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+2.46.0 (2024-12-16)
+-------------------
+* Add service call timeout argument in spawner (`#1808 `_) (`#1886 `_)
+* Fix the spawner to support full wildcard parameter entries (backport `#1933 `_) (`#1939 `_)
+* generate version.h file per package using the ament_generate_version_header (backport `#1449 `_) (`#1938 `_)
+* Add documentation on `ros2_control_node` and make lock_memory false by default (backport `#1890 `_) (`#1895 `_)
+* [Spawner] Accept parsing multiple `--param-file` arguments to spawner (backport `#1805 `_) (`#1894 `_)
+* Contributors: mergify[bot]
+
+2.45.0 (2024-12-03)
+-------------------
+* Add CM `switch_controller` service timeout as parameter to spawner.py (backport `#1790 `_) (`#1879 `_)
+* Fix Hardware spawner and add tests for it (backport `#1759 `_) (`#1827 `_)
+* Contributors: mergify[bot]
+
+2.44.0 (2024-11-09)
+-------------------
+* [ros2_control_node] Handle simulation environment clocks (backport `#1810 `_) (`#1862 `_)
+* Change from thread_priority.hpp to realtime_helpers.hpp (backport `#1829 `_) (`#1866 `_)
+* [ros2_control_node] Add option to set the CPU affinity (backport `#1852 `_) (`#1856 `_)
+* [ros2_control_node] Add the realtime_tools lock_memory method to prevent page faults (backport `#1822 `_) (`#1850 `_)
+* fix: typo use thread_priority (backport `#1844 `_) (`#1847 `_)
+* [Spawner] Add support for wildcard entries in the controller param files (`#1724 `_) (`#1835 `_)
+* Add test coverage for `params_file` parameter in spawner/unspawner tests (`#1754 `_) (`#1838 `_)
+* Fix unload of controllers when spawned with `--unload-on-kill` (`#1717 `_) (`#1842 `_)
+* add thread_priority option to the ros2_control_node (`#1820 `_) (`#1824 `_)
+* Add few warning compiler options to error (backport `#1181 `_) (`#1816 `_)
+* Add -Wconversion flag to protect future developments (`#1053 `_) (`#1815 `_)
+* Fix timeout value in std output (backport `#1807 `_) (`#1812 `_)
+* Improve launch utils to support the multiple controller names (`#1782 `_) (`#1783 `_)
+* allow extra spawner arguments to not declare every argument in launch utils (`#1505 `_) (`#1792 `_)
+* Refactor spawner to be able to reuse code for ros2controlcli (backport `#1661 `_) (`#1695 `_)
+* [rqt_controller_manager] Add hardware components (`#1455 `_) (`#1586 `_)
+* [CM] Handle other exceptions while loading the controller plugin (`#1731 `_) (`#1733 `_)
+* [CM] Throw an exception when the components initially fail to be in the required state (backport `#1729 `_) (`#1777 `_)
+* Fix spawner tests timeout on source builds (backport `#1692 `_) (`#1697 `_)
+* Contributors: mergify[bot]
+
2.43.1 (2024-09-11)
-------------------
* controller_manager: Add space to string literal concatenation (`#1245 `_) (`#1747 `_)
diff --git a/controller_manager/CMakeLists.txt b/controller_manager/CMakeLists.txt
index 14bf66d299..4a8b2ad45b 100644
--- a/controller_manager/CMakeLists.txt
+++ b/controller_manager/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.5)
project(controller_manager)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
- add_compile_options(-Wall -Wextra)
+ add_compile_options(-Wall -Wextra -Werror=conversion -Werror=unused-but-set-variable -Werror=return-type -Werror=shadow)
endif()
set(THIS_PACKAGE_INCLUDE_DEPENDS
@@ -175,8 +175,25 @@ if(BUILD_TESTING)
target_link_libraries(test_spawner_unspawner ${PROJECT_NAME} test_controller)
ament_target_dependencies(test_spawner_unspawner ros2_control_test_assets)
+ ament_add_gmock(test_hardware_spawner
+ test/test_hardware_spawner.cpp
+ TIMEOUT 120
+ )
+ target_include_directories(test_hardware_spawner PRIVATE include)
+ target_link_libraries(test_hardware_spawner ${PROJECT_NAME})
+ ament_target_dependencies(test_hardware_spawner ros2_control_test_assets)
+
install(FILES test/test_controller_spawner_with_type.yaml
- DESTINATION test)
+ DESTINATION test)
+
+ install(FILES test/test_controller_spawner_with_basic_controllers.yaml
+ DESTINATION test)
+
+ install(FILES test/test_controller_overriding_parameters.yaml
+ DESTINATION test)
+
+ install(FILES test/test_controller_spawner_wildcard_entries.yaml
+ DESTINATION test)
ament_add_gmock(
test_hardware_management_srvs
@@ -209,3 +226,4 @@ ament_export_dependencies(
${THIS_PACKAGE_INCLUDE_DEPENDS}
)
ament_package()
+ament_generate_version_header(${PROJECT_NAME})
diff --git a/controller_manager/controller_manager/__init__.py b/controller_manager/controller_manager/__init__.py
index f49bed4d34..638a28ce86 100644
--- a/controller_manager/controller_manager/__init__.py
+++ b/controller_manager/controller_manager/__init__.py
@@ -23,6 +23,10 @@
set_hardware_component_state,
switch_controllers,
unload_controller,
+ get_parameter_from_param_files,
+ set_controller_parameters,
+ set_controller_parameters_from_param_files,
+ bcolors,
)
__all__ = [
@@ -36,4 +40,8 @@
"set_hardware_component_state",
"switch_controllers",
"unload_controller",
+ "get_parameter_from_param_files",
+ "set_controller_parameters",
+ "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 00a6f37145..2f0816ff94 100644
--- a/controller_manager/controller_manager/controller_manager_services.py
+++ b/controller_manager/controller_manager/controller_manager_services.py
@@ -26,12 +26,64 @@
)
import rclpy
+import yaml
+from rcl_interfaces.msg import Parameter
+
+# @note: The versions conditioning is added here to support the source-compatibility with Humble
+# The `get_parameter_value` function is moved to `rclpy.parameter` module from `ros2param.api` module from version 3.6.0
+try:
+ from rclpy.parameter import get_parameter_value
+except ImportError:
+ from ros2param.api import get_parameter_value
+from ros2param.api import call_set_parameters
+
+
+# from https://stackoverflow.com/a/287944
+class bcolors:
+ MAGENTA = "\033[95m"
+ OKBLUE = "\033[94m"
+ OKCYAN = "\033[96m"
+ OKGREEN = "\033[92m"
+ WARNING = "\033[93m"
+ FAIL = "\033[91m"
+ ENDC = "\033[0m"
+ BOLD = "\033[1m"
+ UNDERLINE = "\033[4m"
class ServiceNotFoundError(Exception):
pass
+class SingletonServiceCaller:
+ """
+ Singleton class to call services of controller manager.
+
+ This class is used to create a service client for a given service name.
+ If the service client already exists, it returns the existing client.
+ It is used to avoid creating multiple service clients for the same service name.
+
+ It needs Node object, service type and fully qualified service name to create a service client.
+
+ """
+
+ _clients = {}
+
+ def __new__(cls, node, service_type, fully_qualified_service_name):
+ if (node, fully_qualified_service_name) not in cls._clients:
+ cls._clients[(node, fully_qualified_service_name)] = node.create_client(
+ service_type, fully_qualified_service_name
+ )
+ node.get_logger().debug(
+ f"{bcolors.MAGENTA}Creating a new service client : {fully_qualified_service_name} with node : {node.get_name()}{bcolors.ENDC}"
+ )
+
+ node.get_logger().debug(
+ f"{bcolors.OKBLUE}Returning the existing service client : {fully_qualified_service_name} for node : {node.get_name()}{bcolors.ENDC}"
+ )
+ return cls._clients[(node, fully_qualified_service_name)]
+
+
def service_caller(
node,
service_name,
@@ -65,15 +117,23 @@ def service_caller(
@return The service response
"""
- cli = node.create_client(service_type, service_name)
+ namespace = "" if node.get_namespace() == "/" else node.get_namespace()
+ fully_qualified_service_name = (
+ f"{namespace}/{service_name}" if not service_name.startswith("/") else service_name
+ )
+ cli = SingletonServiceCaller(node, service_type, fully_qualified_service_name)
while not cli.service_is_ready():
- node.get_logger().info(f"waiting for service {service_name} to become available...")
+ node.get_logger().info(
+ f"waiting for service {fully_qualified_service_name} to become available..."
+ )
if service_timeout:
if not cli.wait_for_service(service_timeout):
- raise ServiceNotFoundError(f"Could not contact service {service_name}")
+ raise ServiceNotFoundError(
+ f"Could not contact service {fully_qualified_service_name}"
+ )
elif not cli.wait_for_service(10.0):
- node.get_logger().warn(f"Could not contact service {service_name}")
+ node.get_logger().warn(f"Could not contact service {fully_qualified_service_name}")
node.get_logger().debug(f"requester: making request: {request}\n")
future = None
@@ -82,17 +142,19 @@ def service_caller(
rclpy.spin_until_future_complete(node, future, timeout_sec=call_timeout)
if future.result() is None:
node.get_logger().warning(
- f"Failed getting a result from calling {service_name} in "
- f"{service_timeout}. (Attempt {attempt+1} of {max_attempts}.)"
+ f"Failed getting a result from calling {fully_qualified_service_name} in "
+ f"{call_timeout}. (Attempt {attempt+1} of {max_attempts}.)"
)
else:
return future.result()
raise RuntimeError(
- f"Could not successfully call service {service_name} after {max_attempts} attempts."
+ f"Could not successfully call service {fully_qualified_service_name} after {max_attempts} attempts."
)
-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(
@@ -101,10 +163,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,
@@ -112,10 +175,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,
@@ -123,10 +187,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,
@@ -134,10 +201,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,
@@ -145,10 +215,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(
@@ -157,10 +230,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(
@@ -169,11 +245,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
@@ -183,6 +265,8 @@ def set_hardware_component_state(
f"{controller_manager_name}/set_hardware_component_state",
SetHardwareComponentState,
request,
+ service_timeout,
+ call_timeout,
)
@@ -194,6 +278,7 @@ def switch_controllers(
strict,
activate_asap,
timeout,
+ call_timeout=10.0,
):
request = SwitchController.Request()
request.activate_controllers = activate_controllers
@@ -205,11 +290,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(
@@ -218,4 +309,165 @@ def unload_controller(node, controller_manager_name, controller_name, service_ti
UnloadController,
request,
service_timeout,
+ call_timeout,
+ )
+
+
+def get_params_files_with_controller_parameters(
+ node, controller_name: str, namespace: str, parameter_files: list
+):
+ 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 = "/**"
+ ROS_PARAMS_KEY = "ros__parameters"
+ 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 parameter_file in controller_parameter_files:
+ break
+ 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)
+ if WILDCARD_KEY in parameters and ROS_PARAMS_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 WILDCARD_KEY in parameters and ROS_PARAMS_KEY in parameters[WILDCARD_KEY]:
+ controller_param_dict = parameters[WILDCARD_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 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(
+ node, controller_manager_name, controller_name, parameter_name, parameter_value
+):
+ parameter = Parameter()
+ parameter.name = controller_name + "." + parameter_name
+ parameter_string = str(parameter_value)
+ parameter.value = get_parameter_value(string_value=parameter_string)
+
+ response = call_set_parameters(
+ node=node, node_name=controller_manager_name, parameters=[parameter]
)
+ assert len(response.results) == 1
+ result = response.results[0]
+ if result.successful:
+ node.get_logger().info(
+ bcolors.OKCYAN
+ + 'Setting controller param "'
+ + parameter_name
+ + '" to "'
+ + parameter_string
+ + '" for '
+ + bcolors.BOLD
+ + controller_name
+ + bcolors.ENDC
+ )
+ else:
+ node.get_logger().fatal(
+ bcolors.FAIL
+ + 'Could not set controller param "'
+ + parameter_name
+ + '" to "'
+ + parameter_string
+ + '" for '
+ + bcolors.BOLD
+ + controller_name
+ + bcolors.ENDC
+ )
+ return False
+ return True
+
+
+def set_controller_parameters_from_param_files(
+ node, controller_manager_name: str, controller_name: str, parameter_files: list, namespace=None
+):
+ 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",
+ controller_parameter_files,
+ )
+
+ controller_type = get_parameter_from_param_files(
+ node, controller_name, spawner_namespace, controller_parameter_files, "type"
+ )
+ if controller_type and not set_controller_parameters(
+ node, controller_manager_name, controller_name, "type", controller_type
+ ):
+ return False
+ return True
diff --git a/controller_manager/controller_manager/hardware_spawner.py b/controller_manager/controller_manager/hardware_spawner.py
index 3e3a487c6a..4f7afe714c 100644
--- a/controller_manager/controller_manager/hardware_spawner.py
+++ b/controller_manager/controller_manager/hardware_spawner.py
@@ -19,6 +19,7 @@
from controller_manager import (
list_hardware_components,
set_hardware_component_state,
+ bcolors,
)
from controller_manager.controller_manager_services import ServiceNotFoundError
@@ -28,19 +29,6 @@
from rclpy.signals import SignalHandlerOptions
-# from https://stackoverflow.com/a/287944
-class bcolors:
- HEADER = "\033[95m"
- OKBLUE = "\033[94m"
- OKCYAN = "\033[96m"
- OKGREEN = "\033[92m"
- WARNING = "\033[93m"
- FAIL = "\033[91m"
- ENDC = "\033[0m"
- BOLD = "\033[1m"
- UNDERLINE = "\033[4m"
-
-
def first_match(iterable, predicate):
return next((n for n in iterable if predicate(n)), None)
@@ -69,7 +57,7 @@ def has_service_names(node, node_name, node_namespace, service_names):
def is_hardware_component_loaded(
node, controller_manager, hardware_component, service_timeout=0.0
):
- components = list_hardware_components(node, hardware_component, service_timeout).component
+ components = list_hardware_components(node, controller_manager, service_timeout).component
return any(c.name == hardware_component for c in components)
@@ -79,49 +67,45 @@ def handle_set_component_state_service_call(
response = set_hardware_component_state(node, controller_manager_name, component, target_state)
if response.ok and response.state == target_state:
node.get_logger().info(
- bcolors.OKGREEN
- + f"{action} component '{component}'. Hardware now in state: {response.state}."
+ f"{bcolors.OKGREEN}{action} component '{component}'. Hardware now in state: {response.state}.{bcolors.ENDC}"
)
elif response.ok and not response.state == target_state:
node.get_logger().warn(
- bcolors.WARNING
- + f"Could not {action} component '{component}'. Service call returned ok=True, but state: {response.state} is not equal to target state '{target_state}'."
+ f"{bcolors.WARNING}Could not {action} component '{component}'. Service call returned ok=True, but state: {response.state} is not equal to target state '{target_state}'.{bcolors.ENDC}"
)
else:
node.get_logger().warn(
- bcolors.WARNING
- + f"Could not {action} component '{component}'. Service call failed. Wrong component name?"
+ f"{bcolors.WARNING}Could not {action} component '{component}'. Service call failed. Wrong component name?{bcolors.ENDC}"
)
-def activate_components(node, controller_manager_name, components_to_activate):
+def activate_component(node, controller_manager_name, component_to_activate):
active_state = State()
active_state.id = State.PRIMARY_STATE_ACTIVE
active_state.label = "active"
- for component in components_to_activate:
- handle_set_component_state_service_call(
- node, controller_manager_name, component, active_state, "activated"
- )
+ handle_set_component_state_service_call(
+ node, controller_manager_name, component_to_activate, active_state, "activated"
+ )
-def configure_components(node, controller_manager_name, components_to_configure):
+def configure_component(node, controller_manager_name, component_to_configure):
inactive_state = State()
inactive_state.id = State.PRIMARY_STATE_INACTIVE
inactive_state.label = "inactive"
- for component in components_to_configure:
- handle_set_component_state_service_call(
- node, controller_manager_name, component, inactive_state, "configured"
- )
+ handle_set_component_state_service_call(
+ node, controller_manager_name, component_to_configure, inactive_state, "configured"
+ )
def main(args=None):
rclpy.init(args=args, signal_handler_options=SignalHandlerOptions.NO)
parser = argparse.ArgumentParser()
- activate_or_confiigure_grp = parser.add_mutually_exclusive_group(required=True)
+ activate_or_configure_grp = parser.add_mutually_exclusive_group(required=True)
parser.add_argument(
- "hardware_component_name",
- help="The name of the hardware component which should be activated.",
+ "hardware_component_names",
+ help="The name of the hardware components which should be activated.",
+ nargs="+",
)
parser.add_argument(
"-c",
@@ -138,13 +122,13 @@ def main(args=None):
type=float,
)
# add arguments which are mutually exclusive
- activate_or_confiigure_grp.add_argument(
+ activate_or_configure_grp.add_argument(
"--activate",
help="Activates the given components. Note: Components are by default configured before activated. ",
action="store_true",
required=False,
)
- activate_or_confiigure_grp.add_argument(
+ activate_or_configure_grp.add_argument(
"--configure",
help="Configures the given components.",
action="store_true",
@@ -153,9 +137,9 @@ def main(args=None):
command_line_args = rclpy.utilities.remove_ros_args(args=sys.argv)[1:]
args = parser.parse_args(command_line_args)
+ hardware_components = args.hardware_component_names
controller_manager_name = args.controller_manager
controller_manager_timeout = args.controller_manager_timeout
- hardware_component = [args.hardware_component_name]
activate = args.activate
configure = args.configure
@@ -168,28 +152,27 @@ def main(args=None):
controller_manager_name = f"/{controller_manager_name}"
try:
- if not is_hardware_component_loaded(
- node, controller_manager_name, hardware_component, controller_manager_timeout
- ):
- node.get_logger().warn(
- bcolors.WARNING
- + "Hardware Component is not loaded - state can not be changed."
- + bcolors.ENDC
- )
- elif activate:
- activate_components(node, controller_manager_name, hardware_component)
- elif configure:
- configure_components(node, controller_manager_name, hardware_component)
- else:
- node.get_logger().error(
- 'You need to either specify if the hardware component should be activated with the "--activate" flag or configured with the "--configure" flag'
- )
- parser.print_help()
- return 0
+ for hardware_component in hardware_components:
+ if not is_hardware_component_loaded(
+ node, controller_manager_name, hardware_component, controller_manager_timeout
+ ):
+ node.get_logger().warn(
+ f"{bcolors.WARNING}Hardware Component is not loaded - state can not be changed.{bcolors.ENDC}"
+ )
+ elif activate:
+ activate_component(node, controller_manager_name, hardware_component)
+ elif configure:
+ configure_component(node, controller_manager_name, hardware_component)
+ else:
+ node.get_logger().error(
+ f'{bcolors.FAIL}You need to either specify if the hardware component should be activated with the "--activate" flag or configured with the "--configure" flag{bcolors.ENDC}'
+ )
+ parser.print_help()
+ return 0
except KeyboardInterrupt:
pass
except ServiceNotFoundError as err:
- node.get_logger().fatal(str(err))
+ node.get_logger().fatal(f"{bcolors.FAIL}{str(err)}{bcolors.ENDC}")
return 1
finally:
rclpy.shutdown()
diff --git a/controller_manager/controller_manager/launch_utils.py b/controller_manager/controller_manager/launch_utils.py
index e3ef28e928..823181a0ee 100644
--- a/controller_manager/controller_manager/launch_utils.py
+++ b/controller_manager/controller_manager/launch_utils.py
@@ -19,8 +19,11 @@
from launch_ros.actions import Node
-def generate_load_controller_launch_description(
- controller_name, controller_type=None, controller_params_file=None
+def generate_controllers_spawner_launch_description(
+ controller_names: list,
+ controller_type=None,
+ controller_params_files=None,
+ extra_spawner_args=[],
):
"""
Generate launch description for loading a controller using spawner.
@@ -32,14 +35,14 @@ def generate_load_controller_launch_description(
Examples
--------
# Assuming the controller type and controller parameters are known to the controller_manager
- generate_load_controller_launch_description('joint_state_broadcaster')
-
- # Passing controller type and controller parameter file to load
- generate_load_controller_launch_description(
- 'joint_state_broadcaster',
- controller_type='joint_state_broadcaster/JointStateBroadcaster',
- controller_params_file=os.path.join(get_package_share_directory('my_pkg'),
- 'config', 'controller_params.yaml')
+ generate_controllers_spawner_launch_description(['joint_state_broadcaster'])
+
+ # Passing controller type and parameter file to load the controller
+ generate_controllers_spawner_launch_description(
+ ['joint_state_broadcaster'],
+ controller_params_files=[os.path.join(get_package_share_directory('my_pkg'),
+ 'config', 'controller_params.yaml')],
+ extra_spawner_args=[--load-only]
)
"""
@@ -54,17 +57,18 @@ def generate_load_controller_launch_description(
description="Wait until the node is interrupted and then unload controller",
)
- spawner_arguments = [
- controller_name,
- "--controller-manager",
- LaunchConfiguration("controller_manager_name"),
- ]
-
- if controller_type:
- spawner_arguments += ["--controller-type", controller_type]
+ spawner_arguments = controller_names
+ spawner_arguments.extend(
+ [
+ "--controller-manager",
+ LaunchConfiguration("controller_manager_name"),
+ ]
+ )
- 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
@@ -79,6 +83,9 @@ def generate_load_controller_launch_description(
)
]
+ if extra_spawner_args:
+ spawner_arguments += extra_spawner_args
+
spawner = Node(
package="controller_manager",
executable="spawner",
@@ -94,3 +101,54 @@ def generate_load_controller_launch_description(
spawner,
]
)
+
+
+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_type=None, 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_type=controller_type,
+ controller_params_files=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 4b452ef1e0..c7fab71403 100644
--- a/controller_manager/controller_manager/spawner.py
+++ b/controller_manager/controller_manager/spawner.py
@@ -19,7 +19,6 @@
import sys
import time
import warnings
-import yaml
from controller_manager import (
configure_controller,
@@ -27,29 +26,15 @@
load_controller,
switch_controllers,
unload_controller,
+ set_controller_parameters,
+ set_controller_parameters_from_param_files,
+ bcolors,
)
from controller_manager.controller_manager_services import ServiceNotFoundError
import rclpy
-from rcl_interfaces.msg import Parameter
from rclpy.node import Node
from rclpy.signals import SignalHandlerOptions
-from ros2param.api import call_set_parameters
-from ros2param.api import get_parameter_value
-
-# from https://stackoverflow.com/a/287944
-
-
-class bcolors:
- MAGENTA = "\033[95m"
- OKBLUE = "\033[94m"
- OKCYAN = "\033[96m"
- OKGREEN = "\033[92m"
- WARNING = "\033[93m"
- FAIL = "\033[91m"
- ENDC = "\033[0m"
- BOLD = "\033[1m"
- UNDERLINE = "\033[4m"
def first_match(iterable, predicate):
@@ -76,29 +61,15 @@ 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)
-def get_parameter_from_param_file(controller_name, namespace, parameter_file, parameter_name):
- with open(parameter_file) as f:
- namespaced_controller = (
- controller_name if namespace == "/" else f"{namespace}/{controller_name}"
- )
- parameters = yaml.safe_load(f)
- if namespaced_controller in parameters:
- value = parameters[namespaced_controller]
- if not isinstance(value, dict) or "ros__parameters" not in value:
- raise RuntimeError(
- f"YAML file : {parameter_file} is not a valid ROS parameter file for controller : {namespaced_controller}"
- )
- if parameter_name in parameters[namespaced_controller]["ros__parameters"]:
- return parameters[namespaced_controller]["ros__parameters"][parameter_name]
- else:
- return None
-
-
def main(args=None):
rclpy.init(args=args, signal_handler_options=SignalHandlerOptions.NO)
@@ -114,7 +85,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(
@@ -153,9 +128,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(
@@ -170,11 +161,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])
@@ -198,7 +193,11 @@ def main(args=None):
try:
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
@@ -206,75 +205,23 @@ def main(args=None):
+ bcolors.ENDC
)
else:
- controller_type = (
- args.controller_type
- if param_file is None
- else get_parameter_from_param_file(
- controller_name, spawner_namespace, param_file, "type"
- )
- )
- if controller_type:
- parameter = Parameter()
- parameter.name = controller_name + ".type"
- parameter.value = get_parameter_value(string_value=controller_type)
-
- response = call_set_parameters(
- node=node, node_name=controller_manager_name, parameters=[parameter]
- )
- assert len(response.results) == 1
- result = response.results[0]
- if result.successful:
- node.get_logger().info(
- bcolors.OKCYAN
- + 'Set controller type to "'
- + controller_type
- + '" for '
- + bcolors.BOLD
- + controller_name
- + bcolors.ENDC
- )
- else:
- node.get_logger().fatal(
- bcolors.FAIL
- + 'Could not set controller type to "'
- + controller_type
- + '" for '
- + bcolors.BOLD
- + controller_name
- + bcolors.ENDC
- )
+ if args.controller_type:
+ if not set_controller_parameters(
+ node,
+ controller_manager_name,
+ controller_name,
+ "type",
+ args.controller_type,
+ ):
return 1
-
- if param_file:
- parameter = Parameter()
- parameter.name = controller_name + ".params_file"
- parameter.value = get_parameter_value(string_value=param_file)
-
- response = call_set_parameters(
- node=node, node_name=controller_manager_name, parameters=[parameter]
- )
- assert len(response.results) == 1
- result = response.results[0]
- if result.successful:
- node.get_logger().info(
- bcolors.OKCYAN
- + 'Set controller params file to "'
- + param_file
- + '" for '
- + bcolors.BOLD
- + controller_name
- + bcolors.ENDC
- )
- else:
- node.get_logger().fatal(
- bcolors.FAIL
- + 'Could not set controller params file to "'
- + param_file
- + '" for '
- + bcolors.BOLD
- + controller_name
- + bcolors.ENDC
- )
+ if param_files:
+ if not set_controller_parameters_from_param_files(
+ node,
+ controller_manager_name,
+ controller_name,
+ param_files,
+ spawner_namespace,
+ ):
return 1
ret = load_controller(node, controller_manager_name, controller_name)
@@ -292,7 +239,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
@@ -301,7 +254,14 @@ def main(args=None):
if not args.stopped and 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(
@@ -319,7 +279,14 @@ def main(args=None):
if not args.stopped and 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(
@@ -329,7 +296,7 @@ def main(args=None):
node.get_logger().info(
bcolors.OKGREEN
- + "Configured and activated all the parsed controllers list!"
+ + f"Configured and activated all the parsed controllers list : {controller_names}!"
+ bcolors.ENDC
)
if args.stopped:
@@ -347,23 +314,43 @@ 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("Failed to deactivate controller")
+ node.get_logger().error(
+ bcolors.FAIL + "Failed to deactivate controller" + bcolors.ENDC
+ )
return 1
- node.get_logger().info("Deactivated controller")
+ node.get_logger().info(
+ f"Successfully deactivated controllers : {controller_names}"
+ )
elif args.stopped:
node.get_logger().warn('"--stopped" flag is deprecated use "--inactive" instead')
- ret = unload_controller(node, controller_manager_name, controller_name)
- if not ret.ok:
- node.get_logger().error("Failed to unload controller")
- return 1
+ unload_status = True
+ for controller_name in controller_names:
+ ret = unload_controller(node, controller_manager_name, controller_name)
+ if not ret.ok:
+ unload_status = False
+ node.get_logger().error(
+ bcolors.FAIL
+ + f"Failed to unload controller : {controller_name}"
+ + bcolors.ENDC
+ )
- node.get_logger().info("Unloaded controller")
+ if unload_status:
+ node.get_logger().info(f"Successfully unloaded controllers : {controller_names}")
+ else:
+ return 1
return 0
except KeyboardInterrupt:
pass
@@ -371,6 +358,7 @@ def main(args=None):
node.get_logger().fatal(str(err))
return 1
finally:
+ node.destroy_node()
rclpy.shutdown()
diff --git a/controller_manager/controller_manager/unspawner.py b/controller_manager/controller_manager/unspawner.py
index d7cab49cbd..15f0ec2899 100644
--- a/controller_manager/controller_manager/unspawner.py
+++ b/controller_manager/controller_manager/unspawner.py
@@ -37,17 +37,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/images/rqt_controller_manager.png b/controller_manager/doc/images/rqt_controller_manager.png
new file mode 100644
index 0000000000..01c4f55bdf
Binary files /dev/null and b/controller_manager/doc/images/rqt_controller_manager.png differ
diff --git a/controller_manager/doc/userdoc.rst b/controller_manager/doc/userdoc.rst
index f5e962047f..236726217b 100644
--- a/controller_manager/doc/userdoc.rst
+++ b/controller_manager/doc/userdoc.rst
@@ -90,27 +90,112 @@ 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] [--load-only] [--stopped] [-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] [--stopped] [--inactive] [-t CONTROLLER_TYPE] [-u]
+ [--controller-manager-timeout CONTROLLER_MANAGER_TIMEOUT] [--switch-timeout SWITCH_TIMEOUT]
+ [--service-call-timeout SERVICE_CALL_TIMEOUT] [--activate-as-group]
+ controller_names [controller_names ...]
positional arguments:
- controller_name Name of the controller
+ controller_names List of controllers
- 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
-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
+ Namespace for the controller
--load-only Only load the controller and leave unconfigured.
- --stopped Load and configure the controller, however do not start them
+ --stopped Load and configure the controller, however do not activate them
+ --inactive Load and configure the controller, however do not activate them
-t CONTROLLER_TYPE, --controller-type CONTROLLER_TYPE
If not provided it should exist in the controller manager namespace
-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 when switching cannot be performed 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
+
+
+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:
+
+ .. code-block:: yaml
+
+ /**:
+ ros__parameters:
+ type: joint_trajectory_controller/JointTrajectoryController
+
+ command_interfaces:
+ - position
+ .....
+
+ position_trajectory_controller_joint1:
+ ros__parameters:
+ joints:
+ - joint1
+
+ position_trajectory_controller_joint2:
+ ros__parameters:
+ joints:
+ - joint2
+
+ .. code-block:: yaml
+
+ /**/position_trajectory_controller:
+ ros__parameters:
+ type: joint_trajectory_controller/JointTrajectoryController
+ joints:
+ - joint1
+ - joint2
+
+ command_interfaces:
+ - position
+ .....
+
+ .. code-block:: yaml
+
+ /position_trajectory_controller:
+ ros__parameters:
+ type: joint_trajectory_controller/JointTrajectoryController
+ joints:
+ - joint1
+ - joint2
+
+ command_interfaces:
+ - position
+ .....
+
+ .. code-block:: yaml
+
+ position_trajectory_controller:
+ ros__parameters:
+ type: joint_trajectory_controller/JointTrajectoryController
+ joints:
+ - joint1
+ - joint2
+
+ command_interfaces:
+ - position
+ .....
+
+ .. code-block:: yaml
+
+ /rrbot_1/position_trajectory_controller:
+ ros__parameters:
+ type: joint_trajectory_controller/JointTrajectoryController
+ joints:
+ - joint1
+ - joint2
+
+ command_interfaces:
+ - position
+ .....
``unspawner``
^^^^^^^^^^^^^^^^
@@ -118,15 +203,18 @@ There are two scripts to interact with controller manager from launch files:
.. 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``
^^^^^^^^^^^^^^^^^^^^^^
@@ -134,20 +222,40 @@ There are two scripts to interact with controller manager from launch files:
.. code-block:: console
$ ros2 run controller_manager hardware_spawner -h
- usage: hardware_spawner [-h] [-c CONTROLLER_MANAGER] (--activate | --configure) hardware_component_name
+ usage: hardware_spawner [-h] [-c CONTROLLER_MANAGER] [--controller-manager-timeout CONTROLLER_MANAGER_TIMEOUT]
+ (--activate | --configure)
+ hardware_component_names [hardware_component_names ...]
positional arguments:
- hardware_component_name
- The name of the hardware component which should be activated.
+ hardware_component_names
+ The name of the hardware components which should be activated.
options:
-h, --help show this help message and exit
-c CONTROLLER_MANAGER, --controller-manager CONTROLLER_MANAGER
Name of the controller manager ROS node
+ --controller-manager-timeout CONTROLLER_MANAGER_TIMEOUT
+ Time to wait for the controller manager
--activate Activates the given components. Note: Components are by default configured before activated.
--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
-----------------------------------------
@@ -177,6 +285,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 for a non-realtime kernel, true for a realtime kernel)
+ 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/package.xml b/controller_manager/package.xml
index d31b4ee6b5..757ebaa3ab 100644
--- a/controller_manager/package.xml
+++ b/controller_manager/package.xml
@@ -2,13 +2,14 @@
controller_manager
- 2.43.1
+ 2.46.0Description of controller_managerBence MagyarDenis ŠtoglApache License 2.0ament_cmake
+ ament_cmake_gen_version_hament_cmake_pythonament_index_cpp
diff --git a/controller_manager/src/controller_manager.cpp b/controller_manager/src/controller_manager.cpp
index 85aee476a8..1f387b89ee 100644
--- a/controller_manager/src/controller_manager.cpp
+++ b/controller_manager/src/controller_manager.cpp
@@ -24,6 +24,7 @@
#include "controller_manager_msgs/msg/hardware_component_state.hpp"
#include "hardware_interface/types/lifecycle_state_names.hpp"
#include "lifecycle_msgs/msg/state.hpp"
+#include "rcl/arguments.h"
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_lifecycle/state.hpp"
@@ -395,7 +396,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);
}
}
@@ -437,6 +445,8 @@ void ControllerManager::init_resource_manager(const std::string & robot_descript
// BEGIN: Keep old functionality on for backwards compatibility
std::vector activate_components_on_start = std::vector({});
get_parameter("activate_components_on_start", activate_components_on_start);
+ rclcpp_lifecycle::State active_state(
+ State::PRIMARY_STATE_ACTIVE, hardware_interface::lifecycle_state_names::ACTIVE);
if (!activate_components_on_start.empty())
{
RCLCPP_WARN(
@@ -444,8 +454,6 @@ void ControllerManager::init_resource_manager(const std::string & robot_descript
"[Deprecated]: Parameter 'activate_components_on_start' is deprecated. "
"Components are activated per default. Don't use this parameters in combination with the new "
"'hardware_components_initial_state' parameter structure.");
- rclcpp_lifecycle::State active_state(
- State::PRIMARY_STATE_ACTIVE, hardware_interface::lifecycle_state_names::ACTIVE);
for (const auto & component : activate_components_on_start)
{
resource_manager_->set_component_state(component, active_state);
@@ -457,9 +465,14 @@ void ControllerManager::init_resource_manager(const std::string & robot_descript
// activate all other components
for (const auto & [component, state] : components_to_activate)
{
- 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());
+ }
}
}
}
@@ -556,13 +569,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;
@@ -574,16 +595,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());
+ }
}
return add_controller_impl(controller_spec);
@@ -966,7 +999,7 @@ controller_interface::return_type ControllerManager::switch_controller(
auto controller_it = std::find_if(
controllers.begin(), controllers.end(),
std::bind(controller_name_compare, std::placeholders::_1, *ctrl_it));
- controller_interface::return_type ret = controller_interface::return_type::OK;
+ controller_interface::return_type status = controller_interface::return_type::OK;
// if controller is not inactive then do not do any following-controllers checks
if (!is_controller_inactive(controller_it->c))
@@ -976,14 +1009,14 @@ controller_interface::return_type ControllerManager::switch_controller(
"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;
+ status = controller_interface::return_type::ERROR;
}
else
{
- ret = check_following_controllers_for_activate(controllers, strictness, controller_it);
+ status = check_following_controllers_for_activate(controllers, strictness, controller_it);
}
- if (ret != controller_interface::return_type::OK)
+ if (status != controller_interface::return_type::OK)
{
RCLCPP_WARN(
get_logger(),
@@ -1017,7 +1050,7 @@ controller_interface::return_type ControllerManager::switch_controller(
auto controller_it = std::find_if(
controllers.begin(), controllers.end(),
std::bind(controller_name_compare, std::placeholders::_1, *ctrl_it));
- controller_interface::return_type ret = controller_interface::return_type::OK;
+ controller_interface::return_type status = controller_interface::return_type::OK;
// if controller is not active then skip preceding-controllers checks
if (!is_controller_active(controller_it->c))
@@ -1025,14 +1058,14 @@ controller_interface::return_type ControllerManager::switch_controller(
RCLCPP_WARN(
get_logger(), "Controller with name '%s' can not be deactivated since it is not active.",
controller_it->info.name.c_str());
- ret = controller_interface::return_type::ERROR;
+ status = controller_interface::return_type::ERROR;
}
else
{
- ret = check_preceeding_controllers_for_deactivate(controllers, strictness, controller_it);
+ status = check_preceeding_controllers_for_deactivate(controllers, strictness, controller_it);
}
- if (ret != controller_interface::return_type::OK)
+ if (status != controller_interface::return_type::OK)
{
RCLCPP_WARN(
get_logger(),
@@ -1113,11 +1146,11 @@ controller_interface::return_type ControllerManager::switch_controller(
// check for double stop
if (!is_active && in_deactivate_list)
{
- auto ret = handle_conflict(
+ auto conflict_status = handle_conflict(
"Could not deactivate controller '" + controller.info.name + "' since it is not active");
- if (ret != controller_interface::return_type::OK)
+ if (conflict_status != controller_interface::return_type::OK)
{
- return ret;
+ return conflict_status;
}
in_deactivate_list = false;
deactivate_request_.erase(deactivate_list_it);
@@ -1126,11 +1159,11 @@ controller_interface::return_type ControllerManager::switch_controller(
// check for doubled activation
if (is_active && !in_deactivate_list && in_activate_list)
{
- auto ret = handle_conflict(
+ auto conflict_status = handle_conflict(
"Could not activate controller '" + controller.info.name + "' since it is already active");
- if (ret != controller_interface::return_type::OK)
+ if (conflict_status != controller_interface::return_type::OK)
{
- return ret;
+ return conflict_status;
}
in_activate_list = false;
activate_request_.erase(activate_list_it);
@@ -1139,21 +1172,21 @@ controller_interface::return_type ControllerManager::switch_controller(
// check for illegal activation of an unconfigured/finalized controller
if (!is_inactive && !in_deactivate_list && in_activate_list)
{
- auto ret = handle_conflict(
+ auto conflict_status = handle_conflict(
"Could not activate controller '" + controller.info.name +
"' since it is not in inactive state");
- if (ret != controller_interface::return_type::OK)
+ if (conflict_status != controller_interface::return_type::OK)
{
- return ret;
+ return conflict_status;
}
in_activate_list = false;
activate_request_.erase(activate_list_it);
}
const auto extract_interfaces_for_controller =
- [this](const ControllerSpec controller, std::vector & request_interface_list)
+ [this](const ControllerSpec ctrl, std::vector & request_interface_list)
{
- auto command_interface_config = controller.c->command_interface_configuration();
+ auto command_interface_config = ctrl.c->command_interface_configuration();
std::vector command_interface_names = {};
if (command_interface_config.type == controller_interface::interface_configuration_type::ALL)
{
@@ -1753,8 +1786,8 @@ void ControllerManager::reload_controller_libraries_service_cb(
loaded_controllers = get_controller_names();
{
// lock controllers
- std::lock_guard guard(rt_controllers_wrapper_.controllers_lock_);
- for (const auto & controller : rt_controllers_wrapper_.get_updated_list(guard))
+ std::lock_guard ctrl_guard(rt_controllers_wrapper_.controllers_lock_);
+ for (const auto & controller : rt_controllers_wrapper_.get_updated_list(ctrl_guard))
{
if (is_controller_active(*controller.c))
{
@@ -2544,26 +2577,25 @@ rclcpp::NodeOptions ControllerManager::determine_controller_node_options(
.allow_undeclared_parameters(true)
.automatically_declare_parameters_from_overrides(true);
std::vector node_options_arguments = controller_node_options.arguments();
- const std::string ros_args_arg = "--ros-args";
- if (controller.info.parameters_file.has_value())
+ for (const auto & parameters_file : controller.info.parameters_files)
{
- if (!check_for_element(node_options_arguments, ros_args_arg))
+ if (!check_for_element(node_options_arguments, RCL_ROS_ARGS_FLAG))
{
- node_options_arguments.push_back(ros_args_arg);
+ node_options_arguments.push_back(RCL_ROS_ARGS_FLAG);
}
- node_options_arguments.push_back("--params-file");
- node_options_arguments.push_back(controller.info.parameters_file.value());
+ node_options_arguments.push_back(RCL_PARAM_FILE_FLAG);
+ node_options_arguments.push_back(parameters_file);
}
// ensure controller's `use_sim_time` parameter matches controller_manager's
const rclcpp::Parameter use_sim_time = this->get_parameter("use_sim_time");
if (use_sim_time.as_bool())
{
- if (!check_for_element(node_options_arguments, ros_args_arg))
+ if (!check_for_element(node_options_arguments, RCL_ROS_ARGS_FLAG))
{
- node_options_arguments.push_back(ros_args_arg);
+ node_options_arguments.push_back(RCL_ROS_ARGS_FLAG);
}
- node_options_arguments.push_back("-p");
+ node_options_arguments.push_back(RCL_PARAM_FLAG);
node_options_arguments.push_back("use_sim_time:=true");
}
diff --git a/controller_manager/src/ros2_control_node.cpp b/controller_manager/src/ros2_control_node.cpp
index b2126aef28..d6a0b0f3dd 100644
--- a/controller_manager/src/ros2_control_node.cpp
+++ b/controller_manager/src/ros2_control_node.cpp
@@ -21,7 +21,7 @@
#include "controller_manager/controller_manager.hpp"
#include "rclcpp/rclcpp.hpp"
-#include "realtime_tools/thread_priority.hpp"
+#include "realtime_tools/realtime_helpers.hpp"
using namespace std::chrono_literals;
@@ -44,14 +44,38 @@ int main(int argc, char ** argv)
auto cm = std::make_shared(executor, manager_node_name);
+ const bool use_sim_time = cm->get_parameter_or("use_sim_time", false);
+
+ const int cpu_affinity = cm->get_parameter_or("cpu_affinity", -1);
+ if (cpu_affinity >= 0)
+ {
+ const auto affinity_result = realtime_tools::set_current_thread_affinity(cpu_affinity);
+ if (!affinity_result.first)
+ {
+ RCLCPP_WARN(
+ cm->get_logger(), "Unable to set the CPU affinity : '%s'", affinity_result.second.c_str());
+ }
+ }
+ const bool has_realtime = realtime_tools::has_realtime_kernel();
+ const bool lock_memory = cm->get_parameter_or("lock_memory", has_realtime);
+ std::string message;
+ if (lock_memory && !realtime_tools::lock_memory(message))
+ {
+ RCLCPP_WARN(cm->get_logger(), "Unable to lock the memory : '%s'", message.c_str());
+ }
+
RCLCPP_INFO(cm->get_logger(), "update rate is %d Hz", cm->get_update_rate());
+ const int thread_priority = cm->get_parameter_or("thread_priority", kSchedPriority);
+ RCLCPP_INFO(
+ cm->get_logger(), "Spawning %s RT thread with scheduler priority: %d", cm->get_name(),
+ thread_priority);
std::thread cm_thread(
- [cm]()
+ [cm, thread_priority, use_sim_time]()
{
if (realtime_tools::has_realtime_kernel())
{
- if (!realtime_tools::configure_sched_fifo(kSchedPriority))
+ if (!realtime_tools::configure_sched_fifo(thread_priority))
{
RCLCPP_WARN(
cm->get_logger(),
@@ -64,7 +88,7 @@ int main(int argc, char ** argv)
{
RCLCPP_INFO(
cm->get_logger(), "Successful set up FIFO RT scheduling policy with priority %i.",
- kSchedPriority);
+ thread_priority);
}
}
else
@@ -99,7 +123,14 @@ int main(int argc, char ** argv)
// wait until we hit the end of the period
next_iteration_time += period;
- std::this_thread::sleep_until(next_iteration_time);
+ if (use_sim_time)
+ {
+ cm->get_clock()->sleep_until(current_time + period);
+ }
+ else
+ {
+ std::this_thread::sleep_until(next_iteration_time);
+ }
}
});
diff --git a/controller_manager/test/test_chainable_controller/test_chainable_controller.hpp b/controller_manager/test/test_chainable_controller/test_chainable_controller.hpp
index a7205d3024..03dc4812a5 100644
--- a/controller_manager/test/test_chainable_controller/test_chainable_controller.hpp
+++ b/controller_manager/test/test_chainable_controller/test_chainable_controller.hpp
@@ -22,7 +22,7 @@
#include "controller_interface/chainable_controller_interface.hpp"
#include "controller_manager/visibility_control.h"
#include "rclcpp/subscription.hpp"
-#include "realtime_tools/realtime_buffer.h"
+#include "realtime_tools/realtime_buffer.hpp"
#include "std_msgs/msg/float64_multi_array.hpp"
namespace test_chainable_controller
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_wildcard_entries.yaml b/controller_manager/test/test_controller_spawner_wildcard_entries.yaml
new file mode 100644
index 0000000000..1a05ac03d6
--- /dev/null
+++ b/controller_manager/test/test_controller_spawner_wildcard_entries.yaml
@@ -0,0 +1,10 @@
+/**:
+ ros__parameters:
+ type: "controller_manager/test_controller"
+ joint_names: ["joint1"]
+ param1: 1.0
+ param2: 2.0
+
+wildcard_ctrl_3:
+ ros__parameters:
+ param3: 3.0
diff --git a/controller_manager/test/test_controller_spawner_with_basic_controllers.yaml b/controller_manager/test/test_controller_spawner_with_basic_controllers.yaml
new file mode 100644
index 0000000000..bc93f162c1
--- /dev/null
+++ b/controller_manager/test/test_controller_spawner_with_basic_controllers.yaml
@@ -0,0 +1,13 @@
+ctrl_1:
+ ros__parameters:
+ joint_names: ["joint1"]
+
+ctrl_2:
+ ros__parameters:
+ joint_names: ["joint2"]
+ fallback_controllers: ["ctrl_6", "ctrl_7", "ctrl_8"]
+
+ctrl_3:
+ ros__parameters:
+ joint_names: ["joint3"]
+ fallback_controllers: ["ctrl_9"]
diff --git a/controller_manager/test/test_controller_spawner_with_type.yaml b/controller_manager/test/test_controller_spawner_with_type.yaml
index 892427bab7..23fd69b216 100644
--- a/controller_manager/test/test_controller_spawner_with_type.yaml
+++ b/controller_manager/test/test_controller_spawner_with_type.yaml
@@ -2,26 +2,42 @@ ctrl_with_parameters_and_type:
ros__parameters:
type: "controller_manager/test_controller"
joint_names: ["joint0"]
+ interface_name: "position"
-chainable_ctrl_with_parameters_and_type:
- ros__parameters:
- type: "controller_manager/test_chainable_controller"
- joint_names: ["joint1"]
+/**:
+ chainable_ctrl_with_parameters_and_type:
+ ros__parameters:
+ type: "controller_manager/test_chainable_controller"
+ joint_names: ["joint1"]
+
+ wildcard_ctrl_with_parameters_and_type:
+ ros__parameters:
+ type: "controller_manager/test_controller"
+ joint_names: ["joint1"]
ctrl_with_parameters_and_no_type:
ros__parameters:
joint_names: ["joint2"]
-/foo_namespace/ctrl_with_parameters_and_type:
+/foo_namespace/ns_ctrl_with_parameters_and_type:
ros__parameters:
type: "controller_manager/test_controller"
joint_names: ["joint1"]
-/foo_namespace/chainable_ctrl_with_parameters_and_type:
+/foo_namespace/ns_chainable_ctrl_with_parameters_and_type:
+ ros__parameters:
+ type: "controller_manager/test_chainable_controller"
+ joint_names: ["joint1"]
+
+/foo_namespace/ns_ctrl_with_parameters_and_no_type:
+ ros__parameters:
+ joint_names: ["joint2"]
+
+/**/wildcard_chainable_ctrl_with_parameters_and_type:
ros__parameters:
type: "controller_manager/test_chainable_controller"
joint_names: ["joint1"]
-/foo_namespace/ctrl_with_parameters_and_no_type:
+/**/wildcard_ctrl_with_parameters_and_no_type:
ros__parameters:
joint_names: ["joint2"]
diff --git a/controller_manager/test/test_hardware_spawner.cpp b/controller_manager/test/test_hardware_spawner.cpp
new file mode 100644
index 0000000000..b9f3027602
--- /dev/null
+++ b/controller_manager/test/test_hardware_spawner.cpp
@@ -0,0 +1,282 @@
+// Copyright 2021 PAL Robotics S.L.
+//
+// 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
+
+#include
+#include
+#include
+#include
+
+#include "controller_manager/controller_manager.hpp"
+#include "controller_manager_test_common.hpp"
+#include "lifecycle_msgs/msg/state.hpp"
+#include "test_chainable_controller/test_chainable_controller.hpp"
+#include "test_controller/test_controller.hpp"
+
+using ::testing::_;
+using ::testing::Return;
+
+class RMServiceCaller
+{
+public:
+ explicit RMServiceCaller(const std::string & cm_name)
+ {
+ list_srv_node_ = std::make_shared("list_srv_client");
+ srv_executor_.add_node(list_srv_node_);
+ list_hw_components_client_ =
+ list_srv_node_->create_client(
+ cm_name + "/list_hardware_components");
+ }
+
+ lifecycle_msgs::msg::State get_component_state(const std::string & component_name)
+ {
+ auto request =
+ std::make_shared();
+ EXPECT_TRUE(list_hw_components_client_->wait_for_service(std::chrono::milliseconds(500)));
+ auto future = list_hw_components_client_->async_send_request(request);
+ EXPECT_EQ(srv_executor_.spin_until_future_complete(future), rclcpp::FutureReturnCode::SUCCESS);
+ auto result = future.get();
+
+ auto it = std::find_if(
+ std::begin(result->component), std::end(result->component),
+ [&component_name](const auto & cmp) { return cmp.name == component_name; });
+
+ EXPECT_NE(it, std::end(result->component));
+
+ return it->state;
+ };
+
+protected:
+ rclcpp::executors::SingleThreadedExecutor srv_executor_;
+ rclcpp::Node::SharedPtr list_srv_node_;
+ rclcpp::Client::SharedPtr
+ list_hw_components_client_;
+};
+
+using namespace std::chrono_literals;
+class TestHardwareSpawner : public ControllerManagerFixture,
+ public RMServiceCaller
+{
+public:
+ TestHardwareSpawner()
+ : ControllerManagerFixture(), RMServiceCaller(TEST_CM_NAME)
+ {
+ cm_->set_parameter(
+ rclcpp::Parameter("hardware_components_initial_state.unconfigured", "TestSystemHardware"));
+ }
+
+ void SetUp() override
+ {
+ ControllerManagerFixture::SetUp();
+
+ update_executor_ =
+ std::make_shared(rclcpp::ExecutorOptions(), 2);
+
+ update_executor_->add_node(cm_);
+ update_executor_spin_future_ =
+ std::async(std::launch::async, [this]() -> void { update_executor_->spin(); });
+ // This sleep is needed to prevent a too fast test from ending before the
+ // executor has began to spin, which causes it to hang
+ std::this_thread::sleep_for(50ms);
+ }
+
+ void TearDown() override { update_executor_->cancel(); }
+
+protected:
+ // Using a MultiThreadedExecutor so we can call update on a separate thread from service callbacks
+ std::shared_ptr update_executor_;
+ std::future update_executor_spin_future_;
+};
+
+int call_spawner(const std::string extra_args)
+{
+ std::string spawner_script =
+ "python3 -m coverage run --append --branch $(ros2 pkg prefix "
+ "controller_manager)/lib/controller_manager/hardware_spawner ";
+ return std::system((spawner_script + extra_args).c_str());
+}
+
+TEST_F(TestHardwareSpawner, spawner_with_no_arguments_errors)
+{
+ EXPECT_NE(call_spawner(""), 0) << "Missing mandatory arguments";
+}
+
+TEST_F(TestHardwareSpawner, spawner_without_manager_errors_with_given_timeout)
+{
+ EXPECT_NE(call_spawner("TestSystemHardware --controller-manager-timeout 1.0"), 0)
+ << "Wrong controller manager name";
+}
+
+TEST_F(TestHardwareSpawner, spawner_without_component_name_argument)
+{
+ EXPECT_NE(call_spawner("-c test_controller_manager"), 0)
+ << "Missing component name argument parameter";
+}
+
+TEST_F(TestHardwareSpawner, spawner_non_exising_hardware_component)
+{
+ EXPECT_NE(call_spawner("TestSystemHardware1 -c test_controller_manager"), 0)
+ << "Missing component name argument parameter";
+}
+
+TEST_F(TestHardwareSpawner, set_component_to_configured_state_and_back_to_activated)
+{
+ EXPECT_EQ(call_spawner("TestSystemHardware --configure -c test_controller_manager"), 0);
+ EXPECT_EQ(
+ get_component_state("TestSystemHardware").id,
+ lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE);
+
+ EXPECT_EQ(call_spawner("TestSystemHardware --activate -c test_controller_manager"), 0);
+ EXPECT_EQ(
+ get_component_state("TestSystemHardware").id, lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE);
+}
+
+class TestHardwareSpawnerWithoutRobotDescription
+: public ControllerManagerFixture,
+ public RMServiceCaller
+{
+public:
+ TestHardwareSpawnerWithoutRobotDescription()
+ : ControllerManagerFixture(""),
+ RMServiceCaller(TEST_CM_NAME)
+ {
+ cm_->set_parameter(rclcpp::Parameter(
+ "hardware_components_initial_state.unconfigured",
+ std::vector{"TestSystemHardware"}));
+ }
+
+public:
+ void SetUp() override
+ {
+ ControllerManagerFixture::SetUp();
+
+ update_timer_ = cm_->create_wall_timer(
+ std::chrono::milliseconds(10),
+ [&]()
+ {
+ cm_->read(time_, PERIOD);
+ cm_->update(time_, PERIOD);
+ cm_->write(time_, PERIOD);
+ });
+
+ update_executor_ =
+ std::make_shared(rclcpp::ExecutorOptions(), 2);
+
+ update_executor_->add_node(cm_);
+ update_executor_spin_future_ =
+ std::async(std::launch::async, [this]() -> void { update_executor_->spin(); });
+ // This sleep is needed to prevent a too fast test from ending before the
+ // executor has began to spin, which causes it to hang
+ std::this_thread::sleep_for(50ms);
+ }
+
+ void TearDown() override { update_executor_->cancel(); }
+
+ rclcpp::TimerBase::SharedPtr robot_description_sending_timer_;
+
+protected:
+ rclcpp::TimerBase::SharedPtr update_timer_;
+
+ // Using a MultiThreadedExecutor so we can call update on a separate thread from service callbacks
+ std::shared_ptr update_executor_;
+ std::future update_executor_spin_future_;
+};
+
+TEST_F(TestHardwareSpawnerWithoutRobotDescription, when_no_robot_description_spawner_times_out)
+{
+ EXPECT_EQ(
+ call_spawner(
+ "TestSystemHardware --configure -c test_controller_manager --controller-manager-timeout 1.0"),
+ 256)
+ << "could not change hardware state because not robot description and not services for "
+ "controller "
+ "manager are active";
+}
+
+TEST_F(TestHardwareSpawnerWithoutRobotDescription, spawner_with_later_load_of_robot_description)
+{
+ // Delay sending robot description
+ robot_description_sending_timer_ = cm_->create_wall_timer(
+ std::chrono::milliseconds(2500), [&]() { pass_robot_description_to_cm_and_rm(); });
+
+ EXPECT_EQ(
+ call_spawner(
+ "TestSystemHardware --configure -c test_controller_manager --controller-manager-timeout 1.0"),
+ 256)
+ << "could not activate control because not robot description";
+ EXPECT_EQ(
+ call_spawner(
+ "TestSystemHardware --configure -c test_controller_manager --controller-manager-timeout 2.5"),
+ 0);
+ EXPECT_EQ(
+ get_component_state("TestSystemHardware").id,
+ lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE);
+}
+
+class TestHardwareSpawnerWithNamespacedCM
+: public ControllerManagerFixture,
+ public RMServiceCaller
+{
+public:
+ TestHardwareSpawnerWithNamespacedCM()
+ : ControllerManagerFixture(
+ ros2_control_test_assets::minimal_robot_urdf, false, "foo_namespace"),
+ RMServiceCaller("foo_namespace/" + std::string(TEST_CM_NAME))
+ {
+ cm_->set_parameter(
+ rclcpp::Parameter("hardware_components_initial_state.unconfigured", "TestSystemHardware"));
+ }
+
+public:
+ void SetUp() override
+ {
+ ControllerManagerFixture::SetUp();
+
+ update_executor_ =
+ std::make_shared(rclcpp::ExecutorOptions(), 2);
+
+ update_executor_->add_node(cm_);
+ update_executor_spin_future_ =
+ std::async(std::launch::async, [this]() -> void { update_executor_->spin(); });
+ // This sleep is needed to prevent a too fast test from ending before the
+ // executor has began to spin, which causes it to hang
+ std::this_thread::sleep_for(50ms);
+ }
+
+ void TearDown() override { update_executor_->cancel(); }
+
+protected:
+ // Using a MultiThreadedExecutor so we can call update on a separate thread from service callbacks
+ std::shared_ptr update_executor_;
+ std::future update_executor_spin_future_;
+};
+
+TEST_F(TestHardwareSpawnerWithNamespacedCM, set_component_to_configured_state_cm_namespace)
+{
+ ControllerManagerRunner cm_runner(this);
+ EXPECT_EQ(
+ call_spawner(
+ "TestSystemHardware --configure -c test_controller_manager --controller-manager-timeout 1.0"),
+ 256)
+ << "Should fail without defining the namespace";
+ EXPECT_EQ(
+ call_spawner("TestSystemHardware --configure -c test_controller_manager --ros-args -r "
+ "__ns:=/foo_namespace"),
+ 0);
+
+ EXPECT_EQ(
+ get_component_state("TestSystemHardware").id,
+ lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE);
+}
diff --git a/controller_manager/test/test_spawner_unspawner.cpp b/controller_manager/test/test_spawner_unspawner.cpp
index c83f777cfa..8a0c35a6a3 100644
--- a/controller_manager/test/test_spawner_unspawner.cpp
+++ b/controller_manager/test/test_spawner_unspawner.cpp
@@ -33,6 +33,7 @@ const char coveragepy_script[] = "python3 -m coverage run --append --branch";
using namespace std::chrono_literals;
class TestLoadController : public ControllerManagerFixture
{
+public:
void SetUp() override
{
ControllerManagerFixture::SetUp();
@@ -41,9 +42,9 @@ class TestLoadController : public ControllerManagerFixtureread(TIME, PERIOD);
- cm_->update(TIME, PERIOD);
- cm_->write(TIME, PERIOD);
+ cm_->read(time_, PERIOD);
+ cm_->update(time_, PERIOD);
+ cm_->write(time_, PERIOD);
});
update_executor_ =
@@ -219,6 +220,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_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_arg)
{
ControllerManagerRunner cm_runner(this);
@@ -236,11 +281,142 @@ TEST_F(TestLoadController, spawner_test_type_in_arg)
ASSERT_EQ(ctrl_2.c->get_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE);
}
+TEST_F(TestLoadController, 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(
+ "ctrl_with_parameters_and_type chainable_ctrl_with_parameters_and_type --load-only -c "
+ "test_controller_manager -p " +
+ test_file_path),
+ 0);
+
+ ASSERT_EQ(cm_->get_loaded_controllers().size(), 2ul);
+
+ 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_state().id(),
+ lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+ ASSERT_EQ(
+ 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(
+ 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_state().id(),
+ lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+ ASSERT_EQ(
+ cm_->get_parameter("chainable_ctrl_with_parameters_and_type.params_file").as_string_array()[0],
+ test_file_path);
+
+ EXPECT_EQ(
+ call_spawner(
+ "ctrl_with_parameters_and_no_type -c test_controller_manager --controller-manager-timeout "
+ "1.0 -p " +
+ test_file_path),
+ 256);
+ // Will still be same as the current call will fail
+ ASSERT_EQ(cm_->get_loaded_controllers().size(), 2ul);
+
+ auto ctrl_1 = cm_->get_loaded_controllers()[0];
+ ASSERT_EQ(ctrl_1.info.name, "ctrl_with_parameters_and_type");
+ ASSERT_EQ(ctrl_1.info.type, test_controller::TEST_CONTROLLER_CLASS_NAME);
+ ASSERT_EQ(ctrl_1.c->get_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+ ASSERT_EQ(
+ 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");
+ ASSERT_EQ(ctrl_2.info.type, test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ ASSERT_EQ(ctrl_2.c->get_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+ ASSERT_EQ(
+ cm_->get_parameter("chainable_ctrl_with_parameters_and_type.params_file").as_string_array()[0],
+ test_file_path);
+}
+
+TEST_F(TestLoadController, spawner_test_with_wildcard_entries_with_no_ctrl_name)
+{
+ const std::string test_file_path = ament_index_cpp::get_package_prefix("controller_manager") +
+ "/test/test_controller_spawner_wildcard_entries.yaml";
+
+ ControllerManagerRunner cm_runner(this);
+ // Provide controller type via the parsed file
+ EXPECT_EQ(
+ call_spawner(
+ "wildcard_ctrl_1 wildcard_ctrl_2 wildcard_ctrl_3 -c test_controller_manager "
+ "--controller-manager-timeout 1.0 "
+ "-p " +
+ test_file_path),
+ 0);
+
+ auto verify_ctrl_parameter = [](const auto & ctrl_node, bool has_param_3)
+ {
+ 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({"joint1"}));
+
+ if (!ctrl_node->has_parameter("param1"))
+ {
+ ctrl_node->declare_parameter("param1", -10.0);
+ }
+ ASSERT_THAT(ctrl_node->get_parameter("param1").as_double(), 1.0);
+
+ if (!ctrl_node->has_parameter("param2"))
+ {
+ ctrl_node->declare_parameter("param2", -10.0);
+ }
+ ASSERT_THAT(ctrl_node->get_parameter("param2").as_double(), 2.0);
+
+ if (!ctrl_node->has_parameter("param3"))
+ {
+ ctrl_node->declare_parameter("param3", -10.0);
+ }
+ ASSERT_THAT(ctrl_node->get_parameter("param3").as_double(), has_param_3 ? 3.0 : -10.0);
+ };
+
+ ASSERT_EQ(cm_->get_loaded_controllers().size(), 3ul);
+
+ auto wildcard_ctrl_1 = cm_->get_loaded_controllers()[0];
+ ASSERT_EQ(wildcard_ctrl_1.info.name, "wildcard_ctrl_1");
+ ASSERT_EQ(wildcard_ctrl_1.info.type, test_controller::TEST_CONTROLLER_CLASS_NAME);
+ ASSERT_EQ(wildcard_ctrl_1.c->get_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE);
+ verify_ctrl_parameter(wildcard_ctrl_1.c->get_node(), false);
+
+ auto wildcard_ctrl_2 = cm_->get_loaded_controllers()[1];
+ ASSERT_EQ(wildcard_ctrl_2.info.name, "wildcard_ctrl_2");
+ ASSERT_EQ(wildcard_ctrl_2.info.type, test_controller::TEST_CONTROLLER_CLASS_NAME);
+ ASSERT_EQ(wildcard_ctrl_2.c->get_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE);
+ verify_ctrl_parameter(wildcard_ctrl_2.c->get_node(), false);
+
+ auto wildcard_ctrl_3 = cm_->get_loaded_controllers()[2];
+ ASSERT_EQ(wildcard_ctrl_3.info.name, "wildcard_ctrl_3");
+ ASSERT_EQ(wildcard_ctrl_3.info.type, test_controller::TEST_CONTROLLER_CLASS_NAME);
+ ASSERT_EQ(wildcard_ctrl_3.c->get_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE);
+ verify_ctrl_parameter(wildcard_ctrl_3.c->get_node(), true);
+}
+
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);
+ cm_->set_parameter(rclcpp::Parameter("ctrl_3.type", test_controller::TEST_CONTROLLER_CLASS_NAME));
std::stringstream ss;
ss << "timeout --signal=INT 5 "
<< std::string(coveragepy_script) +
@@ -254,6 +430,25 @@ TEST_F(TestLoadController, unload_on_kill)
ASSERT_EQ(cm_->get_loaded_controllers().size(), 0ul);
}
+TEST_F(TestLoadController, unload_on_kill_activate_as_group)
+{
+ // Launch spawner with unload on kill
+ // timeout command will kill it after the specified time with signal SIGINT
+ ControllerManagerRunner cm_runner(this);
+ cm_->set_parameter(rclcpp::Parameter("ctrl_3.type", test_controller::TEST_CONTROLLER_CLASS_NAME));
+ cm_->set_parameter(rclcpp::Parameter("ctrl_2.type", test_controller::TEST_CONTROLLER_CLASS_NAME));
+ std::stringstream ss;
+ ss << "timeout --signal=INT 5 "
+ << std::string(coveragepy_script) +
+ " $(ros2 pkg prefix controller_manager)/lib/controller_manager/spawner "
+ << "ctrl_3 ctrl_2 --activate-as-group -c test_controller_manager --unload-on-kill";
+
+ EXPECT_NE(std::system(ss.str().c_str()), 0)
+ << "timeout should have killed spawner and returned non 0 code";
+
+ ASSERT_EQ(cm_->get_loaded_controllers().size(), 0ul);
+}
+
TEST_F(TestLoadController, spawner_with_many_controllers)
{
std::stringstream ss;
@@ -513,14 +708,14 @@ TEST_F(TestLoadControllerWithNamespacedCM, spawner_test_type_in_params_file)
// Provide controller type via the parsed file
EXPECT_EQ(
call_spawner(
- "ctrl_with_parameters_and_type chainable_ctrl_with_parameters_and_type --load-only -c "
+ "ns_ctrl_with_parameters_and_type ns_chainable_ctrl_with_parameters_and_type --load-only -c "
"test_controller_manager --controller-manager-timeout 1.0 -p " +
test_file_path),
256)
<< "Should fail without the namespacing it";
EXPECT_EQ(
call_spawner(
- "ctrl_with_parameters_and_type chainable_ctrl_with_parameters_and_type --load-only -c "
+ "ns_ctrl_with_parameters_and_type ns_chainable_ctrl_with_parameters_and_type --load-only -c "
"test_controller_manager -p " +
test_file_path + " --ros-args -r __ns:=/foo_namespace"),
0);
@@ -528,25 +723,33 @@ TEST_F(TestLoadControllerWithNamespacedCM, spawner_test_type_in_params_file)
ASSERT_EQ(cm_->get_loaded_controllers().size(), 2ul);
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.name, "ns_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_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_array()[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");
+ chain_ctrl_with_parameters_and_type.info.name, "ns_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_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_array()[0],
+ test_file_path);
EXPECT_EQ(
call_spawner(
- "ctrl_with_parameters_and_no_type -c test_controller_manager -p " + test_file_path +
+ "ns_ctrl_with_parameters_and_no_type -c test_controller_manager -p " + test_file_path +
" --ros-args -r __ns:=/foo_namespace"),
256)
<< "Should fail as no type is defined!";
@@ -554,14 +757,18 @@ TEST_F(TestLoadControllerWithNamespacedCM, spawner_test_type_in_params_file)
ASSERT_EQ(cm_->get_loaded_controllers().size(), 2ul);
auto ctrl_1 = cm_->get_loaded_controllers()[0];
- ASSERT_EQ(ctrl_1.info.name, "ctrl_with_parameters_and_type");
+ ASSERT_EQ(ctrl_1.info.name, "ns_ctrl_with_parameters_and_type");
ASSERT_EQ(ctrl_1.info.type, test_controller::TEST_CONTROLLER_CLASS_NAME);
ASSERT_EQ(ctrl_1.c->get_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+ 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, "chainable_ctrl_with_parameters_and_type");
+ 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_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+ ASSERT_EQ(
+ cm_->get_parameter(ctrl_2.info.name + ".params_file").as_string_array()[0], test_file_path);
}
TEST_F(
@@ -574,28 +781,28 @@ TEST_F(
// Provide controller type via the parsed file
EXPECT_EQ(
call_spawner(
- "ctrl_with_parameters_and_type chainable_ctrl_with_parameters_and_type --load-only -c "
+ "ns_ctrl_with_parameters_and_type ns_chainable_ctrl_with_parameters_and_type --load-only -c "
"test_controller_manager --controller-manager-timeout 1.0 -p " +
test_file_path),
256)
<< "Should fail without the namespacing it";
EXPECT_EQ(
call_spawner(
- "ctrl_with_parameters_and_type chainable_ctrl_with_parameters_and_type --load-only -c "
+ "ns_ctrl_with_parameters_and_type ns_chainable_ctrl_with_parameters_and_type --load-only -c "
"test_controller_manager --namespace foo_namespace --controller-manager-timeout 1.0 -p " +
test_file_path + " --ros-args -r __ns:=/random_namespace"),
256)
<< "Should fail when parsed namespace through both way with different namespaces";
EXPECT_EQ(
call_spawner(
- "ctrl_with_parameters_and_type chainable_ctrl_with_parameters_and_type --load-only -c "
+ "ns_ctrl_with_parameters_and_type ns_chainable_ctrl_with_parameters_and_type --load-only -c "
"test_controller_manager --namespace foo_namespace --controller-manager-timeout 1.0 -p" +
test_file_path + " --ros-args -r __ns:=/foo_namespace"),
256)
<< "Should fail when parsed namespace through both ways even with same namespacing name";
EXPECT_EQ(
call_spawner(
- "ctrl_with_parameters_and_type chainable_ctrl_with_parameters_and_type --load-only -c "
+ "ns_ctrl_with_parameters_and_type ns_chainable_ctrl_with_parameters_and_type --load-only -c "
"test_controller_manager --namespace foo_namespace -p " +
test_file_path),
0)
@@ -604,25 +811,34 @@ TEST_F(
ASSERT_EQ(cm_->get_loaded_controllers().size(), 2ul);
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.name, "ns_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_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_array()[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");
+ chain_ctrl_with_parameters_and_type.info.name, "ns_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_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_array()[0],
+ test_file_path);
EXPECT_EQ(
call_spawner(
- "ctrl_with_parameters_and_no_type -c test_controller_manager --namespace foo_namespace -p " +
+ "ns_ctrl_with_parameters_and_no_type -c test_controller_manager --namespace foo_namespace "
+ "-p " +
test_file_path),
256)
<< "Should fail as no type is defined!";
@@ -630,12 +846,252 @@ TEST_F(
ASSERT_EQ(cm_->get_loaded_controllers().size(), 2ul);
auto ctrl_1 = cm_->get_loaded_controllers()[0];
- ASSERT_EQ(ctrl_1.info.name, "ctrl_with_parameters_and_type");
+ ASSERT_EQ(ctrl_1.info.name, "ns_ctrl_with_parameters_and_type");
ASSERT_EQ(ctrl_1.info.type, test_controller::TEST_CONTROLLER_CLASS_NAME);
ASSERT_EQ(ctrl_1.c->get_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+ 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, "chainable_ctrl_with_parameters_and_type");
+ 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_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+ 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)
+{
+ 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(
+ "wildcard_ctrl_with_parameters_and_type wildcard_chainable_ctrl_with_parameters_and_type "
+ "--load-only -c "
+ "test_controller_manager --controller-manager-timeout 1.0 -p " +
+ test_file_path),
+ 256)
+ << "Should fail without the namespacing it due to timeout but can find the parameters";
+ EXPECT_EQ(
+ call_spawner(
+ "wildcard_ctrl_with_parameters_and_type wildcard_chainable_ctrl_with_parameters_and_type "
+ "--load-only -c "
+ "test_controller_manager -p " +
+ test_file_path + " --ros-args -r __ns:=/foo_namespace"),
+ 0);
+
+ ASSERT_EQ(cm_->get_loaded_controllers().size(), 2ul);
+
+ auto ctrl_with_parameters_and_type = cm_->get_loaded_controllers()[0];
+ ASSERT_EQ(ctrl_with_parameters_and_type.info.name, "wildcard_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_state().id(),
+ lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+
+ auto chain_ctrl_with_parameters_and_type = cm_->get_loaded_controllers()[1];
+ ASSERT_EQ(
+ chain_ctrl_with_parameters_and_type.info.name,
+ "wildcard_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_state().id(),
+ lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+
+ EXPECT_EQ(
+ call_spawner(
+ "wildcard_ctrl_with_parameters_and_no_type -c test_controller_manager -p " + test_file_path +
+ " --ros-args -r __ns:=/foo_namespace"),
+ 256)
+ << "Should fail as no type is defined!";
+ // Will still be same as the current call will fail
+ ASSERT_EQ(cm_->get_loaded_controllers().size(), 2ul);
+
+ auto ctrl_1 = cm_->get_loaded_controllers()[0];
+ ASSERT_EQ(ctrl_1.info.name, "wildcard_ctrl_with_parameters_and_type");
+ ASSERT_EQ(ctrl_1.info.type, test_controller::TEST_CONTROLLER_CLASS_NAME);
+ ASSERT_EQ(ctrl_1.c->get_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
+
+ auto ctrl_2 = cm_->get_loaded_controllers()[1];
+ ASSERT_EQ(ctrl_2.info.name, "wildcard_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_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED);
}
+
+TEST_F(
+ TestLoadControllerWithNamespacedCM,
+ spawner_test_fail_namespaced_controllers_with_non_wildcard_entries)
+{
+ 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(
+ "ctrl_with_parameters_and_type --load-only -c "
+ "test_controller_manager --controller-manager-timeout 1.0 -p " +
+ test_file_path),
+ 256)
+ << "Should fail without the namespacing it";
+ EXPECT_EQ(
+ call_spawner(
+ "ctrl_with_parameters_and_type --load-only -c "
+ "test_controller_manager --namespace foo_namespace -p " +
+ test_file_path),
+ 256)
+ << "Should fail even namespacing it as ctrl_with_parameters_and_type is not a wildcard entry";
+ EXPECT_EQ(
+ call_spawner(
+ "chainable_ctrl_with_parameters_and_type --load-only -c "
+ "test_controller_manager --namespace foo_namespace -p " +
+ test_file_path),
+ 0)
+ << "Should work as chainable_ctrl_with_parameters_and_type is a wildcard entry";
+
+ 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, "chainable_ctrl_with_parameters_and_type");
+ ASSERT_EQ(
+ ctrl_with_parameters_and_type.info.type, test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ ASSERT_EQ(
+ ctrl_with_parameters_and_type.c->get_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 basic_ctrls_test_file_path =
+ ament_index_cpp::get_package_prefix("controller_manager") +
+ "/test/test_controller_spawner_with_basic_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" + basic_ctrls_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_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_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_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], basic_ctrls_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_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], basic_ctrls_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 basic_ctrls_test_file_path =
+ ament_index_cpp::get_package_prefix("controller_manager") +
+ "/test/test_controller_spawner_with_basic_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" + basic_ctrls_test_file_path + " -p" + basic_ctrls_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_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_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_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], basic_ctrls_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_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], basic_ctrls_test_file_path);
+}
diff --git a/controller_manager_msgs/CHANGELOG.rst b/controller_manager_msgs/CHANGELOG.rst
index 1098951237..07bae99e65 100644
--- a/controller_manager_msgs/CHANGELOG.rst
+++ b/controller_manager_msgs/CHANGELOG.rst
@@ -2,6 +2,15 @@
Changelog for package controller_manager_msgs
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+2.46.0 (2024-12-16)
+-------------------
+
+2.45.0 (2024-12-03)
+-------------------
+
+2.44.0 (2024-11-09)
+-------------------
+
2.43.1 (2024-09-11)
-------------------
diff --git a/controller_manager_msgs/package.xml b/controller_manager_msgs/package.xml
index b772a87641..b044709cc4 100644
--- a/controller_manager_msgs/package.xml
+++ b/controller_manager_msgs/package.xml
@@ -2,7 +2,7 @@
controller_manager_msgs
- 2.43.1
+ 2.46.0Messages and services for the controller manager.Bence MagyarDenis Štogl
diff --git a/doc/Doxyfile b/doc/Doxyfile
deleted file mode 100644
index d05c75a0b6..0000000000
--- a/doc/Doxyfile
+++ /dev/null
@@ -1,2579 +0,0 @@
-# Doxyfile 1.8.17
-
-# This file describes the settings to be used by the documentation system
-# doxygen (www.doxygen.org) for a project.
-#
-# All text after a double hash (##) is considered a comment and is placed in
-# front of the TAG it is preceding.
-#
-# All text after a single hash (#) is considered a comment and will be ignored.
-# The format is:
-# TAG = value [value, ...]
-# For lists, items can also be appended using:
-# TAG += value [value, ...]
-# Values that contain spaces should be placed between quotes (\" \").
-
-#---------------------------------------------------------------------------
-# Project related configuration options
-#---------------------------------------------------------------------------
-
-# This tag specifies the encoding used for all characters in the configuration
-# file that follow. The default is UTF-8 which is also the encoding used for all
-# text before the first occurrence of this tag. Doxygen uses libiconv (or the
-# iconv built into libc) for the transcoding. See
-# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
-# The default value is: UTF-8.
-
-DOXYFILE_ENCODING = UTF-8
-
-# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
-# double-quotes, unless you are using Doxywizard) that should identify the
-# project for which the documentation is generated. This name is used in the
-# title of most generated pages and in a few other places.
-# The default value is: My Project.
-
-PROJECT_NAME = "ROS2 Control"
-
-# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
-# could be handy for archiving the generated documentation or if some version
-# control system is used.
-
-PROJECT_NUMBER =
-
-# Using the PROJECT_BRIEF tag one can provide an optional one line description
-# for a project that appears at the top of each page and should give viewer a
-# quick idea about the purpose of the project. Keep the description short.
-
-PROJECT_BRIEF =
-
-# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
-# in the documentation. The maximum height of the logo should not exceed 55
-# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
-# the logo to the output directory.
-
-PROJECT_LOGO =
-
-# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
-# into which the generated documentation will be written. If a relative path is
-# entered, it will be relative to the location where doxygen was started. If
-# left blank the current directory will be used.
-
-OUTPUT_DIRECTORY = doc/_build/
-
-# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
-# directories (in 2 levels) under the output directory of each output format and
-# will distribute the generated files over these directories. Enabling this
-# option can be useful when feeding doxygen a huge amount of source files, where
-# putting all generated files in the same directory would otherwise causes
-# performance problems for the file system.
-# The default value is: NO.
-
-CREATE_SUBDIRS = NO
-
-# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
-# characters to appear in the names of generated files. If set to NO, non-ASCII
-# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
-# U+3044.
-# The default value is: NO.
-
-ALLOW_UNICODE_NAMES = NO
-
-# The OUTPUT_LANGUAGE tag is used to specify the language in which all
-# documentation generated by doxygen is written. Doxygen will use this
-# information to generate all constant output in the proper language.
-# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
-# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
-# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
-# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
-# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
-# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
-# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
-# Ukrainian and Vietnamese.
-# The default value is: English.
-
-OUTPUT_LANGUAGE = English
-
-# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all
-# documentation generated by doxygen is written. Doxygen will use this
-# information to generate all generated output in the proper direction.
-# Possible values are: None, LTR, RTL and Context.
-# The default value is: None.
-
-OUTPUT_TEXT_DIRECTION = None
-
-# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
-# descriptions after the members that are listed in the file and class
-# documentation (similar to Javadoc). Set to NO to disable this.
-# The default value is: YES.
-
-BRIEF_MEMBER_DESC = YES
-
-# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
-# description of a member or function before the detailed description
-#
-# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
-# brief descriptions will be completely suppressed.
-# The default value is: YES.
-
-REPEAT_BRIEF = YES
-
-# This tag implements a quasi-intelligent brief description abbreviator that is
-# used to form the text in various listings. Each string in this list, if found
-# as the leading text of the brief description, will be stripped from the text
-# and the result, after processing the whole list, is used as the annotated
-# text. Otherwise, the brief description is used as-is. If left blank, the
-# following values are used ($name is automatically replaced with the name of
-# the entity):The $name class, The $name widget, The $name file, is, provides,
-# specifies, contains, represents, a, an and the.
-
-ABBREVIATE_BRIEF = "The $name class" \
- "The $name widget" \
- "The $name file" \
- is \
- provides \
- specifies \
- contains \
- represents \
- a \
- an \
- the
-
-# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
-# doxygen will generate a detailed section even if there is only a brief
-# description.
-# The default value is: NO.
-
-ALWAYS_DETAILED_SEC = NO
-
-# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
-# inherited members of a class in the documentation of that class as if those
-# members were ordinary class members. Constructors, destructors and assignment
-# operators of the base classes will not be shown.
-# The default value is: NO.
-
-INLINE_INHERITED_MEMB = NO
-
-# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
-# before files name in the file list and in the header files. If set to NO the
-# shortest path that makes the file name unique will be used
-# The default value is: YES.
-
-FULL_PATH_NAMES = YES
-
-# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
-# Stripping is only done if one of the specified strings matches the left-hand
-# part of the path. The tag can be used to show relative paths in the file list.
-# If left blank the directory from which doxygen is run is used as the path to
-# strip.
-#
-# Note that you can specify absolute paths here, but also relative paths, which
-# will be relative from the directory where doxygen is started.
-# This tag requires that the tag FULL_PATH_NAMES is set to YES.
-
-STRIP_FROM_PATH =
-
-# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
-# path mentioned in the documentation of a class, which tells the reader which
-# header file to include in order to use a class. If left blank only the name of
-# the header file containing the class definition is used. Otherwise one should
-# specify the list of include paths that are normally passed to the compiler
-# using the -I flag.
-
-STRIP_FROM_INC_PATH =
-
-# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
-# less readable) file names. This can be useful is your file systems doesn't
-# support long names like on DOS, Mac, or CD-ROM.
-# The default value is: NO.
-
-SHORT_NAMES = NO
-
-# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
-# first line (until the first dot) of a Javadoc-style comment as the brief
-# description. If set to NO, the Javadoc-style will behave just like regular Qt-
-# style comments (thus requiring an explicit @brief command for a brief
-# description.)
-# The default value is: NO.
-
-JAVADOC_AUTOBRIEF = NO
-
-# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
-# such as
-# /***************
-# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
-# Javadoc-style will behave just like regular comments and it will not be
-# interpreted by doxygen.
-# The default value is: NO.
-
-JAVADOC_BANNER = NO
-
-# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
-# line (until the first dot) of a Qt-style comment as the brief description. If
-# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
-# requiring an explicit \brief command for a brief description.)
-# The default value is: NO.
-
-QT_AUTOBRIEF = NO
-
-# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
-# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
-# a brief description. This used to be the default behavior. The new default is
-# to treat a multi-line C++ comment block as a detailed description. Set this
-# tag to YES if you prefer the old behavior instead.
-#
-# Note that setting this tag to YES also means that rational rose comments are
-# not recognized any more.
-# The default value is: NO.
-
-MULTILINE_CPP_IS_BRIEF = NO
-
-# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
-# documentation from any documented member that it re-implements.
-# The default value is: YES.
-
-INHERIT_DOCS = YES
-
-# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
-# page for each member. If set to NO, the documentation of a member will be part
-# of the file/class/namespace that contains it.
-# The default value is: NO.
-
-SEPARATE_MEMBER_PAGES = NO
-
-# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
-# uses this value to replace tabs by spaces in code fragments.
-# Minimum value: 1, maximum value: 16, default value: 4.
-
-TAB_SIZE = 4
-
-# This tag can be used to specify a number of aliases that act as commands in
-# the documentation. An alias has the form:
-# name=value
-# For example adding
-# "sideeffect=@par Side Effects:\n"
-# will allow you to put the command \sideeffect (or @sideeffect) in the
-# documentation, which will result in a user-defined paragraph with heading
-# "Side Effects:". You can put \n's in the value part of an alias to insert
-# newlines (in the resulting output). You can put ^^ in the value part of an
-# alias to insert a newline as if a physical newline was in the original file.
-# When you need a literal { or } or , in the value part of an alias you have to
-# escape them by means of a backslash (\), this can lead to conflicts with the
-# commands \{ and \} for these it is advised to use the version @{ and @} or use
-# a double escape (\\{ and \\})
-
-ALIASES =
-
-# This tag can be used to specify a number of word-keyword mappings (TCL only).
-# A mapping has the form "name=value". For example adding "class=itcl::class"
-# will allow you to use the command class in the itcl::class meaning.
-
-TCL_SUBST =
-
-# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
-# only. Doxygen will then generate output that is more tailored for C. For
-# instance, some of the names that are used will be different. The list of all
-# members will be omitted, etc.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_FOR_C = NO
-
-# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
-# Python sources only. Doxygen will then generate output that is more tailored
-# for that language. For instance, namespaces will be presented as packages,
-# qualified scopes will look different, etc.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_JAVA = NO
-
-# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
-# sources. Doxygen will then generate output that is tailored for Fortran.
-# The default value is: NO.
-
-OPTIMIZE_FOR_FORTRAN = NO
-
-# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
-# sources. Doxygen will then generate output that is tailored for VHDL.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_VHDL = NO
-
-# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
-# sources only. Doxygen will then generate output that is more tailored for that
-# language. For instance, namespaces will be presented as modules, types will be
-# separated into more groups, etc.
-# The default value is: NO.
-
-OPTIMIZE_OUTPUT_SLICE = NO
-
-# Doxygen selects the parser to use depending on the extension of the files it
-# parses. With this tag you can assign which parser to use for a given
-# extension. Doxygen has a built-in mapping, but you can override or extend it
-# using this tag. The format is ext=language, where ext is a file extension, and
-# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
-# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice,
-# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
-# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
-# tries to guess whether the code is fixed or free formatted code, this is the
-# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat
-# .inc files as Fortran files (default is PHP), and .f files as C (default is
-# Fortran), use: inc=Fortran f=C.
-#
-# Note: For files without extension you can use no_extension as a placeholder.
-#
-# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
-# the files are not read by doxygen.
-
-EXTENSION_MAPPING =
-
-# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
-# according to the Markdown format, which allows for more readable
-# documentation. See https://daringfireball.net/projects/markdown/ for details.
-# The output of markdown processing is further processed by doxygen, so you can
-# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
-# case of backward compatibilities issues.
-# The default value is: YES.
-
-MARKDOWN_SUPPORT = YES
-
-# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
-# to that level are automatically included in the table of contents, even if
-# they do not have an id attribute.
-# Note: This feature currently applies only to Markdown headings.
-# Minimum value: 0, maximum value: 99, default value: 5.
-# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
-
-TOC_INCLUDE_HEADINGS = 5
-
-# When enabled doxygen tries to link words that correspond to documented
-# classes, or namespaces to their corresponding documentation. Such a link can
-# be prevented in individual cases by putting a % sign in front of the word or
-# globally by setting AUTOLINK_SUPPORT to NO.
-# The default value is: YES.
-
-AUTOLINK_SUPPORT = YES
-
-# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
-# to include (a tag file for) the STL sources as input, then you should set this
-# tag to YES in order to let doxygen match functions declarations and
-# definitions whose arguments contain STL classes (e.g. func(std::string);
-# versus func(std::string) {}). This also make the inheritance and collaboration
-# diagrams that involve STL classes more complete and accurate.
-# The default value is: NO.
-
-BUILTIN_STL_SUPPORT = NO
-
-# If you use Microsoft's C++/CLI language, you should set this option to YES to
-# enable parsing support.
-# The default value is: NO.
-
-CPP_CLI_SUPPORT = NO
-
-# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
-# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
-# will parse them like normal C++ but will assume all classes use public instead
-# of private inheritance when no explicit protection keyword is present.
-# The default value is: NO.
-
-SIP_SUPPORT = NO
-
-# For Microsoft's IDL there are propget and propput attributes to indicate
-# getter and setter methods for a property. Setting this option to YES will make
-# doxygen to replace the get and set methods by a property in the documentation.
-# This will only work if the methods are indeed getting or setting a simple
-# type. If this is not the case, or you want to show the methods anyway, you
-# should set this option to NO.
-# The default value is: YES.
-
-IDL_PROPERTY_SUPPORT = YES
-
-# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
-# tag is set to YES then doxygen will reuse the documentation of the first
-# member in the group (if any) for the other members of the group. By default
-# all members of a group must be documented explicitly.
-# The default value is: NO.
-
-DISTRIBUTE_GROUP_DOC = NO
-
-# If one adds a struct or class to a group and this option is enabled, then also
-# any nested class or struct is added to the same group. By default this option
-# is disabled and one has to add nested compounds explicitly via \ingroup.
-# The default value is: NO.
-
-GROUP_NESTED_COMPOUNDS = NO
-
-# Set the SUBGROUPING tag to YES to allow class member groups of the same type
-# (for instance a group of public functions) to be put as a subgroup of that
-# type (e.g. under the Public Functions section). Set it to NO to prevent
-# subgrouping. Alternatively, this can be done per class using the
-# \nosubgrouping command.
-# The default value is: YES.
-
-SUBGROUPING = YES
-
-# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
-# are shown inside the group in which they are included (e.g. using \ingroup)
-# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
-# and RTF).
-#
-# Note that this feature does not work in combination with
-# SEPARATE_MEMBER_PAGES.
-# The default value is: NO.
-
-INLINE_GROUPED_CLASSES = NO
-
-# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
-# with only public data fields or simple typedef fields will be shown inline in
-# the documentation of the scope in which they are defined (i.e. file,
-# namespace, or group documentation), provided this scope is documented. If set
-# to NO, structs, classes, and unions are shown on a separate page (for HTML and
-# Man pages) or section (for LaTeX and RTF).
-# The default value is: NO.
-
-INLINE_SIMPLE_STRUCTS = NO
-
-# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
-# enum is documented as struct, union, or enum with the name of the typedef. So
-# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
-# with name TypeT. When disabled the typedef will appear as a member of a file,
-# namespace, or class. And the struct will be named TypeS. This can typically be
-# useful for C code in case the coding convention dictates that all compound
-# types are typedef'ed and only the typedef is referenced, never the tag name.
-# The default value is: NO.
-
-TYPEDEF_HIDES_STRUCT = NO
-
-# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
-# cache is used to resolve symbols given their name and scope. Since this can be
-# an expensive process and often the same symbol appears multiple times in the
-# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
-# doxygen will become slower. If the cache is too large, memory is wasted. The
-# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
-# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
-# symbols. At the end of a run doxygen will report the cache usage and suggest
-# the optimal cache size from a speed point of view.
-# Minimum value: 0, maximum value: 9, default value: 0.
-
-LOOKUP_CACHE_SIZE = 0
-
-#---------------------------------------------------------------------------
-# Build related configuration options
-#---------------------------------------------------------------------------
-
-# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
-# documentation are documented, even if no documentation was available. Private
-# class members and static file members will be hidden unless the
-# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
-# Note: This will also disable the warnings about undocumented members that are
-# normally produced when WARNINGS is set to YES.
-# The default value is: NO.
-
-EXTRACT_ALL = NO
-
-# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
-# be included in the documentation.
-# The default value is: NO.
-
-EXTRACT_PRIVATE = NO
-
-# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
-# methods of a class will be included in the documentation.
-# The default value is: NO.
-
-EXTRACT_PRIV_VIRTUAL = NO
-
-# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
-# scope will be included in the documentation.
-# The default value is: NO.
-
-EXTRACT_PACKAGE = NO
-
-# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
-# included in the documentation.
-# The default value is: NO.
-
-EXTRACT_STATIC = NO
-
-# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
-# locally in source files will be included in the documentation. If set to NO,
-# only classes defined in header files are included. Does not have any effect
-# for Java sources.
-# The default value is: YES.
-
-EXTRACT_LOCAL_CLASSES = YES
-
-# This flag is only useful for Objective-C code. If set to YES, local methods,
-# which are defined in the implementation section but not in the interface are
-# included in the documentation. If set to NO, only methods in the interface are
-# included.
-# The default value is: NO.
-
-EXTRACT_LOCAL_METHODS = NO
-
-# If this flag is set to YES, the members of anonymous namespaces will be
-# extracted and appear in the documentation as a namespace called
-# 'anonymous_namespace{file}', where file will be replaced with the base name of
-# the file that contains the anonymous namespace. By default anonymous namespace
-# are hidden.
-# The default value is: NO.
-
-EXTRACT_ANON_NSPACES = NO
-
-# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
-# undocumented members inside documented classes or files. If set to NO these
-# members will be included in the various overviews, but no documentation
-# section is generated. This option has no effect if EXTRACT_ALL is enabled.
-# The default value is: NO.
-
-HIDE_UNDOC_MEMBERS = NO
-
-# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
-# undocumented classes that are normally visible in the class hierarchy. If set
-# to NO, these classes will be included in the various overviews. This option
-# has no effect if EXTRACT_ALL is enabled.
-# The default value is: NO.
-
-HIDE_UNDOC_CLASSES = NO
-
-# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
-# declarations. If set to NO, these declarations will be included in the
-# documentation.
-# The default value is: NO.
-
-HIDE_FRIEND_COMPOUNDS = NO
-
-# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
-# documentation blocks found inside the body of a function. If set to NO, these
-# blocks will be appended to the function's detailed documentation block.
-# The default value is: NO.
-
-HIDE_IN_BODY_DOCS = NO
-
-# The INTERNAL_DOCS tag determines if documentation that is typed after a
-# \internal command is included. If the tag is set to NO then the documentation
-# will be excluded. Set it to YES to include the internal documentation.
-# The default value is: NO.
-
-INTERNAL_DOCS = NO
-
-# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
-# names in lower-case letters. If set to YES, upper-case letters are also
-# allowed. This is useful if you have classes or files whose names only differ
-# in case and if your file system supports case sensitive file names. Windows
-# (including Cygwin) ands Mac users are advised to set this option to NO.
-# The default value is: system dependent.
-
-CASE_SENSE_NAMES = YES
-
-# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
-# their full class and namespace scopes in the documentation. If set to YES, the
-# scope will be hidden.
-# The default value is: NO.
-
-HIDE_SCOPE_NAMES = NO
-
-# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
-# append additional text to a page's title, such as Class Reference. If set to
-# YES the compound reference will be hidden.
-# The default value is: NO.
-
-HIDE_COMPOUND_REFERENCE= NO
-
-# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
-# the files that are included by a file in the documentation of that file.
-# The default value is: YES.
-
-SHOW_INCLUDE_FILES = YES
-
-# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
-# grouped member an include statement to the documentation, telling the reader
-# which file to include in order to use the member.
-# The default value is: NO.
-
-SHOW_GROUPED_MEMB_INC = NO
-
-# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
-# files with double quotes in the documentation rather than with sharp brackets.
-# The default value is: NO.
-
-FORCE_LOCAL_INCLUDES = NO
-
-# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
-# documentation for inline members.
-# The default value is: YES.
-
-INLINE_INFO = YES
-
-# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
-# (detailed) documentation of file and class members alphabetically by member
-# name. If set to NO, the members will appear in declaration order.
-# The default value is: YES.
-
-SORT_MEMBER_DOCS = YES
-
-# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
-# descriptions of file, namespace and class members alphabetically by member
-# name. If set to NO, the members will appear in declaration order. Note that
-# this will also influence the order of the classes in the class list.
-# The default value is: NO.
-
-SORT_BRIEF_DOCS = NO
-
-# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
-# (brief and detailed) documentation of class members so that constructors and
-# destructors are listed first. If set to NO the constructors will appear in the
-# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
-# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
-# member documentation.
-# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
-# detailed member documentation.
-# The default value is: NO.
-
-SORT_MEMBERS_CTORS_1ST = NO
-
-# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
-# of group names into alphabetical order. If set to NO the group names will
-# appear in their defined order.
-# The default value is: NO.
-
-SORT_GROUP_NAMES = NO
-
-# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
-# fully-qualified names, including namespaces. If set to NO, the class list will
-# be sorted only by class name, not including the namespace part.
-# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
-# Note: This option applies only to the class list, not to the alphabetical
-# list.
-# The default value is: NO.
-
-SORT_BY_SCOPE_NAME = NO
-
-# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
-# type resolution of all parameters of a function it will reject a match between
-# the prototype and the implementation of a member function even if there is
-# only one candidate or it is obvious which candidate to choose by doing a
-# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
-# accept a match between prototype and implementation in such cases.
-# The default value is: NO.
-
-STRICT_PROTO_MATCHING = NO
-
-# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
-# list. This list is created by putting \todo commands in the documentation.
-# The default value is: YES.
-
-GENERATE_TODOLIST = YES
-
-# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
-# list. This list is created by putting \test commands in the documentation.
-# The default value is: YES.
-
-GENERATE_TESTLIST = YES
-
-# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
-# list. This list is created by putting \bug commands in the documentation.
-# The default value is: YES.
-
-GENERATE_BUGLIST = YES
-
-# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
-# the deprecated list. This list is created by putting \deprecated commands in
-# the documentation.
-# The default value is: YES.
-
-GENERATE_DEPRECATEDLIST= YES
-
-# The ENABLED_SECTIONS tag can be used to enable conditional documentation
-# sections, marked by \if ... \endif and \cond
-# ... \endcond blocks.
-
-ENABLED_SECTIONS =
-
-# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
-# initial value of a variable or macro / define can have for it to appear in the
-# documentation. If the initializer consists of more lines than specified here
-# it will be hidden. Use a value of 0 to hide initializers completely. The
-# appearance of the value of individual variables and macros / defines can be
-# controlled using \showinitializer or \hideinitializer command in the
-# documentation regardless of this setting.
-# Minimum value: 0, maximum value: 10000, default value: 30.
-
-MAX_INITIALIZER_LINES = 30
-
-# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
-# the bottom of the documentation of classes and structs. If set to YES, the
-# list will mention the files that were used to generate the documentation.
-# The default value is: YES.
-
-SHOW_USED_FILES = YES
-
-# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
-# will remove the Files entry from the Quick Index and from the Folder Tree View
-# (if specified).
-# The default value is: YES.
-
-SHOW_FILES = YES
-
-# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
-# page. This will remove the Namespaces entry from the Quick Index and from the
-# Folder Tree View (if specified).
-# The default value is: YES.
-
-SHOW_NAMESPACES = YES
-
-# The FILE_VERSION_FILTER tag can be used to specify a program or script that
-# doxygen should invoke to get the current version for each file (typically from
-# the version control system). Doxygen will invoke the program by executing (via
-# popen()) the command command input-file, where command is the value of the
-# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
-# by doxygen. Whatever the program writes to standard output is used as the file
-# version. For an example see the documentation.
-
-FILE_VERSION_FILTER =
-
-# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
-# by doxygen. The layout file controls the global structure of the generated
-# output files in an output format independent way. To create the layout file
-# that represents doxygen's defaults, run doxygen with the -l option. You can
-# optionally specify a file name after the option, if omitted DoxygenLayout.xml
-# will be used as the name of the layout file.
-#
-# Note that if you run doxygen from a directory containing a file called
-# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
-# tag is left empty.
-
-LAYOUT_FILE =
-
-# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
-# the reference definitions. This must be a list of .bib files. The .bib
-# extension is automatically appended if omitted. This requires the bibtex tool
-# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
-# For LaTeX the style of the bibliography can be controlled using
-# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
-# search path. See also \cite for info how to create references.
-
-CITE_BIB_FILES =
-
-#---------------------------------------------------------------------------
-# Configuration options related to warning and progress messages
-#---------------------------------------------------------------------------
-
-# The QUIET tag can be used to turn on/off the messages that are generated to
-# standard output by doxygen. If QUIET is set to YES this implies that the
-# messages are off.
-# The default value is: NO.
-
-QUIET = NO
-
-# The WARNINGS tag can be used to turn on/off the warning messages that are
-# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
-# this implies that the warnings are on.
-#
-# Tip: Turn warnings on while writing the documentation.
-# The default value is: YES.
-
-WARNINGS = YES
-
-# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
-# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
-# will automatically be disabled.
-# The default value is: YES.
-
-WARN_IF_UNDOCUMENTED = YES
-
-# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
-# potential errors in the documentation, such as not documenting some parameters
-# in a documented function, or documenting parameters that don't exist or using
-# markup commands wrongly.
-# The default value is: YES.
-
-WARN_IF_DOC_ERROR = YES
-
-# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
-# are documented, but have no documentation for their parameters or return
-# value. If set to NO, doxygen will only warn about wrong or incomplete
-# parameter documentation, but not about the absence of documentation. If
-# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
-# The default value is: NO.
-
-WARN_NO_PARAMDOC = NO
-
-# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
-# a warning is encountered.
-# The default value is: NO.
-
-WARN_AS_ERROR = NO
-
-# The WARN_FORMAT tag determines the format of the warning messages that doxygen
-# can produce. The string should contain the $file, $line, and $text tags, which
-# will be replaced by the file and line number from which the warning originated
-# and the warning text. Optionally the format may contain $version, which will
-# be replaced by the version of the file (if it could be obtained via
-# FILE_VERSION_FILTER)
-# The default value is: $file:$line: $text.
-
-WARN_FORMAT = "$file:$line: $text"
-
-# The WARN_LOGFILE tag can be used to specify a file to which warning and error
-# messages should be written. If left blank the output is written to standard
-# error (stderr).
-
-WARN_LOGFILE =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the input files
-#---------------------------------------------------------------------------
-
-# The INPUT tag is used to specify the files and/or directories that contain
-# documented source files. You may enter file names like myfile.cpp or
-# directories like /usr/src/myproject. Separate the files or directories with
-# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
-# Note: If this tag is empty the current directory is searched.
-
-INPUT = README.md .
-
-# This tag can be used to specify the character encoding of the source files
-# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
-# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
-# documentation (see: https://www.gnu.org/software/libiconv/) for the list of
-# possible encodings.
-# The default value is: UTF-8.
-
-INPUT_ENCODING = UTF-8
-
-# If the value of the INPUT tag contains directories, you can use the
-# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
-# *.h) to filter out the source-files in the directories.
-#
-# Note that for custom extensions or not directly supported extensions you also
-# need to set EXTENSION_MAPPING for the extension otherwise the files are not
-# read by doxygen.
-#
-# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
-# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
-# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
-# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
-# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen
-# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f, *.for, *.tcl, *.vhd,
-# *.vhdl, *.ucf, *.qsf and *.ice.
-
-FILE_PATTERNS = *.c \
- *.cc \
- *.cxx \
- *.cpp \
- *.c++ \
- *.java \
- *.ii \
- *.ixx \
- *.ipp \
- *.i++ \
- *.inl \
- *.idl \
- *.ddl \
- *.odl \
- *.h \
- *.hh \
- *.hxx \
- *.hpp \
- *.h++ \
- *.cs \
- *.d \
- *.php \
- *.php4 \
- *.php5 \
- *.phtml \
- *.inc \
- *.m \
- *.markdown \
- *.md \
- *.mm \
- *.dox \
- *.doc \
- *.txt \
- *.py \
- *.pyw \
- *.f90 \
- *.f95 \
- *.f03 \
- *.f08 \
- *.f \
- *.for \
- *.tcl \
- *.vhd \
- *.vhdl \
- *.ucf \
- *.qsf \
- *.ice
-
-# The RECURSIVE tag can be used to specify whether or not subdirectories should
-# be searched for input files as well.
-# The default value is: NO.
-
-RECURSIVE = YES
-
-# The EXCLUDE tag can be used to specify files and/or directories that should be
-# excluded from the INPUT source files. This way you can easily exclude a
-# subdirectory from a directory tree whose root is specified with the INPUT tag.
-#
-# Note that relative paths are relative to the directory from which doxygen is
-# run.
-
-EXCLUDE =
-
-# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
-# directories that are symbolic links (a Unix file system feature) are excluded
-# from the input.
-# The default value is: NO.
-
-EXCLUDE_SYMLINKS = NO
-
-# If the value of the INPUT tag contains directories, you can use the
-# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
-# certain files from those directories.
-#
-# Note that the wildcards are matched against the file with absolute path, so to
-# exclude all test directories for example use the pattern */test/*
-
-EXCLUDE_PATTERNS =
-
-# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
-# (namespaces, classes, functions, etc.) that should be excluded from the
-# output. The symbol name can be a fully qualified name, a word, or if the
-# wildcard * is used, a substring. Examples: ANamespace, AClass,
-# AClass::ANamespace, ANamespace::*Test
-#
-# Note that the wildcards are matched against the file with absolute path, so to
-# exclude all test directories use the pattern */test/*
-
-EXCLUDE_SYMBOLS =
-
-# The EXAMPLE_PATH tag can be used to specify one or more files or directories
-# that contain example code fragments that are included (see the \include
-# command).
-
-EXAMPLE_PATH =
-
-# If the value of the EXAMPLE_PATH tag contains directories, you can use the
-# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
-# *.h) to filter out the source-files in the directories. If left blank all
-# files are included.
-
-EXAMPLE_PATTERNS = *
-
-# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
-# searched for input files to be used with the \include or \dontinclude commands
-# irrespective of the value of the RECURSIVE tag.
-# The default value is: NO.
-
-EXAMPLE_RECURSIVE = NO
-
-# The IMAGE_PATH tag can be used to specify one or more files or directories
-# that contain images that are to be included in the documentation (see the
-# \image command).
-
-IMAGE_PATH =
-
-# The INPUT_FILTER tag can be used to specify a program that doxygen should
-# invoke to filter for each input file. Doxygen will invoke the filter program
-# by executing (via popen()) the command:
-#
-#
-#
-# where is the value of the INPUT_FILTER tag, and is the
-# name of an input file. Doxygen will then use the output that the filter
-# program writes to standard output. If FILTER_PATTERNS is specified, this tag
-# will be ignored.
-#
-# Note that the filter must not add or remove lines; it is applied before the
-# code is scanned, but not when the output code is generated. If lines are added
-# or removed, the anchors will not be placed correctly.
-#
-# Note that for custom extensions or not directly supported extensions you also
-# need to set EXTENSION_MAPPING for the extension otherwise the files are not
-# properly processed by doxygen.
-
-INPUT_FILTER =
-
-# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
-# basis. Doxygen will compare the file name with each pattern and apply the
-# filter if there is a match. The filters are a list of the form: pattern=filter
-# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
-# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
-# patterns match the file name, INPUT_FILTER is applied.
-#
-# Note that for custom extensions or not directly supported extensions you also
-# need to set EXTENSION_MAPPING for the extension otherwise the files are not
-# properly processed by doxygen.
-
-FILTER_PATTERNS =
-
-# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
-# INPUT_FILTER) will also be used to filter the input files that are used for
-# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
-# The default value is: NO.
-
-FILTER_SOURCE_FILES = NO
-
-# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
-# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
-# it is also possible to disable source filtering for a specific pattern using
-# *.ext= (so without naming a filter).
-# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
-
-FILTER_SOURCE_PATTERNS =
-
-# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
-# is part of the input, its contents will be placed on the main page
-# (index.html). This can be useful if you have a project on for instance GitHub
-# and want to reuse the introduction page also for the doxygen output.
-
-USE_MDFILE_AS_MAINPAGE = README.md
-
-#---------------------------------------------------------------------------
-# Configuration options related to source browsing
-#---------------------------------------------------------------------------
-
-# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
-# generated. Documented entities will be cross-referenced with these sources.
-#
-# Note: To get rid of all source code in the generated output, make sure that
-# also VERBATIM_HEADERS is set to NO.
-# The default value is: NO.
-
-SOURCE_BROWSER = NO
-
-# Setting the INLINE_SOURCES tag to YES will include the body of functions,
-# classes and enums directly into the documentation.
-# The default value is: NO.
-
-INLINE_SOURCES = NO
-
-# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
-# special comment blocks from generated source code fragments. Normal C, C++ and
-# Fortran comments will always remain visible.
-# The default value is: YES.
-
-STRIP_CODE_COMMENTS = YES
-
-# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
-# entity all documented functions referencing it will be listed.
-# The default value is: NO.
-
-REFERENCED_BY_RELATION = NO
-
-# If the REFERENCES_RELATION tag is set to YES then for each documented function
-# all documented entities called/used by that function will be listed.
-# The default value is: NO.
-
-REFERENCES_RELATION = NO
-
-# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
-# to YES then the hyperlinks from functions in REFERENCES_RELATION and
-# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
-# link to the documentation.
-# The default value is: YES.
-
-REFERENCES_LINK_SOURCE = YES
-
-# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
-# source code will show a tooltip with additional information such as prototype,
-# brief description and links to the definition and documentation. Since this
-# will make the HTML file larger and loading of large files a bit slower, you
-# can opt to disable this feature.
-# The default value is: YES.
-# This tag requires that the tag SOURCE_BROWSER is set to YES.
-
-SOURCE_TOOLTIPS = YES
-
-# If the USE_HTAGS tag is set to YES then the references to source code will
-# point to the HTML generated by the htags(1) tool instead of doxygen built-in
-# source browser. The htags tool is part of GNU's global source tagging system
-# (see https://www.gnu.org/software/global/global.html). You will need version
-# 4.8.6 or higher.
-#
-# To use it do the following:
-# - Install the latest version of global
-# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
-# - Make sure the INPUT points to the root of the source tree
-# - Run doxygen as normal
-#
-# Doxygen will invoke htags (and that will in turn invoke gtags), so these
-# tools must be available from the command line (i.e. in the search path).
-#
-# The result: instead of the source browser generated by doxygen, the links to
-# source code will now point to the output of htags.
-# The default value is: NO.
-# This tag requires that the tag SOURCE_BROWSER is set to YES.
-
-USE_HTAGS = NO
-
-# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
-# verbatim copy of the header file for each class for which an include is
-# specified. Set to NO to disable this.
-# See also: Section \class.
-# The default value is: YES.
-
-VERBATIM_HEADERS = YES
-
-# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
-# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the
-# cost of reduced performance. This can be particularly helpful with template
-# rich C++ code for which doxygen's built-in parser lacks the necessary type
-# information.
-# Note: The availability of this option depends on whether or not doxygen was
-# generated with the -Duse_libclang=ON option for CMake.
-# The default value is: NO.
-
-CLANG_ASSISTED_PARSING = NO
-
-# If clang assisted parsing is enabled you can provide the compiler with command
-# line options that you would normally use when invoking the compiler. Note that
-# the include paths will already be set by doxygen for the files and directories
-# specified with INPUT and INCLUDE_PATH.
-# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
-
-CLANG_OPTIONS =
-
-# If clang assisted parsing is enabled you can provide the clang parser with the
-# path to the compilation database (see:
-# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) used when the files
-# were built. This is equivalent to specifying the "-p" option to a clang tool,
-# such as clang-check. These options will then be passed to the parser.
-# Note: The availability of this option depends on whether or not doxygen was
-# generated with the -Duse_libclang=ON option for CMake.
-
-CLANG_DATABASE_PATH =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the alphabetical class index
-#---------------------------------------------------------------------------
-
-# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
-# compounds will be generated. Enable this if the project contains a lot of
-# classes, structs, unions or interfaces.
-# The default value is: YES.
-
-ALPHABETICAL_INDEX = YES
-
-# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
-# which the alphabetical index list will be split.
-# Minimum value: 1, maximum value: 20, default value: 5.
-# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
-
-COLS_IN_ALPHA_INDEX = 5
-
-# In case all classes in a project start with a common prefix, all classes will
-# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
-# can be used to specify a prefix (or a list of prefixes) that should be ignored
-# while generating the index headers.
-# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
-
-IGNORE_PREFIX =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the HTML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
-# The default value is: YES.
-
-GENERATE_HTML = YES
-
-# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
-# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
-# it.
-# The default directory is: html.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_OUTPUT = html
-
-# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
-# generated HTML page (for example: .htm, .php, .asp).
-# The default value is: .html.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_FILE_EXTENSION = .html
-
-# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
-# each generated HTML page. If the tag is left blank doxygen will generate a
-# standard header.
-#
-# To get valid HTML the header file that includes any scripts and style sheets
-# that doxygen needs, which is dependent on the configuration options used (e.g.
-# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
-# default header using
-# doxygen -w html new_header.html new_footer.html new_stylesheet.css
-# YourConfigFile
-# and then modify the file new_header.html. See also section "Doxygen usage"
-# for information on how to generate the default header that doxygen normally
-# uses.
-# Note: The header is subject to change so you typically have to regenerate the
-# default header when upgrading to a newer version of doxygen. For a description
-# of the possible markers and block names see the documentation.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_HEADER =
-
-# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
-# generated HTML page. If the tag is left blank doxygen will generate a standard
-# footer. See HTML_HEADER for more information on how to generate a default
-# footer and what special commands can be used inside the footer. See also
-# section "Doxygen usage" for information on how to generate the default footer
-# that doxygen normally uses.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_FOOTER =
-
-# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
-# sheet that is used by each HTML page. It can be used to fine-tune the look of
-# the HTML output. If left blank doxygen will generate a default style sheet.
-# See also section "Doxygen usage" for information on how to generate the style
-# sheet that doxygen normally uses.
-# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
-# it is more robust and this tag (HTML_STYLESHEET) will in the future become
-# obsolete.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_STYLESHEET =
-
-# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
-# cascading style sheets that are included after the standard style sheets
-# created by doxygen. Using this option one can overrule certain style aspects.
-# This is preferred over using HTML_STYLESHEET since it does not replace the
-# standard style sheet and is therefore more robust against future updates.
-# Doxygen will copy the style sheet files to the output directory.
-# Note: The order of the extra style sheet files is of importance (e.g. the last
-# style sheet in the list overrules the setting of the previous ones in the
-# list). For an example see the documentation.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_EXTRA_STYLESHEET =
-
-# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
-# other source files which should be copied to the HTML output directory. Note
-# that these files will be copied to the base HTML output directory. Use the
-# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
-# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
-# files will be copied as-is; there are no commands or markers available.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_EXTRA_FILES =
-
-# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
-# will adjust the colors in the style sheet and background images according to
-# this color. Hue is specified as an angle on a colorwheel, see
-# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
-# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
-# purple, and 360 is red again.
-# Minimum value: 0, maximum value: 359, default value: 220.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_HUE = 220
-
-# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
-# in the HTML output. For a value of 0 the output will use grayscales only. A
-# value of 255 will produce the most vivid colors.
-# Minimum value: 0, maximum value: 255, default value: 100.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_SAT = 100
-
-# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
-# luminance component of the colors in the HTML output. Values below 100
-# gradually make the output lighter, whereas values above 100 make the output
-# darker. The value divided by 100 is the actual gamma applied, so 80 represents
-# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
-# change the gamma.
-# Minimum value: 40, maximum value: 240, default value: 80.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_COLORSTYLE_GAMMA = 80
-
-# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
-# page will contain the date and time when the page was generated. Setting this
-# to YES can help to show when doxygen was last run and thus if the
-# documentation is up to date.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_TIMESTAMP = NO
-
-# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
-# documentation will contain a main index with vertical navigation menus that
-# are dynamically created via JavaScript. If disabled, the navigation index will
-# consists of multiple levels of tabs that are statically embedded in every HTML
-# page. Disable this option to support browsers that do not have JavaScript,
-# like the Qt help browser.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_DYNAMIC_MENUS = YES
-
-# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
-# documentation will contain sections that can be hidden and shown after the
-# page has loaded.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_DYNAMIC_SECTIONS = NO
-
-# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
-# shown in the various tree structured indices initially; the user can expand
-# and collapse entries dynamically later on. Doxygen will expand the tree to
-# such a level that at most the specified number of entries are visible (unless
-# a fully collapsed tree already exceeds this amount). So setting the number of
-# entries 1 will produce a full collapsed tree by default. 0 is a special value
-# representing an infinite number of entries and will result in a full expanded
-# tree by default.
-# Minimum value: 0, maximum value: 9999, default value: 100.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_INDEX_NUM_ENTRIES = 100
-
-# If the GENERATE_DOCSET tag is set to YES, additional index files will be
-# generated that can be used as input for Apple's Xcode 3 integrated development
-# environment (see: https://developer.apple.com/xcode/), introduced with OSX
-# 10.5 (Leopard). To create a documentation set, doxygen will generate a
-# Makefile in the HTML output directory. Running make will produce the docset in
-# that directory and running make install will install the docset in
-# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
-# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
-# genXcode/_index.html for more information.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_DOCSET = NO
-
-# This tag determines the name of the docset feed. A documentation feed provides
-# an umbrella under which multiple documentation sets from a single provider
-# (such as a company or product suite) can be grouped.
-# The default value is: Doxygen generated docs.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_FEEDNAME = "Doxygen generated docs"
-
-# This tag specifies a string that should uniquely identify the documentation
-# set bundle. This should be a reverse domain-name style string, e.g.
-# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_BUNDLE_ID = org.doxygen.Project
-
-# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
-# the documentation publisher. This should be a reverse domain-name style
-# string, e.g. com.mycompany.MyDocSet.documentation.
-# The default value is: org.doxygen.Publisher.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_PUBLISHER_ID = org.doxygen.Publisher
-
-# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
-# The default value is: Publisher.
-# This tag requires that the tag GENERATE_DOCSET is set to YES.
-
-DOCSET_PUBLISHER_NAME = Publisher
-
-# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
-# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
-# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
-# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on
-# Windows.
-#
-# The HTML Help Workshop contains a compiler that can convert all HTML output
-# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
-# files are now used as the Windows 98 help format, and will replace the old
-# Windows help format (.hlp) on all Windows platforms in the future. Compressed
-# HTML files also contain an index, a table of contents, and you can search for
-# words in the documentation. The HTML workshop also contains a viewer for
-# compressed HTML files.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_HTMLHELP = NO
-
-# The CHM_FILE tag can be used to specify the file name of the resulting .chm
-# file. You can add a path in front of the file if the result should not be
-# written to the html output directory.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-CHM_FILE =
-
-# The HHC_LOCATION tag can be used to specify the location (absolute path
-# including file name) of the HTML help compiler (hhc.exe). If non-empty,
-# doxygen will try to run the HTML help compiler on the generated index.hhp.
-# The file has to be specified with full path.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-HHC_LOCATION =
-
-# The GENERATE_CHI flag controls if a separate .chi index file is generated
-# (YES) or that it should be included in the master .chm file (NO).
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-GENERATE_CHI = NO
-
-# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
-# and project file content.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-CHM_INDEX_ENCODING =
-
-# The BINARY_TOC flag controls whether a binary table of contents is generated
-# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
-# enables the Previous and Next buttons.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-BINARY_TOC = NO
-
-# The TOC_EXPAND flag can be set to YES to add extra items for group members to
-# the table of contents of the HTML help documentation and to the tree view.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
-
-TOC_EXPAND = NO
-
-# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
-# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
-# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
-# (.qch) of the generated HTML documentation.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_QHP = NO
-
-# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
-# the file name of the resulting .qch file. The path specified is relative to
-# the HTML output folder.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QCH_FILE =
-
-# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
-# Project output. For more information please see Qt Help Project / Namespace
-# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_NAMESPACE = org.doxygen.Project
-
-# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
-# Help Project output. For more information please see Qt Help Project / Virtual
-# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-
-# folders).
-# The default value is: doc.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_VIRTUAL_FOLDER = doc
-
-# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
-# filter to add. For more information please see Qt Help Project / Custom
-# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
-# filters).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_CUST_FILTER_NAME =
-
-# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
-# custom filter to add. For more information please see Qt Help Project / Custom
-# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
-# filters).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_CUST_FILTER_ATTRS =
-
-# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
-# project's filter section matches. Qt Help Project / Filter Attributes (see:
-# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHP_SECT_FILTER_ATTRS =
-
-# The QHG_LOCATION tag can be used to specify the location of Qt's
-# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
-# generated .qhp file.
-# This tag requires that the tag GENERATE_QHP is set to YES.
-
-QHG_LOCATION =
-
-# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
-# generated, together with the HTML files, they form an Eclipse help plugin. To
-# install this plugin and make it available under the help contents menu in
-# Eclipse, the contents of the directory containing the HTML and XML files needs
-# to be copied into the plugins directory of eclipse. The name of the directory
-# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
-# After copying Eclipse needs to be restarted before the help appears.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_ECLIPSEHELP = NO
-
-# A unique identifier for the Eclipse help plugin. When installing the plugin
-# the directory name containing the HTML and XML files should also have this
-# name. Each documentation set should have its own identifier.
-# The default value is: org.doxygen.Project.
-# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
-
-ECLIPSE_DOC_ID = org.doxygen.Project
-
-# If you want full control over the layout of the generated HTML pages it might
-# be necessary to disable the index and replace it with your own. The
-# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
-# of each HTML page. A value of NO enables the index and the value YES disables
-# it. Since the tabs in the index contain the same information as the navigation
-# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-DISABLE_INDEX = NO
-
-# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
-# structure should be generated to display hierarchical information. If the tag
-# value is set to YES, a side panel will be generated containing a tree-like
-# index structure (just like the one that is generated for HTML Help). For this
-# to work a browser that supports JavaScript, DHTML, CSS and frames is required
-# (i.e. any modern browser). Windows users are probably better off using the
-# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
-# further fine-tune the look of the index. As an example, the default style
-# sheet generated by doxygen has an example that shows how to put an image at
-# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
-# the same information as the tab index, you could consider setting
-# DISABLE_INDEX to YES when enabling this option.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-GENERATE_TREEVIEW = NO
-
-# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
-# doxygen will group on one line in the generated HTML documentation.
-#
-# Note that a value of 0 will completely suppress the enum values from appearing
-# in the overview section.
-# Minimum value: 0, maximum value: 20, default value: 4.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-ENUM_VALUES_PER_LINE = 4
-
-# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
-# to set the initial width (in pixels) of the frame in which the tree is shown.
-# Minimum value: 0, maximum value: 1500, default value: 250.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-TREEVIEW_WIDTH = 250
-
-# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
-# external symbols imported via tag files in a separate window.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-EXT_LINKS_IN_WINDOW = NO
-
-# Use this tag to change the font size of LaTeX formulas included as images in
-# the HTML documentation. When you change the font size after a successful
-# doxygen run you need to manually remove any form_*.png images from the HTML
-# output directory to force them to be regenerated.
-# Minimum value: 8, maximum value: 50, default value: 10.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-FORMULA_FONTSIZE = 10
-
-# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
-# generated for formulas are transparent PNGs. Transparent PNGs are not
-# supported properly for IE 6.0, but are supported on all modern browsers.
-#
-# Note that when changing this option you need to delete any form_*.png files in
-# the HTML output directory before the changes have effect.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-FORMULA_TRANSPARENT = YES
-
-# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
-# to create new LaTeX commands to be used in formulas as building blocks. See
-# the section "Including formulas" for details.
-
-FORMULA_MACROFILE =
-
-# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
-# https://www.mathjax.org) which uses client side JavaScript for the rendering
-# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
-# installed or if you want to formulas look prettier in the HTML output. When
-# enabled you may also need to install MathJax separately and configure the path
-# to it using the MATHJAX_RELPATH option.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-USE_MATHJAX = NO
-
-# When MathJax is enabled you can set the default output format to be used for
-# the MathJax output. See the MathJax site (see:
-# http://docs.mathjax.org/en/latest/output.html) for more details.
-# Possible values are: HTML-CSS (which is slower, but has the best
-# compatibility), NativeMML (i.e. MathML) and SVG.
-# The default value is: HTML-CSS.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_FORMAT = HTML-CSS
-
-# When MathJax is enabled you need to specify the location relative to the HTML
-# output directory using the MATHJAX_RELPATH option. The destination directory
-# should contain the MathJax.js script. For instance, if the mathjax directory
-# is located at the same level as the HTML output directory, then
-# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
-# Content Delivery Network so you can quickly see the result without installing
-# MathJax. However, it is strongly recommended to install a local copy of
-# MathJax from https://www.mathjax.org before deployment.
-# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/
-
-# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
-# extension names that should be enabled during MathJax rendering. For example
-# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_EXTENSIONS =
-
-# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
-# of code that will be used on startup of the MathJax code. See the MathJax site
-# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
-# example see the documentation.
-# This tag requires that the tag USE_MATHJAX is set to YES.
-
-MATHJAX_CODEFILE =
-
-# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
-# the HTML output. The underlying search engine uses javascript and DHTML and
-# should work on any modern browser. Note that when using HTML help
-# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
-# there is already a search function so this one should typically be disabled.
-# For large projects the javascript based search engine can be slow, then
-# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
-# search using the keyboard; to jump to the search box use + S
-# (what the is depends on the OS and browser, but it is typically
-# , /