diff --git a/.github/reviewer-lottery.yml b/.github/reviewer-lottery.yml
deleted file mode 100644
index 2da79efd9a..0000000000
--- a/.github/reviewer-lottery.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-groups:
- # Default reviewers for all pull requests.
- # Usually, at least on of the maintainers should approve PR before merging.
- # The best is if two maintainers do that.
- - name: maintainers # name of the group
- reviewers: 2 # how many reviewers do you want to assign?
- internal_reviewers: 1 # how many reviewers do you want to assign when the PR author belongs to this group?
- usernames: # github usernames of the reviewers
- - bmagyar
- - destogl
-
- # Reviewers group to get broader feedback.
- - name: reviewers
- reviewers: 5
- usernames:
- - aprotyas
- - arne48
- - bijoua29
- - christophfroehlich
- - DasRoteSkelett
- - erickisos
- - fmauch
- - jaron-l
- - livanov93
- - mcbed
- - moriarty
- - olivier-stasse
- - peterdavidfagan
- - progtologist
- - saikishor
- - VanshGehlot
- - VX792
diff --git a/.github/workflows/README.md b/.github/workflows/README.md
index 5e8c8ef0fd..62007ffc2d 100644
--- a/.github/workflows/README.md
+++ b/.github/workflows/README.md
@@ -1,6 +1,7 @@
ROS2 Distro | Branch | Build status | Documentation | Released packages
:---------: | :----: | :----------: | :-----------: | :---------------:
-**Rolling** | [`master`](https://github.com/ros-controls/ros2_control/tree/master) | [](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-binary-build-main.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-binary-build-testing.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-semi-binary-build-main.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-semi-binary-build-testing.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-source-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)
-**Iron** | [`master`](https://github.com/ros-controls/ros2_control/tree/master) | [](https://github.com/ros-controls/ros2_control/actions/workflows/iron-binary-build-main.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/iron-binary-build-testing.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/iron-semi-binary-build-main.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/iron-semi-binary-build-testing.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/iron-source-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/#iron)
-**Humble** | [`humble`](https://github.com/ros-controls/ros2_control/tree/humble) | [](https://github.com/ros-controls/ros2_control/actions/workflows/humble-binary-build-main.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/humble-binary-build-testing.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/humble-semi-binary-build-main.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/humble-semi-binary-build-testing.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/humble-source-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)
+**Rolling** | [`master`](https://github.com/ros-controls/ros2_control/tree/master) | [](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-binary-build.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-semi-binary-build.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-source-build.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-debian-build.yml)
[](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) | [](https://github.com/ros-controls/ros2_control/actions/workflows/jazzy-binary-build.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/jazzy-semi-binary-build.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/jazzy-source-build.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/jazzy-debian-build.yml)
[](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) | [](https://github.com/ros-controls/ros2_control/actions/workflows/iron-binary-build.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/iron-semi-binary-build.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/iron-source-build.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/iron-debian-build.yml)
[](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) | [](https://github.com/ros-controls/ros2_control/actions/workflows/humble-binary-build.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/humble-semi-binary-build.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/humble-source-build.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/humble-debian-build.yml)
[](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/ci-coverage-build.yml b/.github/workflows/ci-coverage-build.yml
deleted file mode 100644
index 171a313fc0..0000000000
--- a/.github/workflows/ci-coverage-build.yml
+++ /dev/null
@@ -1,53 +0,0 @@
-name: Coverage Build
-on:
- workflow_dispatch:
- push:
- branches:
- - master
- pull_request:
- branches:
- - master
-
-jobs:
- coverage:
- name: coverage build
- runs-on: ubuntu-22.04
- strategy:
- fail-fast: false
- env:
- ROS_DISTRO: rolling
- steps:
- - uses: ros-tooling/setup-ros@0.7.1
- with:
- required-ros-distributions: ${{ env.ROS_DISTRO }}
- - uses: actions/checkout@v4
- - uses: ros-tooling/action-ros-ci@0.3.6
- with:
- target-ros2-distro: ${{ env.ROS_DISTRO }}
- import-token: ${{ secrets.GITHUB_TOKEN }}
- # build all packages listed in the meta package
- package-name:
- controller_interface
- controller_manager
- hardware_interface
- hardware_interface_testing
- transmission_interface
-
- vcs-repo-file-url: |
- https://raw.githubusercontent.com/${{ github.repository }}/${{ github.sha }}/ros2_control-not-released.${{ env.ROS_DISTRO }}.repos?token=${{ secrets.GITHUB_TOKEN }}
- colcon-defaults: |
- {
- "build": {
- "mixin": ["coverage-gcc"]
- }
- }
- colcon-mixin-repository: https://raw.githubusercontent.com/colcon/colcon-mixin-repository/master/index.yaml
- - uses: codecov/codecov-action@v3.1.5
- with:
- file: ros_ws/lcov/total_coverage.info
- flags: unittests
- name: codecov-umbrella
- - uses: actions/upload-artifact@v4.3.0
- with:
- name: colcon-logs-ubuntu-22.04-coverage-rolling
- path: ros_ws/log
diff --git a/.github/workflows/ci-format.yml b/.github/workflows/ci-format.yml
deleted file mode 100644
index c7199b88c5..0000000000
--- a/.github/workflows/ci-format.yml
+++ /dev/null
@@ -1,23 +0,0 @@
-# This is a format job. Pre-commit has a first-party GitHub action, so we use
-# that: https://github.com/pre-commit/action
-
-name: Format
-
-on:
- workflow_dispatch:
- pull_request:
-
-jobs:
- pre-commit:
- name: Format
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-python@v5.0.0
- with:
- python-version: '3.10'
- - name: Install system hooks
- run: sudo apt install -qq cppcheck
- - uses: pre-commit/action@v3.0.0
- with:
- extra_args: --all-files --hook-stage manual
diff --git a/.github/workflows/ci-ros-lint.yml b/.github/workflows/ci-ros-lint.yml
deleted file mode 100644
index 52da5edfe4..0000000000
--- a/.github/workflows/ci-ros-lint.yml
+++ /dev/null
@@ -1,57 +0,0 @@
-name: ROS Lint
-on:
- pull_request:
-
-jobs:
- ament_lint:
- name: ament_${{ matrix.linter }}
- runs-on: ubuntu-latest
- strategy:
- fail-fast: false
- matrix:
- linter: [cppcheck, copyright, lint_cmake]
- env:
- AMENT_CPPCHECK_ALLOW_SLOW_VERSIONS: true
- steps:
- - uses: actions/checkout@v4
- - uses: ros-tooling/setup-ros@0.7.1
- - uses: ros-tooling/action-ros-lint@v0.1
- with:
- distribution: rolling
- linter: ${{ matrix.linter }}
- package-name:
- controller_interface
- controller_manager
- controller_manager_msgs
- hardware_interface
- hardware_interface_testing
- ros2controlcli
- ros2_control
- ros2_control_test_assets
- transmission_interface
-
- ament_lint_100:
- name: ament_${{ matrix.linter }}
- runs-on: ubuntu-latest
- strategy:
- fail-fast: false
- matrix:
- linter: [cpplint]
- steps:
- - uses: actions/checkout@v4
- - uses: ros-tooling/setup-ros@0.7.1
- - uses: ros-tooling/action-ros-lint@v0.1
- with:
- distribution: rolling
- linter: cpplint
- arguments: "--linelength=100 --filter=-whitespace/newline"
- package-name:
- controller_interface
- controller_manager
- controller_manager_msgs
- hardware_interface
- hardware_interface_testing
- ros2controlcli
- ros2_control
- ros2_control_test_assets
- transmission_interface
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index f5015d8a90..70366d4033 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -42,7 +42,7 @@ jobs:
type=semver,pattern={{major}}.{{minor}}
- name: Build and push Docker image
- uses: docker/build-push-action@v5
+ uses: docker/build-push-action@v6
with:
context: .
push: true
@@ -82,7 +82,7 @@ jobs:
type=semver,pattern={{major}}.{{minor}}
- name: Build and push Docker image
- uses: docker/build-push-action@v5
+ uses: docker/build-push-action@v6
with:
context: .
push: true
diff --git a/.github/workflows/humble-abi-compatibility.yml b/.github/workflows/humble-abi-compatibility.yml
index 5c288fabfb..0e2488d31e 100644
--- a/.github/workflows/humble-abi-compatibility.yml
+++ b/.github/workflows/humble-abi-compatibility.yml
@@ -4,6 +4,15 @@ on:
pull_request:
branches:
- humble
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/humble-abi-compatibility.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control-not-released.humble.repos'
jobs:
abi_check:
diff --git a/.github/workflows/humble-binary-build-main.yml b/.github/workflows/humble-binary-build-main.yml
deleted file mode 100644
index a438d7a9e5..0000000000
--- a/.github/workflows/humble-binary-build-main.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-name: Humble Binary Build - main
-# author: Denis Štogl
-# description: 'Build & test all dependencies from released (binary) packages.'
-
-on:
- workflow_dispatch:
- branches:
- - humble
- pull_request:
- branches:
- - humble
- push:
- branches:
- - humble
- schedule:
- # Run every morning to detect flakiness and broken dependencies
- - cron: '03 1 * * *'
-
-jobs:
- binary:
- uses: ./.github/workflows/reusable-industrial-ci-with-cache.yml
- with:
- ros_distro: humble
- ros_repo: main
- upstream_workspace: ros2_control-not-released.humble.repos
- ref_for_scheduled_build: humble
diff --git a/.github/workflows/humble-binary-build-testing.yml b/.github/workflows/humble-binary-build-testing.yml
deleted file mode 100644
index 5a61534d4e..0000000000
--- a/.github/workflows/humble-binary-build-testing.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-name: Humble Binary Build - testing
-# author: Denis Štogl
-# description: 'Build & test all dependencies from released (binary) packages.'
-
-on:
- workflow_dispatch:
- branches:
- - humble
- pull_request:
- branches:
- - humble
- push:
- branches:
- - humble
- schedule:
- # Run every morning to detect flakiness and broken dependencies
- - cron: '03 1 * * *'
-
-jobs:
- binary:
- uses: ./.github/workflows/reusable-industrial-ci-with-cache.yml
- with:
- ros_distro: humble
- ros_repo: testing
- upstream_workspace: ros2_control-not-released.humble.repos
- ref_for_scheduled_build: humble
diff --git a/.github/workflows/humble-binary-build.yml b/.github/workflows/humble-binary-build.yml
new file mode 100644
index 0000000000..6392ba8e68
--- /dev/null
+++ b/.github/workflows/humble-binary-build.yml
@@ -0,0 +1,47 @@
+name: Humble Binary Build
+# author: Denis Štogl
+# description: 'Build & test all dependencies from released (binary) packages.'
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - humble
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/humble-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control-not-released.humble.repos'
+ push:
+ branches:
+ - humble
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/humble-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control-not-released.humble.repos'
+ schedule:
+ # Run every morning to detect flakiness and broken dependencies
+ - cron: '03 1 * * *'
+
+jobs:
+ binary:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
+ strategy:
+ fail-fast: false
+ matrix:
+ ROS_DISTRO: [humble]
+ ROS_REPO: [main, testing]
+ with:
+ ros_distro: ${{ matrix.ROS_DISTRO }}
+ ros_repo: ${{ matrix.ROS_REPO }}
+ upstream_workspace: ros2_control-not-released.${{ matrix.ROS_DISTRO }}.repos
+ ref_for_scheduled_build: humble
diff --git a/.github/workflows/humble-check-docs.yml b/.github/workflows/humble-check-docs.yml
new file mode 100644
index 0000000000..f3c31703cd
--- /dev/null
+++ b/.github/workflows/humble-check-docs.yml
@@ -0,0 +1,18 @@
+name: Humble Check Docs
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - humble
+ paths:
+ - '**.rst'
+ - '**.md'
+ - '**.yaml'
+
+jobs:
+ check-docs:
+ name: Check Docs
+ uses: ros-controls/control.ros.org/.github/workflows/reusable-sphinx-check-single-version.yml@humble
+ with:
+ ROS2_CONTROL_PR: ${{ github.ref }}
diff --git a/.github/workflows/humble-coverage-build.yml b/.github/workflows/humble-coverage-build.yml
new file mode 100644
index 0000000000..209c931d4e
--- /dev/null
+++ b/.github/workflows/humble-coverage-build.yml
@@ -0,0 +1,36 @@
+name: Coverage Build - Humble
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - humble
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/humble-coverage-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.humble.repos'
+ - 'codecov.yml'
+ pull_request:
+ branches:
+ - humble
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/humble-coverage-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.humble.repos'
+ - 'codecov.yml'
+
+jobs:
+ coverage_humble:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-build-coverage.yml@master
+ secrets: inherit
+ with:
+ ros_distro: humble
diff --git a/.github/workflows/humble-debian-build.yml b/.github/workflows/humble-debian-build.yml
index 0db1f58210..85a1b38241 100644
--- a/.github/workflows/humble-debian-build.yml
+++ b/.github/workflows/humble-debian-build.yml
@@ -1,30 +1,33 @@
-name: Debian Humble Build
+name: Debian Humble Source Build
on:
workflow_dispatch:
pull_request:
branches:
- humble
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/humble-debian-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.humble.repos'
schedule:
# Run every day to detect flakiness and broken dependencies
- cron: '03 1 * * *'
jobs:
- humble_debian:
- name: Humble debian build
- runs-on: ubuntu-latest
- env:
- ROS_DISTRO: humble
- container: ghcr.io/ros-controls/ros:humble-debian
- steps:
- - uses: actions/checkout@v4
- with:
- path: src/ros2_control
- - name: Build and test
- shell: bash
- run: |
- source /opt/ros2_ws/install/setup.bash
- vcs import src < src/ros2_control/ros2_control.${{ env.ROS_DISTRO }}.repos
- colcon build --packages-skip rqt_controller_manager
- colcon test --packages-skip rqt_controller_manager control_msgs controller_manager_msgs
- colcon test-result --verbose
+ 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:
+ ros_distro: ${{ matrix.ROS_DISTRO }}
+ upstream_workspace: ros2_control.${{ matrix.ROS_DISTRO }}.repos
+ ref_for_scheduled_build: master
+ skip_packages: rqt_controller_manager
+ skip_packages_test: controller_manager_msgs
diff --git a/.github/workflows/humble-pre-commit.yml b/.github/workflows/humble-pre-commit.yml
new file mode 100644
index 0000000000..5bb2408578
--- /dev/null
+++ b/.github/workflows/humble-pre-commit.yml
@@ -0,0 +1,13 @@
+name: Pre-Commit - Humble
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - humble
+
+jobs:
+ pre-commit:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-pre-commit.yml@master
+ with:
+ ros_distro: humble
diff --git a/.github/workflows/humble-rhel-binary-build.yml b/.github/workflows/humble-rhel-binary-build.yml
index ed37092520..62c5cd62ba 100644
--- a/.github/workflows/humble-rhel-binary-build.yml
+++ b/.github/workflows/humble-rhel-binary-build.yml
@@ -1,31 +1,31 @@
-name: RHEL Humble Binary Build
+name: RHEL Humble Semi-Binary Build
on:
workflow_dispatch:
pull_request:
branches:
- humble
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/humble-rhel-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.humble.repos'
schedule:
# Run every day to detect flakiness and broken dependencies
- cron: '03 1 * * *'
jobs:
- humble_rhel_binary:
- name: Humble RHEL binary build
- runs-on: ubuntu-latest
- env:
- ROS_DISTRO: humble
- container: ghcr.io/ros-controls/ros:humble-rhel
- steps:
- - uses: actions/checkout@v4
- with:
- path: src/ros2_control
- - name: Install dependencies
- run: |
- rosdep update
- rosdep install -iyr --from-path src/ros2_control || true
- - name: Build and test
- run: |
- source /opt/ros/${{ env.ROS_DISTRO }}/setup.bash
- colcon build --packages-skip rqt_controller_manager
- colcon test --packages-skip rqt_controller_manager ros2controlcli
- colcon test-result --verbose
+ 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:
+ ros_distro: ${{ matrix.ROS_DISTRO }}
+ upstream_workspace: ros2_control.${{ matrix.ROS_DISTRO }}.repos
+ ref_for_scheduled_build: humble
+ skip_packages: rqt_controller_manager
diff --git a/.github/workflows/humble-semi-binary-build-main.yml b/.github/workflows/humble-semi-binary-build-main.yml
deleted file mode 100644
index 539477a302..0000000000
--- a/.github/workflows/humble-semi-binary-build-main.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Humble Semi-Binary Build - main
-# description: 'Build & test that compiles the main dependencies from source.'
-
-on:
- workflow_dispatch:
- branches:
- - humble
- pull_request:
- branches:
- - humble
- push:
- branches:
- - humble
- schedule:
- # Run every morning to detect flakiness and broken dependencies
- - cron: '33 1 * * *'
-
-jobs:
- semi_binary:
- uses: ./.github/workflows/reusable-industrial-ci-with-cache.yml
- with:
- ros_distro: humble
- ros_repo: main
- upstream_workspace: ros2_control.humble.repos
- ref_for_scheduled_build: humble
diff --git a/.github/workflows/humble-semi-binary-build-testing.yml b/.github/workflows/humble-semi-binary-build-testing.yml
deleted file mode 100644
index 889822e7f2..0000000000
--- a/.github/workflows/humble-semi-binary-build-testing.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Humble Semi-Binary Build - testing
-# description: 'Build & test that compiles the main dependencies from source.'
-
-on:
- workflow_dispatch:
- branches:
- - humble
- pull_request:
- branches:
- - humble
- push:
- branches:
- - humble
- schedule:
- # Run every morning to detect flakiness and broken dependencies
- - cron: '33 1 * * *'
-
-jobs:
- semi_binary:
- uses: ./.github/workflows/reusable-industrial-ci-with-cache.yml
- with:
- ros_distro: humble
- ros_repo: testing
- upstream_workspace: ros2_control.humble.repos
- ref_for_scheduled_build: humble
diff --git a/.github/workflows/humble-semi-binary-build.yml b/.github/workflows/humble-semi-binary-build.yml
new file mode 100644
index 0000000000..560ac37cff
--- /dev/null
+++ b/.github/workflows/humble-semi-binary-build.yml
@@ -0,0 +1,47 @@
+name: Humble Semi-Binary Build
+# author: Denis Štogl
+# description: 'Build & test all dependencies from released (binary) packages.'
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - humble
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/humble-semi-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.humble.repos'
+ push:
+ branches:
+ - humble
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/humble-semi-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.humble.repos'
+ schedule:
+ # Run every morning to detect flakiness and broken dependencies
+ - cron: '03 1 * * *'
+
+jobs:
+ binary:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
+ strategy:
+ fail-fast: false
+ matrix:
+ ROS_DISTRO: [humble]
+ ROS_REPO: [main, 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
diff --git a/.github/workflows/humble-source-build.yml b/.github/workflows/humble-source-build.yml
index ff0fd62e05..878ad92677 100644
--- a/.github/workflows/humble-source-build.yml
+++ b/.github/workflows/humble-source-build.yml
@@ -1,19 +1,27 @@
name: Humble Source Build
on:
workflow_dispatch:
- branches:
- - humble
push:
branches:
- humble
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/humble-source-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.humble.repos'
schedule:
# Run every day to detect flakiness and broken dependencies
- cron: '03 3 * * *'
jobs:
source:
- uses: ./.github/workflows/reusable-ros-tooling-source-build.yml
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-ros-tooling-source-build.yml@master
with:
ros_distro: humble
ref: humble
ros2_repo_branch: humble
+ os_name: ubuntu-22.04
diff --git a/.github/workflows/iron-abi-compatibility.yml b/.github/workflows/iron-abi-compatibility.yml
index ab6642625f..c2d9c19110 100644
--- a/.github/workflows/iron-abi-compatibility.yml
+++ b/.github/workflows/iron-abi-compatibility.yml
@@ -4,6 +4,15 @@ on:
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:
diff --git a/.github/workflows/iron-binary-build-main.yml b/.github/workflows/iron-binary-build-main.yml
deleted file mode 100644
index bc2dbd96cc..0000000000
--- a/.github/workflows/iron-binary-build-main.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-name: Iron Binary Build - main
-# author: Denis Štogl
-# description: 'Build & test all dependencies from released (binary) packages.'
-
-on:
- workflow_dispatch:
- branches:
- - iron
- pull_request:
- branches:
- - iron
- push:
- branches:
- - iron
- schedule:
- # Run every morning to detect flakiness and broken dependencies
- - cron: '03 1 * * *'
-
-jobs:
- binary:
- uses: ./.github/workflows/reusable-industrial-ci-with-cache.yml
- with:
- ros_distro: iron
- ros_repo: main
- upstream_workspace: ros2_control-not-released.iron.repos
- ref_for_scheduled_build: iron
diff --git a/.github/workflows/iron-binary-build-testing.yml b/.github/workflows/iron-binary-build-testing.yml
deleted file mode 100644
index 512c4b55e4..0000000000
--- a/.github/workflows/iron-binary-build-testing.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-name: Iron Binary Build - testing
-# author: Denis Štogl
-# description: 'Build & test all dependencies from released (binary) packages.'
-
-on:
- workflow_dispatch:
- branches:
- - iron
- pull_request:
- branches:
- - iron
- push:
- branches:
- - iron
- schedule:
- # Run every morning to detect flakiness and broken dependencies
- - cron: '03 1 * * *'
-
-jobs:
- binary:
- uses: ./.github/workflows/reusable-industrial-ci-with-cache.yml
- with:
- ros_distro: iron
- ros_repo: testing
- upstream_workspace: ros2_control-not-released.iron.repos
- ref_for_scheduled_build: iron
diff --git a/.github/workflows/iron-binary-build.yml b/.github/workflows/iron-binary-build.yml
new file mode 100644
index 0000000000..ef90e256a0
--- /dev/null
+++ b/.github/workflows/iron-binary-build.yml
@@ -0,0 +1,47 @@
+name: Iron Binary Build
+# author: Denis Štogl
+# description: 'Build & test all dependencies from released (binary) packages.'
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - iron
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/iron-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control-not-released.iron.repos'
+ push:
+ branches:
+ - iron
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/iron-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control-not-released.iron.repos'
+ schedule:
+ # Run every morning to detect flakiness and broken dependencies
+ - cron: '03 1 * * *'
+
+jobs:
+ binary:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
+ strategy:
+ fail-fast: false
+ matrix:
+ ROS_DISTRO: [iron]
+ ROS_REPO: [main, testing]
+ with:
+ ros_distro: ${{ matrix.ROS_DISTRO }}
+ ros_repo: ${{ matrix.ROS_REPO }}
+ upstream_workspace: ros2_control-not-released.iron.repos
+ ref_for_scheduled_build: iron
diff --git a/.github/workflows/ci-check-docs.yml b/.github/workflows/iron-check-docs.yml
similarity index 57%
rename from .github/workflows/ci-check-docs.yml
rename to .github/workflows/iron-check-docs.yml
index 90a822aa72..e9295dad44 100644
--- a/.github/workflows/ci-check-docs.yml
+++ b/.github/workflows/iron-check-docs.yml
@@ -1,12 +1,18 @@
-name: Check Docs
+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@master
+ 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
new file mode 100644
index 0000000000..ff5be81d7d
--- /dev/null
+++ b/.github/workflows/iron-coverage-build.yml
@@ -0,0 +1,36 @@
+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
index 405e4f9135..3cbe0c5127 100644
--- a/.github/workflows/iron-debian-build.yml
+++ b/.github/workflows/iron-debian-build.yml
@@ -1,30 +1,33 @@
-name: Debian Iron Build
+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:
- iron_debian:
- name: Iron debian build
- runs-on: ubuntu-latest
- env:
- ROS_DISTRO: iron
- container: ghcr.io/ros-controls/ros:iron-debian
- steps:
- - uses: actions/checkout@v4
- with:
- path: src/ros2_control
- - name: Build and test
- shell: bash
- run: |
- source /opt/ros2_ws/install/setup.bash
- vcs import src < src/ros2_control/ros2_control.${{ env.ROS_DISTRO }}.repos
- colcon build --packages-skip rqt_controller_manager
- colcon test --packages-skip rqt_controller_manager control_msgs controller_manager_msgs
- colcon test-result --verbose
+ debian_source_build:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-debian-build.yml@master
+ strategy:
+ fail-fast: false
+ matrix:
+ ROS_DISTRO: [iron]
+ with:
+ ros_distro: ${{ matrix.ROS_DISTRO }}
+ upstream_workspace: ros2_control.${{ matrix.ROS_DISTRO }}.repos
+ ref_for_scheduled_build: master
+ skip_packages: rqt_controller_manager
+ skip_packages_test: controller_manager_msgs
diff --git a/.github/workflows/iron-pre-commit.yml b/.github/workflows/iron-pre-commit.yml
new file mode 100644
index 0000000000..a128958031
--- /dev/null
+++ b/.github/workflows/iron-pre-commit.yml
@@ -0,0 +1,13 @@
+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
index fc48bd80ea..f308c495f3 100644
--- a/.github/workflows/iron-rhel-binary-build.yml
+++ b/.github/workflows/iron-rhel-binary-build.yml
@@ -1,32 +1,31 @@
-name: RHEL Iron Binary Build
+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:
- iron_rhel_binary:
- name: Iron RHEL binary build
- runs-on: ubuntu-latest
- env:
- ROS_DISTRO: iron
- container: ghcr.io/ros-controls/ros:iron-rhel
- steps:
- - uses: actions/checkout@v4
- with:
- path: src/ros2_control
- - name: Install dependencies
- run: |
- rosdep update
- rosdep install -iyr --from-path src/ros2_control || true
- - name: Build and test
- run: |
- source /opt/ros/${{ env.ROS_DISTRO }}/setup.bash
- colcon build --packages-skip rqt_controller_manager
- colcon test --packages-skip rqt_controller_manager ros2controlcli
- colcon test-result --verbose
+ rhel_semi_binary_build:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-rhel-binary-build.yml@master
+ strategy:
+ fail-fast: false
+ matrix:
+ ROS_DISTRO: [iron]
+ with:
+ ros_distro: ${{ matrix.ROS_DISTRO }}
+ upstream_workspace: ros2_control.${{ matrix.ROS_DISTRO }}.repos
+ ref_for_scheduled_build: iron
+ skip_packages: rqt_controller_manager
diff --git a/.github/workflows/iron-semi-binary-build-main.yml b/.github/workflows/iron-semi-binary-build-main.yml
deleted file mode 100644
index 1399e8b32b..0000000000
--- a/.github/workflows/iron-semi-binary-build-main.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Iron Semi-Binary Build - main
-# description: 'Build & test that compiles the main dependencies from source.'
-
-on:
- workflow_dispatch:
- branches:
- - iron
- pull_request:
- branches:
- - iron
- push:
- branches:
- - iron
- schedule:
- # Run every morning to detect flakiness and broken dependencies
- - cron: '33 1 * * *'
-
-jobs:
- semi_binary:
- uses: ./.github/workflows/reusable-industrial-ci-with-cache.yml
- with:
- ros_distro: iron
- ros_repo: main
- upstream_workspace: ros2_control.iron.repos
- ref_for_scheduled_build: iron
diff --git a/.github/workflows/iron-semi-binary-build-testing.yml b/.github/workflows/iron-semi-binary-build-testing.yml
deleted file mode 100644
index b29f798931..0000000000
--- a/.github/workflows/iron-semi-binary-build-testing.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Iron Semi-Binary Build - testing
-# description: 'Build & test that compiles the main dependencies from source.'
-
-on:
- workflow_dispatch:
- branches:
- - iron
- pull_request:
- branches:
- - iron
- push:
- branches:
- - iron
- schedule:
- # Run every morning to detect flakiness and broken dependencies
- - cron: '33 1 * * *'
-
-jobs:
- semi_binary:
- uses: ./.github/workflows/reusable-industrial-ci-with-cache.yml
- with:
- ros_distro: iron
- ros_repo: testing
- upstream_workspace: ros2_control.iron.repos
- ref_for_scheduled_build: iron
diff --git a/.github/workflows/iron-semi-binary-build.yml b/.github/workflows/iron-semi-binary-build.yml
new file mode 100644
index 0000000000..30a83e5367
--- /dev/null
+++ b/.github/workflows/iron-semi-binary-build.yml
@@ -0,0 +1,47 @@
+name: Iron Semi-Binary Build
+# author: Denis Štogl
+# description: 'Build & test all dependencies from released (binary) packages.'
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - iron
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/iron-semi-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.iron.repos'
+ push:
+ branches:
+ - iron
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/iron-semi-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.iron.repos'
+ schedule:
+ # Run every morning to detect flakiness and broken dependencies
+ - cron: '03 1 * * *'
+
+jobs:
+ binary:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
+ strategy:
+ fail-fast: false
+ matrix:
+ ROS_DISTRO: [iron]
+ ROS_REPO: [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
index 1e9d865c49..3b7c53f6ff 100644
--- a/.github/workflows/iron-source-build.yml
+++ b/.github/workflows/iron-source-build.yml
@@ -1,19 +1,27 @@
name: Iron Source Build
on:
workflow_dispatch:
- branches:
- - iron
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: ./.github/workflows/reusable-ros-tooling-source-build.yml
+ 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
new file mode 100644
index 0000000000..367b3736fb
--- /dev/null
+++ b/.github/workflows/jazzy-abi-compatibility.yml
@@ -0,0 +1,27 @@
+name: Jazzy - ABI Compatibility Check
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/jazzy-abi-compatibility.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control-not-released.jazzy.repos'
+
+jobs:
+ abi_check:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: ros-industrial/industrial_ci@master
+ env:
+ ROS_DISTRO: jazzy
+ ROS_REPO: testing
+ ABICHECK_URL: github:${{ github.repository }}#${{ github.base_ref }}
+ NOT_TEST_BUILD: true
diff --git a/.github/workflows/jazzy-binary-build.yml b/.github/workflows/jazzy-binary-build.yml
new file mode 100644
index 0000000000..5be853ebfc
--- /dev/null
+++ b/.github/workflows/jazzy-binary-build.yml
@@ -0,0 +1,47 @@
+name: Jazzy Binary Build
+# author: Denis Štogl
+# description: 'Build & test all dependencies from released (binary) packages.'
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/jazzy-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control-not-released.jazzy.repos'
+ push:
+ branches:
+ - master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/jazzy-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control-not-released.jazzy.repos'
+ schedule:
+ # Run every morning to detect flakiness and broken dependencies
+ - cron: '03 1 * * *'
+
+jobs:
+ binary:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
+ strategy:
+ fail-fast: false
+ matrix:
+ ROS_DISTRO: [jazzy]
+ ROS_REPO: [main, testing]
+ with:
+ ros_distro: ${{ matrix.ROS_DISTRO }}
+ ros_repo: ${{ matrix.ROS_REPO }}
+ upstream_workspace: ros2_control-not-released.${{ matrix.ROS_DISTRO }}.repos
+ ref_for_scheduled_build: master
diff --git a/.github/workflows/jazzy-check-docs.yml b/.github/workflows/jazzy-check-docs.yml
new file mode 100644
index 0000000000..cbdf6c30bd
--- /dev/null
+++ b/.github/workflows/jazzy-check-docs.yml
@@ -0,0 +1,18 @@
+name: Jazzy Check Docs
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - master
+ paths:
+ - '**.rst'
+ - '**.md'
+ - '**.yaml'
+
+jobs:
+ check-docs:
+ name: Check Docs
+ uses: ros-controls/control.ros.org/.github/workflows/reusable-sphinx-check-single-version.yml@jazzy
+ with:
+ ROS2_CONTROL_PR: ${{ github.ref }}
diff --git a/.github/workflows/jazzy-coverage-build.yml b/.github/workflows/jazzy-coverage-build.yml
new file mode 100644
index 0000000000..aa345d1e80
--- /dev/null
+++ b/.github/workflows/jazzy-coverage-build.yml
@@ -0,0 +1,35 @@
+name: Coverage Build - Jazzy
+on:
+ workflow_dispatch:
+ # TODO(anyone) activate when branched for Jazzy
+ # push:
+ # branches:
+ # - master
+ # paths:
+ # - '**.hpp'
+ # - '**.h'
+ # - '**.cpp'
+ # - '.github/workflows/jazzy-coverage-build.yml'
+ # - '**/package.xml'
+ # - '**/CMakeLists.txt'
+ # - 'ros2_control.jazzy.repos'
+ # - 'codecov.yml'
+ # pull_request:
+ # branches:
+ # - master
+ # paths:
+ # - '**.hpp'
+ # - '**.h'
+ # - '**.cpp'
+ # - '.github/workflows/jazzy-coverage-build.yml'
+ # - '**/package.xml'
+ # - '**/CMakeLists.txt'
+ # - 'ros2_control.jazzy.repos'
+ # - 'codecov.yml'
+
+jobs:
+ coverage_jazzy:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-build-coverage.yml@master
+ secrets: inherit
+ with:
+ ros_distro: jazzy
diff --git a/.github/workflows/jazzy-debian-build.yml b/.github/workflows/jazzy-debian-build.yml
new file mode 100644
index 0000000000..4ec6a29fff
--- /dev/null
+++ b/.github/workflows/jazzy-debian-build.yml
@@ -0,0 +1,32 @@
+name: Debian Jazzy Source Build
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/jazzy-debian-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.jazzy.repos'
+ schedule:
+ # Run every day to detect flakiness and broken dependencies
+ - cron: '03 1 * * *'
+
+
+jobs:
+ debian_source_build:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-debian-build.yml@master
+ strategy:
+ fail-fast: false
+ matrix:
+ ROS_DISTRO: [jazzy]
+ with:
+ ros_distro: ${{ matrix.ROS_DISTRO }}
+ upstream_workspace: ros2_control.${{ matrix.ROS_DISTRO }}.repos
+ ref_for_scheduled_build: master
+ skip_packages: rqt_controller_manager
diff --git a/.github/workflows/jazzy-pre-commit.yml b/.github/workflows/jazzy-pre-commit.yml
new file mode 100644
index 0000000000..d9ec610bbc
--- /dev/null
+++ b/.github/workflows/jazzy-pre-commit.yml
@@ -0,0 +1,13 @@
+name: Pre-Commit - Jazzy
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - master
+
+jobs:
+ pre-commit:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-pre-commit.yml@master
+ with:
+ ros_distro: jazzy
diff --git a/.github/workflows/jazzy-rhel-binary-build.yml b/.github/workflows/jazzy-rhel-binary-build.yml
new file mode 100644
index 0000000000..0dcc912dab
--- /dev/null
+++ b/.github/workflows/jazzy-rhel-binary-build.yml
@@ -0,0 +1,31 @@
+name: RHEL Jazzy Semi-Binary Build
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/jazzy-rhel-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.jazzy.repos'
+ schedule:
+ # Run every day to detect flakiness and broken dependencies
+ - cron: '03 1 * * *'
+
+jobs:
+ rhel_semi_binary_build:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-rhel-binary-build.yml@master
+ strategy:
+ fail-fast: false
+ matrix:
+ ROS_DISTRO: [jazzy]
+ with:
+ ros_distro: ${{ matrix.ROS_DISTRO }}
+ upstream_workspace: ros2_control.${{ matrix.ROS_DISTRO }}.repos
+ ref_for_scheduled_build: master
+ skip_packages: rqt_controller_manager
diff --git a/.github/workflows/jazzy-semi-binary-build.yml b/.github/workflows/jazzy-semi-binary-build.yml
new file mode 100644
index 0000000000..9634732cf9
--- /dev/null
+++ b/.github/workflows/jazzy-semi-binary-build.yml
@@ -0,0 +1,47 @@
+name: Jazzy Semi-Binary Build
+# author: Denis Štogl
+# description: 'Build & test all dependencies from released (binary) packages.'
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/jazzy-semi-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.jazzy.repos'
+ push:
+ branches:
+ - master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/jazzy-semi-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.jazzy.repos'
+ schedule:
+ # Run every morning to detect flakiness and broken dependencies
+ - cron: '03 1 * * *'
+
+jobs:
+ binary:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
+ strategy:
+ fail-fast: false
+ matrix:
+ ROS_DISTRO: [jazzy]
+ ROS_REPO: [main, 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
diff --git a/.github/workflows/jazzy-source-build.yml b/.github/workflows/jazzy-source-build.yml
new file mode 100644
index 0000000000..65066a4bf2
--- /dev/null
+++ b/.github/workflows/jazzy-source-build.yml
@@ -0,0 +1,27 @@
+name: Jazzy Source Build
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/jazzy-source-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.jazzy.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: jazzy
+ ref: master
+ ros2_repo_branch: master
+ container: ubuntu:24.04
diff --git a/.github/workflows/reusable-industrial-ci-with-cache.yml b/.github/workflows/reusable-industrial-ci-with-cache.yml
deleted file mode 100644
index acefeebfac..0000000000
--- a/.github/workflows/reusable-industrial-ci-with-cache.yml
+++ /dev/null
@@ -1,96 +0,0 @@
-name: Reusable industrial_ci Workflow with Cache
-# Reusable action to simplify dealing with ROS/ROS2 industrial_ci builds with cache
-# author: Denis Štogl
-
-on:
- workflow_call:
- inputs:
- ref_for_scheduled_build:
- description: 'Reference on which the repo should be checkout for scheduled build. Usually is this name of a branch or a tag.'
- default: ''
- required: false
- type: string
-
- upstream_workspace:
- description: 'UPSTREAM_WORKSPACE variable for industrial_ci. Usually path to local .repos file.'
- required: true
- type: string
- ros_distro:
- description: 'ROS_DISTRO variable for industrial_ci'
- required: true
- type: string
- ros_repo:
- description: 'ROS_REPO to run for industrial_ci. Possible values: "main", "testing"'
- default: 'main'
- required: false
- type: string
- os_code_name:
- description: 'OS_CODE_NAME variable for industrial_ci'
- default: ''
- required: false
- type: string
- before_install_upstream_dependencies:
- description: 'BEFORE_INSTALL_UPSTREAM_DEPENDENCIES variable for industrial_ci'
- default: ''
- required: false
- type: string
-
- ccache_dir:
- description: 'Local path to store cache (from "github.workspace"). For standard industrial_ci configuration do not have to be changed'
- default: '.ccache'
- required: false
- type: string
- basedir:
- description: 'Local path to workspace base directory to cache (from "github.workspace"). For standard industrial_ci configuration do not have to be changed'
- default: '.work'
- required: false
- type: string
-
-
-jobs:
- reusable_industrial_ci_with_cache:
- name: ${{ inputs.ros_distro }} ${{ inputs.ros_repo }} ${{ inputs.os_code_name }}
- runs-on: ubuntu-latest
- env:
- CCACHE_DIR: ${{ github.workspace }}/${{ inputs.ccache_dir }}
- BASEDIR: ${{ github.workspace }}/${{ inputs.basedir }}
- CACHE_PREFIX: ${{ inputs.ros_distro }}-${{ inputs.upstream_workspace }}-${{ inputs.ros_repo }}-${{ github.job }}
- steps:
- - name: Checkout ${{ inputs.ref }} when build is not scheduled
- if: ${{ github.event_name != 'schedule' }}
- uses: actions/checkout@v4
- - name: Checkout ${{ inputs.ref }} on scheduled build
- if: ${{ github.event_name == 'schedule' }}
- uses: actions/checkout@v4
- with:
- ref: ${{ inputs.ref_for_scheduled_build }}
- - name: cache target_ws
- if: ${{ ! matrix.env.CCOV }}
- uses: pat-s/always-upload-cache@v3.0.11
- with:
- path: ${{ env.BASEDIR }}/target_ws
- key: target_ws-${{ env.CACHE_PREFIX }}-${{ hashFiles('**/CMakeLists.txt', '**/package.xml') }}-${{ github.run_id }}
- restore-keys: |
- target_ws-${{ env.CACHE_PREFIX }}-${{ hashFiles('**/CMakeLists.txt', '**/package.xml') }}
- - name: cache ccache
- uses: pat-s/always-upload-cache@v3.0.11
- with:
- path: ${{ env.CCACHE_DIR }}
- key: ccache-${{ env.CACHE_PREFIX }}-${{ github.sha }}-${{ github.run_id }}
- restore-keys: |
- ccache-${{ env.CACHE_PREFIX }}-${{ github.sha }}
- ccache-${{ env.CACHE_PREFIX }}
- - uses: 'ros-industrial/industrial_ci@master'
- env:
- UPSTREAM_WORKSPACE: ${{ inputs.upstream_workspace }}
- ROS_DISTRO: ${{ inputs.ros_distro }}
- ROS_REPO: ${{ inputs.ros_repo }}
- OS_CODE_NAME: ${{ inputs.os_code_name }}
- BEFORE_INSTALL_UPSTREAM_DEPENDENCIES: ${{ inputs.before_install_upstream_dependencies }}
- - name: prepare target_ws for cache
- if: ${{ always() && ! matrix.env.CCOV }}
- run: |
- du -sh ${{ env.BASEDIR }}/target_ws
- sudo find ${{ env.BASEDIR }}/target_ws -wholename '*/test_results/*' -delete
- sudo rm -rf ${{ env.BASEDIR }}/target_ws/src
- du -sh ${{ env.BASEDIR }}/target_ws
diff --git a/.github/workflows/reusable-ros-tooling-source-build.yml b/.github/workflows/reusable-ros-tooling-source-build.yml
deleted file mode 100644
index fa96b7288c..0000000000
--- a/.github/workflows/reusable-ros-tooling-source-build.yml
+++ /dev/null
@@ -1,56 +0,0 @@
-name: Reusable industrial_ci Workflow with Cache
-# Reusable action to simplify dealing with ROS/ROS2 industrial_ci builds with cache
-# author: Denis Štogl
-
-on:
- workflow_call:
- inputs:
- ros_distro:
- description: 'ROS2 distribution name'
- required: true
- type: string
- ref:
- description: 'Reference on which the repo should be checkout. Usually is this name of a branch or a tag.'
- required: true
- type: string
- ros2_repo_branch:
- description: 'Branch in the ros2/ros2 repository from which ".repos" should be used. Possible values: master (Rolling), humble.'
- default: 'master'
- required: false
- type: string
-
-jobs:
- reusable_ros_tooling_source_build:
- name: ${{ inputs.ros_distro }} ubuntu-22.04
- runs-on: ubuntu-22.04
- strategy:
- fail-fast: false
- steps:
- - uses: ros-tooling/setup-ros@0.7.1
- with:
- required-ros-distributions: ${{ inputs.ros_distro }}
- - uses: actions/checkout@v4
- with:
- ref: ${{ inputs.ref }}
- - uses: ros-tooling/action-ros-ci@0.3.6
- with:
- target-ros2-distro: ${{ inputs.ros_distro }}
- # build all packages listed in the meta package
- ref: ${{ inputs.ref }} # otherwise the default branch is used for scheduled workflows
- package-name:
- controller_interface
- controller_manager
- controller_manager_msgs
- hardware_interface
- ros2controlcli
- ros2_control
- ros2_control_test_assets
- transmission_interface
- vcs-repo-file-url: |
- https://raw.githubusercontent.com/ros2/ros2/${{ inputs.ros2_repo_branch }}/ros2.repos
- https://raw.githubusercontent.com/${{ github.repository }}/${{ github.sha }}/ros2_control.${{ inputs.ros_distro }}.repos?token=${{ secrets.GITHUB_TOKEN }}
- colcon-mixin-repository: https://raw.githubusercontent.com/colcon/colcon-mixin-repository/master/index.yaml
- - uses: actions/upload-artifact@v4.3.0
- with:
- name: colcon-logs-ubuntu-22.04
- path: ros_ws/log
diff --git a/.github/workflows/reviewer_lottery.yml b/.github/workflows/reviewer_lottery.yml
index ed28964e01..0584f4a7f3 100644
--- a/.github/workflows/reviewer_lottery.yml
+++ b/.github/workflows/reviewer_lottery.yml
@@ -1,14 +1,10 @@
name: Reviewer lottery
+# pull_request_target takes the same events as pull_request,
+# but it runs on the base branch instead of the head branch.
on:
pull_request_target:
types: [opened, ready_for_review, reopened]
jobs:
- test:
- runs-on: ubuntu-latest
- if: github.actor != 'dependabot[bot]' && github.actor != 'mergify[bot]'
- steps:
- - uses: actions/checkout@v4
- - uses: uesteibar/reviewer-lottery@v3
- with:
- repo-token: ${{ secrets.GITHUB_TOKEN }}
+ assign_reviewers:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-reviewer-lottery.yml@master
diff --git a/.github/workflows/rolling-abi-compatibility.yml b/.github/workflows/rolling-abi-compatibility.yml
index 73055ef3e5..b7828390fb 100644
--- a/.github/workflows/rolling-abi-compatibility.yml
+++ b/.github/workflows/rolling-abi-compatibility.yml
@@ -4,6 +4,15 @@ on:
pull_request:
branches:
- master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/rolling-abi-compatibility.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control-not-released.rolling.repos'
jobs:
abi_check:
diff --git a/.github/workflows/rolling-binary-build-main.yml b/.github/workflows/rolling-binary-build-main.yml
deleted file mode 100644
index 05a9b9c0b2..0000000000
--- a/.github/workflows/rolling-binary-build-main.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-name: Rolling Binary Build - main
-# author: Denis Štogl
-# description: 'Build & test all dependencies from released (binary) packages.'
-
-on:
- workflow_dispatch:
- branches:
- - master
- pull_request:
- branches:
- - master
- push:
- branches:
- - master
- schedule:
- # Run every morning to detect flakiness and broken dependencies
- - cron: '03 1 * * *'
-
-jobs:
- binary:
- uses: ./.github/workflows/reusable-industrial-ci-with-cache.yml
- with:
- ros_distro: rolling
- ros_repo: main
- upstream_workspace: ros2_control-not-released.rolling.repos
- ref_for_scheduled_build: master
diff --git a/.github/workflows/rolling-binary-build-testing.yml b/.github/workflows/rolling-binary-build-testing.yml
deleted file mode 100644
index 811c96fce4..0000000000
--- a/.github/workflows/rolling-binary-build-testing.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-name: Rolling Binary Build - testing
-# author: Denis Štogl
-# description: 'Build & test all dependencies from released (binary) packages.'
-
-on:
- workflow_dispatch:
- branches:
- - master
- pull_request:
- branches:
- - master
- push:
- branches:
- - master
- schedule:
- # Run every morning to detect flakiness and broken dependencies
- - cron: '03 1 * * *'
-
-jobs:
- binary:
- uses: ./.github/workflows/reusable-industrial-ci-with-cache.yml
- with:
- ros_distro: rolling
- ros_repo: testing
- upstream_workspace: ros2_control-not-released.rolling.repos
- ref_for_scheduled_build: master
diff --git a/.github/workflows/rolling-binary-build.yml b/.github/workflows/rolling-binary-build.yml
new file mode 100644
index 0000000000..24a28f16ae
--- /dev/null
+++ b/.github/workflows/rolling-binary-build.yml
@@ -0,0 +1,47 @@
+name: Rolling Binary Build
+# author: Denis Štogl
+# description: 'Build & test all dependencies from released (binary) packages.'
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/rolling-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control-not-released.rolling.repos'
+ push:
+ branches:
+ - master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/rolling-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control-not-released.rolling.repos'
+ schedule:
+ # Run every morning to detect flakiness and broken dependencies
+ - cron: '03 1 * * *'
+
+jobs:
+ binary:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
+ strategy:
+ fail-fast: false
+ matrix:
+ ROS_DISTRO: [rolling]
+ ROS_REPO: [main, testing]
+ with:
+ ros_distro: ${{ matrix.ROS_DISTRO }}
+ ros_repo: ${{ matrix.ROS_REPO }}
+ upstream_workspace: ros2_control-not-released.${{ matrix.ROS_DISTRO }}.repos
+ ref_for_scheduled_build: master
diff --git a/.github/workflows/rolling-check-docs.yml b/.github/workflows/rolling-check-docs.yml
new file mode 100644
index 0000000000..bd83c0caca
--- /dev/null
+++ b/.github/workflows/rolling-check-docs.yml
@@ -0,0 +1,19 @@
+name: Rolling Check Docs
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - master
+ paths:
+ - '**.rst'
+ - '**.md'
+ - '**.yaml'
+ - '.github/workflows/rolling-check-docs.yml'
+
+jobs:
+ check-docs:
+ name: Check Docs
+ uses: ros-controls/control.ros.org/.github/workflows/reusable-sphinx-check-single-version.yml@rolling
+ with:
+ ROS2_CONTROL_PR: ${{ github.ref }}
diff --git a/.github/workflows/rolling-coverage-build.yml b/.github/workflows/rolling-coverage-build.yml
new file mode 100644
index 0000000000..45b10876e7
--- /dev/null
+++ b/.github/workflows/rolling-coverage-build.yml
@@ -0,0 +1,36 @@
+name: Coverage Build - Rolling
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/rolling-coverage-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.rolling.repos'
+ - 'codecov.yml'
+ pull_request:
+ branches:
+ - master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/rolling-coverage-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.rolling.repos'
+ - 'codecov.yml'
+
+jobs:
+ coverage_rolling:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-build-coverage.yml@master
+ secrets: inherit
+ with:
+ ros_distro: rolling
diff --git a/.github/workflows/rolling-debian-build.yml b/.github/workflows/rolling-debian-build.yml
index 098af45029..00d4ad844b 100644
--- a/.github/workflows/rolling-debian-build.yml
+++ b/.github/workflows/rolling-debian-build.yml
@@ -1,30 +1,32 @@
-name: Debian Rolling Build
+name: Debian Rolling Source Build
on:
workflow_dispatch:
pull_request:
branches:
- master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/rolling-debian-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.rolling.repos'
schedule:
# Run every day to detect flakiness and broken dependencies
- cron: '03 1 * * *'
jobs:
- rolling_debian:
- name: Rolling debian build
- runs-on: ubuntu-latest
- env:
- ROS_DISTRO: rolling
- container: ghcr.io/ros-controls/ros:rolling-debian
- steps:
- - uses: actions/checkout@v4
- with:
- path: src/ros2_control
- - name: Build and test
- shell: bash
- run: |
- source /opt/ros2_ws/install/setup.bash
- vcs import src < src/ros2_control/ros2_control.${{ env.ROS_DISTRO }}.repos
- colcon build --packages-skip rqt_controller_manager
- colcon test --packages-skip rqt_controller_manager
- colcon test-result --verbose
+ 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:
+ ros_distro: ${{ matrix.ROS_DISTRO }}
+ upstream_workspace: ros2_control.${{ matrix.ROS_DISTRO }}.repos
+ ref_for_scheduled_build: master
+ skip_packages: rqt_controller_manager
diff --git a/.github/workflows/rolling-pre-commit.yml b/.github/workflows/rolling-pre-commit.yml
new file mode 100644
index 0000000000..7bc07ba802
--- /dev/null
+++ b/.github/workflows/rolling-pre-commit.yml
@@ -0,0 +1,13 @@
+name: Pre-Commit - Rolling
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - master
+
+jobs:
+ pre-commit:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-pre-commit.yml@master
+ with:
+ ros_distro: rolling
diff --git a/.github/workflows/rolling-rhel-binary-build.yml b/.github/workflows/rolling-rhel-binary-build.yml
index 06a5411c24..c8939d6015 100644
--- a/.github/workflows/rolling-rhel-binary-build.yml
+++ b/.github/workflows/rolling-rhel-binary-build.yml
@@ -1,32 +1,31 @@
-name: RHEL Rolling Binary Build
+name: RHEL Rolling Semi-Binary Build
on:
workflow_dispatch:
pull_request:
branches:
- master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/rolling-rhel-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.jazzy.repos'
schedule:
# Run every day to detect flakiness and broken dependencies
- cron: '03 1 * * *'
-
jobs:
- rolling_rhel_binary:
- name: Rolling RHEL binary build
- runs-on: ubuntu-latest
- env:
- ROS_DISTRO: rolling
- container: ghcr.io/ros-controls/ros:rolling-rhel
- steps:
- - uses: actions/checkout@v4
- with:
- path: src/ros2_control
- - name: Install dependencies
- run: |
- rosdep update
- rosdep install -iyr --from-path src/ros2_control || true
- - name: Build and test
- run: |
- source /opt/ros/${{ env.ROS_DISTRO }}/setup.bash
- colcon build --packages-skip rqt_controller_manager
- colcon test --packages-skip rqt_controller_manager ros2controlcli
- colcon test-result --verbose
+ 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:
+ ros_distro: ${{ matrix.ROS_DISTRO }}
+ upstream_workspace: ros2_control.${{ matrix.ROS_DISTRO }}.repos
+ ref_for_scheduled_build: master
+ skip_packages: rqt_controller_manager
diff --git a/.github/workflows/rolling-semi-binary-build-main.yml b/.github/workflows/rolling-semi-binary-build-main.yml
deleted file mode 100644
index 1033dd1e6c..0000000000
--- a/.github/workflows/rolling-semi-binary-build-main.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Rolling Semi-Binary Build - main
-# description: 'Build & test that compiles the main dependencies from source.'
-
-on:
- workflow_dispatch:
- branches:
- - master
- pull_request:
- branches:
- - master
- push:
- branches:
- - master
- schedule:
- # Run every morning to detect flakiness and broken dependencies
- - cron: '33 1 * * *'
-
-jobs:
- semi_binary:
- uses: ./.github/workflows/reusable-industrial-ci-with-cache.yml
- with:
- ros_distro: rolling
- ros_repo: main
- upstream_workspace: ros2_control.rolling.repos
- ref_for_scheduled_build: master
diff --git a/.github/workflows/rolling-semi-binary-build-testing.yml b/.github/workflows/rolling-semi-binary-build-testing.yml
deleted file mode 100644
index 4ddfcf5057..0000000000
--- a/.github/workflows/rolling-semi-binary-build-testing.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Rolling Semi-Binary Build - testing
-# description: 'Build & test that compiles the main dependencies from source.'
-
-on:
- workflow_dispatch:
- branches:
- - master
- pull_request:
- branches:
- - master
- push:
- branches:
- - master
- schedule:
- # Run every morning to detect flakiness and broken dependencies
- - cron: '33 1 * * *'
-
-jobs:
- semi_binary:
- uses: ./.github/workflows/reusable-industrial-ci-with-cache.yml
- with:
- ros_distro: rolling
- ros_repo: testing
- upstream_workspace: ros2_control.rolling.repos
- ref_for_scheduled_build: master
diff --git a/.github/workflows/rolling-semi-binary-build.yml b/.github/workflows/rolling-semi-binary-build.yml
new file mode 100644
index 0000000000..4cdb7ab585
--- /dev/null
+++ b/.github/workflows/rolling-semi-binary-build.yml
@@ -0,0 +1,47 @@
+name: Rolling Semi-Binary Build
+# author: Denis Štogl
+# description: 'Build & test all dependencies from released (binary) packages.'
+
+on:
+ workflow_dispatch:
+ pull_request:
+ branches:
+ - master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/rolling-semi-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.rolling.repos'
+ push:
+ branches:
+ - master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/rolling-semi-binary-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.rolling.repos'
+ schedule:
+ # Run every morning to detect flakiness and broken dependencies
+ - cron: '03 1 * * *'
+
+jobs:
+ binary:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-industrial-ci-with-cache.yml@master
+ strategy:
+ fail-fast: false
+ matrix:
+ ROS_DISTRO: [rolling]
+ ROS_REPO: [main, 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
diff --git a/.github/workflows/rolling-source-build.yml b/.github/workflows/rolling-source-build.yml
index b96ad0298e..9bbf09cda4 100644
--- a/.github/workflows/rolling-source-build.yml
+++ b/.github/workflows/rolling-source-build.yml
@@ -1,19 +1,27 @@
name: Rolling Source Build
on:
workflow_dispatch:
- branches:
- - master
push:
branches:
- master
+ paths:
+ - '**.hpp'
+ - '**.h'
+ - '**.cpp'
+ - '**.py'
+ - '.github/workflows/rolling-source-build.yml'
+ - '**/package.xml'
+ - '**/CMakeLists.txt'
+ - 'ros2_control.rolling.repos'
schedule:
# Run every day to detect flakiness and broken dependencies
- cron: '03 3 * * *'
jobs:
source:
- uses: ./.github/workflows/reusable-ros-tooling-source-build.yml
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-ros-tooling-source-build.yml@master
with:
ros_distro: rolling
ref: master
ros2_repo_branch: master
+ container: ubuntu:24.04
diff --git a/.github/workflows/rosdoc2.yml b/.github/workflows/rosdoc2.yml
new file mode 100644
index 0000000000..07a9e2904a
--- /dev/null
+++ b/.github/workflows/rosdoc2.yml
@@ -0,0 +1,14 @@
+name: rosdoc2
+
+on:
+ workflow_dispatch:
+ pull_request:
+ paths:
+ - ros2_control/doc/**
+ - ros2_control/rosdoc2.yaml
+ - ros2_control/package.xml
+
+
+jobs:
+ check:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-rosdoc2.yml@master
diff --git a/.github/workflows/update-pre-commit.yml b/.github/workflows/update-pre-commit.yml
new file mode 100644
index 0000000000..6bedaa0c97
--- /dev/null
+++ b/.github/workflows/update-pre-commit.yml
@@ -0,0 +1,12 @@
+name: Auto Update pre-commit
+# Update pre-commit config and create PR if changes are detected
+# author: Christoph Fröhlich
+
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '0 0 1 * *' # Runs at 00:00, on day 1 of the month
+
+jobs:
+ auto_update_and_create_pr:
+ uses: ros-controls/ros2_control_ci/.github/workflows/reusable-update-pre-commit.yml@master
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 6e6d41cc9c..29862f70e4 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.4.0
+ rev: v4.6.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.4.0
+ rev: v3.16.0
hooks:
- id: pyupgrade
args: [--py36-plus]
@@ -49,40 +50,38 @@ repos:
args: ["--ignore=D100,D101,D102,D103,D104,D105,D106,D107,D203,D212,D404"]
- repo: https://github.com/psf/black
- rev: 23.3.0
+ rev: 24.4.2
hooks:
- id: black
args: ["--line-length=99"]
- repo: https://github.com/pycqa/flake8
- rev: 6.0.0
+ rev: 7.1.0
hooks:
- id: flake8
args: ["--extend-ignore=E501"]
# CPP hooks
- repo: https://github.com/pre-commit/mirrors-clang-format
- rev: v15.0.6
+ rev: v18.1.8
hooks:
- id: clang-format
+ args: ['-fallback-style=none', '-i']
- repo: local
hooks:
- id: ament_cppcheck
name: ament_cppcheck
description: Static code analysis of C/C++ files.
- stages: [commit]
- entry: ament_cppcheck
+ entry: env AMENT_CPPCHECK_ALLOW_SLOW_VERSIONS=1 ament_cppcheck
language: system
files: \.(h\+\+|h|hh|hxx|hpp|cuh|c|cc|cpp|cu|c\+\+|cxx|tpp|txx)$
- # Maybe use https://github.com/cpplint/cpplint instead
- repo: local
hooks:
- id: ament_cpplint
name: ament_cpplint
description: Static code analysis of C/C++ files.
- stages: [commit]
entry: ament_cpplint
language: system
files: \.(h\+\+|h|hh|hxx|hpp|cuh|c|cc|cpp|cu|c\+\+|cxx|tpp|txx)$
@@ -94,7 +93,6 @@ repos:
- id: ament_lint_cmake
name: ament_lint_cmake
description: Check format of CMakeLists.txt files.
- stages: [commit]
entry: ament_lint_cmake
language: system
files: CMakeLists\.txt$
@@ -105,9 +103,9 @@ repos:
- id: ament_copyright
name: ament_copyright
description: Check if copyright notice is available in all files.
- stages: [commit]
entry: ament_copyright
language: system
+ exclude: .*/conf\.py$
# Docs - RestructuredText hooks
- repo: https://github.com/PyCQA/doc8
@@ -128,8 +126,18 @@ repos:
# Spellcheck in comments and docs
# skipping of *.svg files is not working...
- repo: https://github.com/codespell-project/codespell
- rev: v2.2.4
+ rev: v2.3.0
hooks:
- id: codespell
- args: ['--write-changes']
- exclude: CHANGELOG\.rst|\.(svg|pyc)$
+ args: ['--write-changes', '--uri-ignore-words-list=ist', '-L manuel']
+ exclude: CHANGELOG\.rst|\.(svg|pyc|drawio)$
+
+ - repo: https://github.com/python-jsonschema/check-jsonschema
+ rev: 0.28.6
+ hooks:
+ - id: check-github-workflows
+ args: ["--verbose"]
+ - id: check-github-actions
+ args: ["--verbose"]
+ - id: check-dependabot
+ args: ["--verbose"]
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d9cdc27041..df91cfbf20 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -54,7 +54,9 @@ As this project, by default, uses the default GitHub issue labels
## Licensing
-Any contribution that you make to this repository will be under the Apache 2 License, as dictated by that [license]:
+Any contribution that you make to this repository will
+be under the Apache 2 License, as dictated by that
+[license](http://www.apache.org/licenses/LICENSE-2.0.html):
~~~
5. Submission of Contributions. Unless You explicitly state otherwise,
@@ -69,4 +71,3 @@ Any contribution that you make to this repository will be under the Apache 2 Lic
[issues]: https://github.com/ros-controls/ros2_control/issues
[closed-issues]: https://github.com/ros-controls/ros2_control/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20
[help-wanted]: https://github.com/ros-controls/ros2_control/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22
-[license]: http://www.apache.org/licenses/LICENSE-2.0.html
diff --git a/README.md b/README.md
index 0c26afd184..40d2a3c189 100644
--- a/README.md
+++ b/README.md
@@ -11,9 +11,10 @@ For more, please check the [documentation](https://control.ros.org/).
ROS2 Distro | Branch | Build status | Documentation | Released packages
:---------: | :----: | :----------: | :-----------: | :---------------:
-**Rolling** | [`master`](https://github.com/ros-controls/ros2_control/tree/master) | [](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-binary-build-main.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-semi-binary-build-main.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)
-**Iron** | [`iron`](https://github.com/ros-controls/ros2_control/tree/iron) | [](https://github.com/ros-controls/ros2_control/actions/workflows/iron-binary-build-main.yml?branch=iron)
[](https://github.com/ros-controls/ros2_control/actions/workflows/iron-semi-binary-build-main.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) | [](https://github.com/ros-controls/ros2_control/actions/workflows/humble-binary-build-main.yml?branch=master)
[](https://github.com/ros-controls/ros2_control/actions/workflows/humble-semi-binary-build-main.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)
+**Rolling** | [`master`](https://github.com/ros-controls/ros2_control/tree/master) | [](https://github.com/ros-controls/ros2_control/actions/workflows/rolling-binary-build.yml?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) | [](https://github.com/ros-controls/ros2_control/actions/workflows/jazzy-binary-build.yml?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) | [](https://github.com/ros-controls/ros2_control/actions/workflows/iron-binary-build.yml?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) | [](https://github.com/ros-controls/ros2_control/actions/workflows/humble-binary-build.yml?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/codecov.yml b/codecov.yml
index d8a5fde3e0..97106f32ff 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -22,4 +22,5 @@ flags:
- controller_interface
- controller_manager
- hardware_interface
+ - joint_limits
- transmission_interface
diff --git a/controller_interface/CHANGELOG.rst b/controller_interface/CHANGELOG.rst
index c6471c90f6..c4f9c56800 100644
--- a/controller_interface/CHANGELOG.rst
+++ b/controller_interface/CHANGELOG.rst
@@ -2,6 +2,52 @@
Changelog for package controller_interface
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+4.13.0 (2024-07-08)
+-------------------
+* [ControllerChaining] Export state interfaces from chainable controllers (`#1021 `_)
+* Contributors: Sai Kishor Kothakota
+
+4.12.0 (2024-07-01)
+-------------------
+
+4.11.0 (2024-05-14)
+-------------------
+* Fix dependencies for source build (`#1533 `_)
+* Add find_package for ament_cmake_gen_version_h (`#1534 `_)
+* Contributors: Christoph Fröhlich
+
+4.10.0 (2024-05-08)
+-------------------
+* Working async controllers and components [not synchronized] (`#1041 `_)
+* Contributors: Márk Szitanics
+
+4.9.0 (2024-04-30)
+------------------
+* return the proper const object of the pointer in the const method (`#1494 `_)
+* Contributors: Sai Kishor Kothakota
+
+4.8.0 (2024-03-27)
+------------------
+* generate version.h file per package using the ament_generate_version_header (`#1449 `_)
+* Use ament_cmake generated rclcpp version header (`#1448 `_)
+* Contributors: Sai Kishor Kothakota
+
+4.7.0 (2024-03-22)
+------------------
+* add missing compiler definitions of RCLCPP_VERSION_MAJOR (`#1440 `_)
+* Contributors: Sai Kishor Kothakota
+
+4.6.0 (2024-03-02)
+------------------
+* Add -Werror=missing-braces to compile options (`#1423 `_)
+* added conditioning to have rolling tags compilable in older versions (`#1422 `_)
+* Contributors: Sai Kishor Kothakota
+
+4.5.0 (2024-02-12)
+------------------
+* A method to get node options to setup the controller node #api-breaking (`#1169 `_)
+* Contributors: Sai Kishor Kothakota
+
4.4.0 (2024-01-31)
------------------
diff --git a/controller_interface/CMakeLists.txt b/controller_interface/CMakeLists.txt
index 19a501dc62..3dc3bc4d0a 100644
--- a/controller_interface/CMakeLists.txt
+++ b/controller_interface/CMakeLists.txt
@@ -2,7 +2,8 @@ cmake_minimum_required(VERSION 3.16)
project(controller_interface LANGUAGES CXX)
if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)")
- add_compile_options(-Wall -Wextra -Werror=conversion -Werror=unused-but-set-variable -Werror=return-type -Werror=shadow)
+ add_compile_options(-Wall -Wextra -Werror=conversion -Werror=unused-but-set-variable -Werror=return-type -Werror=shadow
+ -Werror=missing-braces)
endif()
set(THIS_PACKAGE_INCLUDE_DEPENDS
@@ -11,6 +12,7 @@ set(THIS_PACKAGE_INCLUDE_DEPENDS
)
find_package(ament_cmake REQUIRED)
+find_package(ament_cmake_gen_version_h REQUIRED)
foreach(Dependency IN ITEMS ${THIS_PACKAGE_INCLUDE_DEPENDS})
find_package(${Dependency} REQUIRED)
endforeach()
@@ -88,3 +90,4 @@ install(TARGETS controller_interface
ament_export_targets(export_controller_interface HAS_LIBRARY_TARGET)
ament_export_dependencies(${THIS_PACKAGE_INCLUDE_DEPENDS})
ament_package()
+ament_generate_version_header(${PROJECT_NAME})
diff --git a/controller_interface/include/controller_interface/async_controller.hpp b/controller_interface/include/controller_interface/async_controller.hpp
new file mode 100644
index 0000000000..5601ff4703
--- /dev/null
+++ b/controller_interface/include/controller_interface/async_controller.hpp
@@ -0,0 +1,111 @@
+// Copyright 2024 ros2_control development team
+//
+// 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 CONTROLLER_INTERFACE__ASYNC_CONTROLLER_HPP_
+#define CONTROLLER_INTERFACE__ASYNC_CONTROLLER_HPP_
+
+#include
+#include
+#include
+
+#include "controller_interface_base.hpp"
+#include "lifecycle_msgs/msg/state.hpp"
+
+namespace controller_interface
+{
+
+class AsyncControllerThread
+{
+public:
+ /// Constructor for the AsyncControllerThread object.
+ /**
+ *
+ * \param[in] controller shared pointer to a controller.
+ * \param[in] cm_update_rate the controller manager's update rate.
+ */
+ AsyncControllerThread(
+ std::shared_ptr & controller, int cm_update_rate)
+ : terminated_(false), controller_(controller), thread_{}, cm_update_rate_(cm_update_rate)
+ {
+ }
+
+ AsyncControllerThread(const AsyncControllerThread & t) = delete;
+ AsyncControllerThread(AsyncControllerThread && t) = delete;
+
+ // Destructor, called when the component is erased from its map.
+ ~AsyncControllerThread()
+ {
+ terminated_.store(true, std::memory_order_seq_cst);
+ if (thread_.joinable())
+ {
+ thread_.join();
+ }
+ }
+
+ /// Creates the controller's thread.
+ /**
+ * Called when the controller is activated.
+ *
+ */
+ void activate()
+ {
+ thread_ = std::thread(&AsyncControllerThread::controller_update_callback, this);
+ }
+
+ /// Periodically execute the controller's update method.
+ /**
+ * Callback of the async controller's thread.
+ * **Not synchronized with the controller manager's write and read currently**
+ *
+ */
+ void controller_update_callback()
+ {
+ using TimePoint = std::chrono::system_clock::time_point;
+ unsigned int used_update_rate =
+ controller_->get_update_rate() == 0 ? cm_update_rate_ : controller_->get_update_rate();
+
+ auto previous_time = controller_->get_node()->now();
+ while (!terminated_.load(std::memory_order_relaxed))
+ {
+ auto const period = std::chrono::nanoseconds(1'000'000'000 / used_update_rate);
+ TimePoint next_iteration_time =
+ TimePoint(std::chrono::nanoseconds(controller_->get_node()->now().nanoseconds()));
+
+ if (controller_->get_state().id() == lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE)
+ {
+ auto const current_time = controller_->get_node()->now();
+ auto const measured_period = current_time - previous_time;
+ previous_time = current_time;
+ controller_->update(
+ controller_->get_node()->now(),
+ (controller_->get_update_rate() != cm_update_rate_ && controller_->get_update_rate() != 0)
+ ? rclcpp::Duration::from_seconds(1.0 / controller_->get_update_rate())
+ : measured_period);
+ }
+
+ next_iteration_time += period;
+ std::this_thread::sleep_until(next_iteration_time);
+ }
+ }
+
+private:
+ std::atomic terminated_;
+ std::shared_ptr controller_;
+ std::thread thread_;
+ unsigned int cm_update_rate_;
+};
+
+} // namespace controller_interface
+
+#endif // CONTROLLER_INTERFACE__ASYNC_CONTROLLER_HPP_
diff --git a/controller_interface/include/controller_interface/chainable_controller_interface.hpp b/controller_interface/include/controller_interface/chainable_controller_interface.hpp
index 2bdccefdc5..2e39f038b1 100644
--- a/controller_interface/include/controller_interface/chainable_controller_interface.hpp
+++ b/controller_interface/include/controller_interface/chainable_controller_interface.hpp
@@ -15,6 +15,7 @@
#ifndef CONTROLLER_INTERFACE__CHAINABLE_CONTROLLER_INTERFACE_HPP_
#define CONTROLLER_INTERFACE__CHAINABLE_CONTROLLER_INTERFACE_HPP_
+#include
#include
#include "controller_interface/controller_interface_base.hpp"
@@ -55,6 +56,9 @@ class ChainableControllerInterface : public ControllerInterfaceBase
CONTROLLER_INTERFACE_PUBLIC
bool is_chainable() const final;
+ CONTROLLER_INTERFACE_PUBLIC
+ std::vector export_state_interfaces() final;
+
CONTROLLER_INTERFACE_PUBLIC
std::vector export_reference_interfaces() final;
@@ -65,8 +69,19 @@ class ChainableControllerInterface : public ControllerInterfaceBase
bool is_in_chained_mode() const final;
protected:
- /// Virtual method that each chainable controller should implement to export its chainable
- /// interfaces.
+ /// Virtual method that each chainable controller should implement to export its read-only
+ /// chainable interfaces.
+ /**
+ * Each chainable controller implements this methods where all its state(read only) interfaces are
+ * exported. The method has the same meaning as `export_state_interfaces` method from
+ * hardware_interface::SystemInterface or hardware_interface::ActuatorInterface.
+ *
+ * \returns list of StateInterfaces that other controller can use as their inputs.
+ */
+ virtual std::vector on_export_state_interfaces();
+
+ /// Virtual method that each chainable controller should implement to export its read/write
+ /// chainable interfaces.
/**
* Each chainable controller implements this methods where all input (command) interfaces are
* exported. The method has the same meaning as `export_command_interface` method from
@@ -74,7 +89,7 @@ class ChainableControllerInterface : public ControllerInterfaceBase
*
* \returns list of CommandInterfaces that other controller can use as their outputs.
*/
- virtual std::vector on_export_reference_interfaces() = 0;
+ virtual std::vector on_export_reference_interfaces();
/// Virtual method that each chainable controller should implement to switch chained mode.
/**
@@ -114,7 +129,12 @@ class ChainableControllerInterface : public ControllerInterfaceBase
virtual return_type update_and_write_commands(
const rclcpp::Time & time, const rclcpp::Duration & period) = 0;
+ /// Storage of values for state interfaces
+ std::vector exported_state_interface_names_;
+ std::vector state_interfaces_values_;
+
/// Storage of values for reference interfaces
+ std::vector exported_reference_interface_names_;
std::vector reference_interfaces_;
private:
diff --git a/controller_interface/include/controller_interface/controller_interface.hpp b/controller_interface/include/controller_interface/controller_interface.hpp
index a3d006755f..17f39c8478 100644
--- a/controller_interface/include/controller_interface/controller_interface.hpp
+++ b/controller_interface/include/controller_interface/controller_interface.hpp
@@ -42,6 +42,14 @@ class ControllerInterface : public controller_interface::ControllerInterfaceBase
CONTROLLER_INTERFACE_PUBLIC
bool is_chainable() const final;
+ /**
+ * A non-chainable controller doesn't export any state interfaces.
+ *
+ * \returns empty list.
+ */
+ CONTROLLER_INTERFACE_PUBLIC
+ std::vector export_state_interfaces() final;
+
/**
* Controller has no reference interfaces.
*
diff --git a/controller_interface/include/controller_interface/controller_interface_base.hpp b/controller_interface/include/controller_interface/controller_interface_base.hpp
index 6ce483d1ff..1b5fd2e059 100644
--- a/controller_interface/include/controller_interface/controller_interface_base.hpp
+++ b/controller_interface/include/controller_interface/controller_interface_base.hpp
@@ -26,6 +26,7 @@
#include "hardware_interface/loaned_state_interface.hpp"
#include "rclcpp/rclcpp.hpp"
+#include "rclcpp/version.h"
#include "rclcpp_lifecycle/lifecycle_node.hpp"
namespace controller_interface
@@ -144,7 +145,7 @@ class ControllerInterfaceBase : public rclcpp_lifecycle::node_interfaces::Lifecy
std::shared_ptr get_node();
CONTROLLER_INTERFACE_PUBLIC
- std::shared_ptr get_node() const;
+ std::shared_ptr get_node() const;
CONTROLLER_INTERFACE_PUBLIC
const rclcpp_lifecycle::State & get_state() const;
@@ -172,7 +173,14 @@ class ControllerInterfaceBase : public rclcpp_lifecycle::node_interfaces::Lifecy
CONTROLLER_INTERFACE_PUBLIC
virtual rclcpp::NodeOptions define_custom_node_options() const
{
+// \note The versions conditioning is added here to support the source-compatibility with Humble
+#if RCLCPP_VERSION_MAJOR >= 21
return rclcpp::NodeOptions().enable_logger_service(true);
+#else
+ return rclcpp::NodeOptions()
+ .allow_undeclared_parameters(true)
+ .automatically_declare_parameters_from_overrides(true);
+#endif
}
/// Declare and initialize a parameter with a type.
@@ -216,11 +224,20 @@ class ControllerInterfaceBase : public rclcpp_lifecycle::node_interfaces::Lifecy
CONTROLLER_INTERFACE_PUBLIC
virtual std::vector export_reference_interfaces() = 0;
+ /**
+ * Export interfaces for a chainable controller that can be used as state interface by other
+ * controllers.
+ *
+ * \returns list of state interfaces for preceding controllers.
+ */
+ CONTROLLER_INTERFACE_PUBLIC
+ virtual std::vector export_state_interfaces() = 0;
+
/**
* Set chained mode of a chainable controller. This method triggers internal processes to switch
* a chainable controller to "chained" mode and vice-versa. Setting controller to "chained" mode
- * usually involves disabling of subscribers and other external interfaces to avoid potential
- * concurrency in input commands.
+ * usually involves the usage of the controller's reference interfaces by the other
+ * controllers
*
* \returns true if mode is switched successfully and false if not.
*/
diff --git a/controller_interface/package.xml b/controller_interface/package.xml
index 919fdf8d20..1ee83bd019 100644
--- a/controller_interface/package.xml
+++ b/controller_interface/package.xml
@@ -2,13 +2,14 @@
controller_interface
- 4.4.0
+ 4.13.0
Description of controller_interface
Bence Magyar
Denis Štogl
Apache License 2.0
ament_cmake
+ ament_cmake_gen_version_h
hardware_interface
rclcpp_lifecycle
diff --git a/controller_interface/src/chainable_controller_interface.cpp b/controller_interface/src/chainable_controller_interface.cpp
index 2f7c67741e..9f4a171ec3 100644
--- a/controller_interface/src/chainable_controller_interface.cpp
+++ b/controller_interface/src/chainable_controller_interface.cpp
@@ -44,26 +44,35 @@ return_type ChainableControllerInterface::update(
return ret;
}
-std::vector
-ChainableControllerInterface::export_reference_interfaces()
+std::vector
+ChainableControllerInterface::export_state_interfaces()
{
- auto reference_interfaces = on_export_reference_interfaces();
+ auto state_interfaces = on_export_state_interfaces();
- // check if the "reference_interfaces_" variable is resized to number of interfaces
- if (reference_interfaces_.size() != reference_interfaces.size())
+ // check if the names of the controller state interfaces begin with the controller's name
+ for (const auto & interface : state_interfaces)
{
- // TODO(destogl): Should here be "FATAL"? It is fatal in terms of controller but not for the
- // framework
- RCLCPP_FATAL(
- get_node()->get_logger(),
- "The internal storage for reference values 'reference_interfaces_' variable has size '%zu', "
- "but it is expected to have the size '%zu' equal to the number of exported reference "
- "interfaces. No reference interface will be exported. Please correct and recompile "
- "the controller with name '%s' and try again.",
- reference_interfaces_.size(), reference_interfaces.size(), get_node()->get_name());
- reference_interfaces.clear();
+ if (interface.get_prefix_name() != get_node()->get_name())
+ {
+ RCLCPP_FATAL(
+ get_node()->get_logger(),
+ "The name of the interface '%s' does not begin with the controller's name. This is "
+ "mandatory for state interfaces. No state interface will be exported. Please "
+ "correct and recompile the controller with name '%s' and try again.",
+ interface.get_name().c_str(), get_node()->get_name());
+ state_interfaces.clear();
+ break;
+ }
}
+ return state_interfaces;
+}
+
+std::vector
+ChainableControllerInterface::export_reference_interfaces()
+{
+ auto reference_interfaces = on_export_reference_interfaces();
+
// check if the names of the reference interfaces begin with the controller's name
for (const auto & interface : reference_interfaces)
{
@@ -113,4 +122,30 @@ bool ChainableControllerInterface::is_in_chained_mode() const { return in_chaine
bool ChainableControllerInterface::on_set_chained_mode(bool /*chained_mode*/) { return true; }
+std::vector
+ChainableControllerInterface::on_export_state_interfaces()
+{
+ state_interfaces_values_.resize(exported_state_interface_names_.size(), 0.0);
+ std::vector state_interfaces;
+ for (size_t i = 0; i < exported_state_interface_names_.size(); ++i)
+ {
+ state_interfaces.emplace_back(hardware_interface::StateInterface(
+ get_node()->get_name(), exported_state_interface_names_[i], &state_interfaces_values_[i]));
+ }
+ return state_interfaces;
+}
+
+std::vector
+ChainableControllerInterface::on_export_reference_interfaces()
+{
+ reference_interfaces_.resize(exported_reference_interface_names_.size(), 0.0);
+ std::vector reference_interfaces;
+ for (size_t i = 0; i < exported_reference_interface_names_.size(); ++i)
+ {
+ reference_interfaces.emplace_back(hardware_interface::CommandInterface(
+ get_node()->get_name(), exported_reference_interface_names_[i], &reference_interfaces_[i]));
+ }
+ return reference_interfaces;
+}
+
} // namespace controller_interface
diff --git a/controller_interface/src/controller_interface.cpp b/controller_interface/src/controller_interface.cpp
index 392a48ffa4..0f11bba71c 100644
--- a/controller_interface/src/controller_interface.cpp
+++ b/controller_interface/src/controller_interface.cpp
@@ -28,6 +28,11 @@ ControllerInterface::ControllerInterface() : ControllerInterfaceBase() {}
bool ControllerInterface::is_chainable() const { return false; }
+std::vector ControllerInterface::export_state_interfaces()
+{
+ return {};
+}
+
std::vector ControllerInterface::export_reference_interfaces()
{
return {};
diff --git a/controller_interface/src/controller_interface_base.cpp b/controller_interface/src/controller_interface_base.cpp
index e48e1d21b3..6b87ba5cda 100644
--- a/controller_interface/src/controller_interface_base.cpp
+++ b/controller_interface/src/controller_interface_base.cpp
@@ -121,7 +121,7 @@ std::shared_ptr ControllerInterfaceBase::get_no
return node_;
}
-std::shared_ptr ControllerInterfaceBase::get_node() const
+std::shared_ptr ControllerInterfaceBase::get_node() const
{
if (!node_.get())
{
diff --git a/controller_interface/test/test_chainable_controller_interface.cpp b/controller_interface/test/test_chainable_controller_interface.cpp
index 47487e019c..2f04273f3e 100644
--- a/controller_interface/test/test_chainable_controller_interface.cpp
+++ b/controller_interface/test/test_chainable_controller_interface.cpp
@@ -16,6 +16,9 @@
#include
+using ::testing::IsEmpty;
+using ::testing::SizeIs;
+
TEST_F(ChainableControllerInterfaceTest, default_returns)
{
TestableChainableControllerInterface controller;
@@ -31,7 +34,7 @@ TEST_F(ChainableControllerInterfaceTest, default_returns)
EXPECT_FALSE(controller.is_in_chained_mode());
}
-TEST_F(ChainableControllerInterfaceTest, export_reference_interfaces)
+TEST_F(ChainableControllerInterfaceTest, export_state_interfaces)
{
TestableChainableControllerInterface controller;
@@ -42,16 +45,16 @@ TEST_F(ChainableControllerInterfaceTest, export_reference_interfaces)
controller_interface::return_type::OK);
ASSERT_NO_THROW(controller.get_node());
- auto reference_interfaces = controller.export_reference_interfaces();
+ auto exported_state_interfaces = controller.export_state_interfaces();
- ASSERT_EQ(reference_interfaces.size(), 1u);
- EXPECT_EQ(reference_interfaces[0].get_prefix_name(), TEST_CONTROLLER_NAME);
- EXPECT_EQ(reference_interfaces[0].get_interface_name(), "test_itf");
+ ASSERT_THAT(exported_state_interfaces, SizeIs(1));
+ EXPECT_EQ(exported_state_interfaces[0].get_prefix_name(), TEST_CONTROLLER_NAME);
+ EXPECT_EQ(exported_state_interfaces[0].get_interface_name(), "test_state");
- EXPECT_EQ(reference_interfaces[0].get_value(), INTERFACE_VALUE);
+ EXPECT_EQ(exported_state_interfaces[0].get_value(), EXPORTED_STATE_INTERFACE_VALUE);
}
-TEST_F(ChainableControllerInterfaceTest, reference_interfaces_storage_not_correct_size)
+TEST_F(ChainableControllerInterfaceTest, export_reference_interfaces)
{
TestableChainableControllerInterface controller;
@@ -62,13 +65,16 @@ TEST_F(ChainableControllerInterfaceTest, reference_interfaces_storage_not_correc
controller_interface::return_type::OK);
ASSERT_NO_THROW(controller.get_node());
- // expect empty return because storage is not resized
- controller.reference_interfaces_.clear();
auto reference_interfaces = controller.export_reference_interfaces();
- ASSERT_TRUE(reference_interfaces.empty());
+
+ ASSERT_THAT(reference_interfaces, SizeIs(1));
+ EXPECT_EQ(reference_interfaces[0].get_prefix_name(), TEST_CONTROLLER_NAME);
+ EXPECT_EQ(reference_interfaces[0].get_interface_name(), "test_itf");
+
+ EXPECT_EQ(reference_interfaces[0].get_value(), INTERFACE_VALUE);
}
-TEST_F(ChainableControllerInterfaceTest, reference_interfaces_prefix_is_not_node_name)
+TEST_F(ChainableControllerInterfaceTest, interfaces_prefix_is_not_node_name)
{
TestableChainableControllerInterface controller;
@@ -83,7 +89,10 @@ TEST_F(ChainableControllerInterfaceTest, reference_interfaces_prefix_is_not_node
// expect empty return because interface prefix is not equal to the node name
auto reference_interfaces = controller.export_reference_interfaces();
- ASSERT_TRUE(reference_interfaces.empty());
+ ASSERT_THAT(reference_interfaces, IsEmpty());
+ // expect empty return because interface prefix is not equal to the node name
+ auto exported_state_interfaces = controller.export_state_interfaces();
+ ASSERT_THAT(exported_state_interfaces, IsEmpty());
}
TEST_F(ChainableControllerInterfaceTest, setting_chained_mode)
@@ -98,12 +107,15 @@ TEST_F(ChainableControllerInterfaceTest, setting_chained_mode)
ASSERT_NO_THROW(controller.get_node());
auto reference_interfaces = controller.export_reference_interfaces();
- ASSERT_EQ(reference_interfaces.size(), 1u);
+ ASSERT_THAT(reference_interfaces, SizeIs(1));
+ auto exported_state_interfaces = controller.export_state_interfaces();
+ ASSERT_THAT(exported_state_interfaces, SizeIs(1));
EXPECT_FALSE(controller.is_in_chained_mode());
// Fail setting chained mode
EXPECT_EQ(reference_interfaces[0].get_value(), INTERFACE_VALUE);
+ EXPECT_EQ(exported_state_interfaces[0].get_value(), EXPORTED_STATE_INTERFACE_VALUE);
EXPECT_FALSE(controller.set_chained_mode(true));
EXPECT_FALSE(controller.is_in_chained_mode());
@@ -116,6 +128,7 @@ TEST_F(ChainableControllerInterfaceTest, setting_chained_mode)
EXPECT_TRUE(controller.set_chained_mode(true));
EXPECT_TRUE(controller.is_in_chained_mode());
+ EXPECT_EQ(exported_state_interfaces[0].get_value(), EXPORTED_STATE_INTERFACE_VALUE_IN_CHAINMODE);
controller.configure();
EXPECT_TRUE(controller.set_chained_mode(false));
@@ -147,6 +160,7 @@ TEST_F(ChainableControllerInterfaceTest, test_update_logic)
controller_interface::return_type::OK);
ASSERT_NO_THROW(controller.get_node());
+ EXPECT_FALSE(controller.set_chained_mode(false));
EXPECT_FALSE(controller.is_in_chained_mode());
// call update and update it from subscriber because not in chained mode
@@ -154,6 +168,7 @@ TEST_F(ChainableControllerInterfaceTest, test_update_logic)
controller.update(rclcpp::Time(0), rclcpp::Duration::from_seconds(0.01)),
controller_interface::return_type::OK);
ASSERT_EQ(controller.reference_interfaces_[0], INTERFACE_VALUE_INITIAL_REF - 1);
+ ASSERT_EQ(controller.state_interfaces_values_[0], EXPORTED_STATE_INTERFACE_VALUE + 1);
// Provoke error in update from subscribers - return ERROR and update_and_write_commands not exec.
controller.set_new_reference_interface_value(INTERFACE_VALUE_SUBSCRIBER_ERROR);
@@ -161,6 +176,7 @@ TEST_F(ChainableControllerInterfaceTest, test_update_logic)
controller.update(rclcpp::Time(0), rclcpp::Duration::from_seconds(0.01)),
controller_interface::return_type::ERROR);
ASSERT_EQ(controller.reference_interfaces_[0], INTERFACE_VALUE_INITIAL_REF - 1);
+ ASSERT_EQ(controller.state_interfaces_values_[0], EXPORTED_STATE_INTERFACE_VALUE + 1);
// Provoke error from update - return ERROR, but reference interface is updated and not reduced
controller.set_new_reference_interface_value(INTERFACE_VALUE_UPDATE_ERROR);
@@ -168,6 +184,7 @@ TEST_F(ChainableControllerInterfaceTest, test_update_logic)
controller.update(rclcpp::Time(0), rclcpp::Duration::from_seconds(0.01)),
controller_interface::return_type::ERROR);
ASSERT_EQ(controller.reference_interfaces_[0], INTERFACE_VALUE_UPDATE_ERROR);
+ ASSERT_EQ(controller.state_interfaces_values_[0], EXPORTED_STATE_INTERFACE_VALUE + 1);
controller.reference_interfaces_[0] = 0.0;
@@ -181,6 +198,8 @@ TEST_F(ChainableControllerInterfaceTest, test_update_logic)
controller.update(rclcpp::Time(0), rclcpp::Duration::from_seconds(0.01)),
controller_interface::return_type::OK);
ASSERT_EQ(controller.reference_interfaces_[0], -1.0);
+ ASSERT_EQ(
+ controller.state_interfaces_values_[0], EXPORTED_STATE_INTERFACE_VALUE_IN_CHAINMODE + 1);
// Provoke error from update - return ERROR, but reference interface is updated directly
controller.set_new_reference_interface_value(INTERFACE_VALUE_SUBSCRIBER_ERROR);
@@ -189,4 +208,6 @@ TEST_F(ChainableControllerInterfaceTest, test_update_logic)
controller.update(rclcpp::Time(0), rclcpp::Duration::from_seconds(0.01)),
controller_interface::return_type::ERROR);
ASSERT_EQ(controller.reference_interfaces_[0], INTERFACE_VALUE_UPDATE_ERROR);
+ ASSERT_EQ(
+ controller.state_interfaces_values_[0], EXPORTED_STATE_INTERFACE_VALUE_IN_CHAINMODE + 1);
}
diff --git a/controller_interface/test/test_chainable_controller_interface.hpp b/controller_interface/test/test_chainable_controller_interface.hpp
index 28401f13d5..f9684c27fd 100644
--- a/controller_interface/test/test_chainable_controller_interface.hpp
+++ b/controller_interface/test/test_chainable_controller_interface.hpp
@@ -29,24 +29,28 @@ constexpr double INTERFACE_VALUE = 1989.0;
constexpr double INTERFACE_VALUE_SUBSCRIBER_ERROR = 12345.0;
constexpr double INTERFACE_VALUE_UPDATE_ERROR = 67890.0;
constexpr double INTERFACE_VALUE_INITIAL_REF = 1984.0;
+constexpr double EXPORTED_STATE_INTERFACE_VALUE = 21833.0;
+constexpr double EXPORTED_STATE_INTERFACE_VALUE_IN_CHAINMODE = 82802.0;
class TestableChainableControllerInterface
: public controller_interface::ChainableControllerInterface
{
public:
- FRIEND_TEST(ChainableControllerInterfaceTest, reference_interfaces_storage_not_correct_size);
+ FRIEND_TEST(ChainableControllerInterfaceTest, interfaces_storage_not_correct_size);
FRIEND_TEST(ChainableControllerInterfaceTest, test_update_logic);
TestableChainableControllerInterface()
{
reference_interfaces_.reserve(1);
reference_interfaces_.push_back(INTERFACE_VALUE);
+ state_interfaces_values_.reserve(1);
+ state_interfaces_values_.push_back(EXPORTED_STATE_INTERFACE_VALUE);
}
controller_interface::CallbackReturn on_init() override
{
// set default value
- name_prefix_of_reference_interfaces_ = get_node()->get_name();
+ name_prefix_of_interfaces_ = get_node()->get_name();
return controller_interface::CallbackReturn::SUCCESS;
}
@@ -63,13 +67,24 @@ class TestableChainableControllerInterface
controller_interface::interface_configuration_type::NONE};
}
+ // Implementation of ChainableController virtual methods
+ std::vector on_export_state_interfaces() override
+ {
+ std::vector state_interfaces;
+
+ state_interfaces.push_back(hardware_interface::StateInterface(
+ name_prefix_of_interfaces_, "test_state", &state_interfaces_values_[0]));
+
+ return state_interfaces;
+ }
+
// Implementation of ChainableController virtual methods
std::vector on_export_reference_interfaces() override
{
std::vector command_interfaces;
command_interfaces.push_back(hardware_interface::CommandInterface(
- name_prefix_of_reference_interfaces_, "test_itf", &reference_interfaces_[0]));
+ name_prefix_of_interfaces_, "test_itf", &reference_interfaces_[0]));
return command_interfaces;
}
@@ -78,6 +93,7 @@ class TestableChainableControllerInterface
{
if (reference_interfaces_[0] == 0.0)
{
+ state_interfaces_values_[0] = EXPORTED_STATE_INTERFACE_VALUE_IN_CHAINMODE;
return true;
}
else
@@ -107,13 +123,14 @@ class TestableChainableControllerInterface
}
reference_interfaces_[0] -= 1;
+ state_interfaces_values_[0] += 1;
return controller_interface::return_type::OK;
}
void set_name_prefix_of_reference_interfaces(const std::string & prefix)
{
- name_prefix_of_reference_interfaces_ = prefix;
+ name_prefix_of_interfaces_ = prefix;
}
void set_new_reference_interface_value(const double ref_itf_value)
@@ -121,7 +138,7 @@ class TestableChainableControllerInterface
reference_interface_value_ = ref_itf_value;
}
- std::string name_prefix_of_reference_interfaces_;
+ std::string name_prefix_of_interfaces_;
double reference_interface_value_ = INTERFACE_VALUE_INITIAL_REF;
};
diff --git a/controller_interface/test/test_controller_interface.cpp b/controller_interface/test/test_controller_interface.cpp
index 3976b2a81e..03838b1a2e 100644
--- a/controller_interface/test/test_controller_interface.cpp
+++ b/controller_interface/test/test_controller_interface.cpp
@@ -88,7 +88,8 @@ TEST(TestableControllerInterface, setting_update_rate_in_configure)
ASSERT_EQ(controller.get_update_rate(), 2812u);
// Test updating of update_rate parameter
- EXPECT_EQ(std::system("ros2 param set /testable_controller_interface update_rate 623"), 0);
+ auto res = controller.get_node()->set_parameter(rclcpp::Parameter("update_rate", 623));
+ EXPECT_EQ(res.successful, true);
// Keep the same update rate until transition from 'UNCONFIGURED' TO 'INACTIVE' does not happen
controller.configure(); // No transition so the update rate should stay intact
ASSERT_NE(controller.get_update_rate(), 623u);
diff --git a/controller_interface/test/test_force_torque_sensor.hpp b/controller_interface/test/test_force_torque_sensor.hpp
index 2d61f665c3..b26517a90c 100644
--- a/controller_interface/test/test_force_torque_sensor.hpp
+++ b/controller_interface/test/test_force_torque_sensor.hpp
@@ -68,13 +68,13 @@ class ForceTorqueSensorTest : public ::testing::Test
protected:
const size_t size_ = 6;
const std::string sensor_name_ = "test_FTS";
- std::array force_values_ = {1.1, 2.2, 3.3};
- std::array torque_values_ = {4.4, 5.5, 6.6};
+ std::array force_values_ = {{1.1, 2.2, 3.3}};
+ std::array torque_values_ = {{4.4, 5.5, 6.6}};
std::unique_ptr force_torque_sensor_;
std::vector full_interface_names_;
- const std::vector fts_interface_names_ = {"force.x", "force.y", "force.z",
- "torque.x", "torque.y", "torque.z"};
+ const std::vector fts_interface_names_ = {
+ {"force.x", "force.y", "force.z", "torque.x", "torque.y", "torque.z"}};
};
#endif // TEST_FORCE_TORQUE_SENSOR_HPP_
diff --git a/controller_interface/test/test_imu_sensor.hpp b/controller_interface/test/test_imu_sensor.hpp
index 9d0a39e7e5..801a425546 100644
--- a/controller_interface/test/test_imu_sensor.hpp
+++ b/controller_interface/test/test_imu_sensor.hpp
@@ -56,9 +56,9 @@ class IMUSensorTest : public ::testing::Test
protected:
const size_t size_ = 10;
const std::string sensor_name_ = "test_IMU";
- std::array orientation_values_ = {1.1, 2.2, 3.3, 4.4};
- std::array angular_velocity_values_ = {4.4, 5.5, 6.6};
- std::array linear_acceleration_values_ = {4.4, 5.5, 6.6};
+ std::array orientation_values_ = {{1.1, 2.2, 3.3, 4.4}};
+ std::array angular_velocity_values_ = {{4.4, 5.5, 6.6}};
+ std::array linear_acceleration_values_ = {{4.4, 5.5, 6.6}};
std::unique_ptr imu_sensor_;
std::vector full_interface_names_;
diff --git a/controller_manager/CHANGELOG.rst b/controller_manager/CHANGELOG.rst
index f5bc17f0f3..1ef5eef03b 100644
--- a/controller_manager/CHANGELOG.rst
+++ b/controller_manager/CHANGELOG.rst
@@ -2,6 +2,74 @@
Changelog for package controller_manager
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+4.13.0 (2024-07-08)
+-------------------
+* Change the spamming checking interface ERROR to DEBUG (`#1605 `_)
+* [ResourceManager] Propagate access to logger and clock interfaces to HardwareComponent (`#1585 `_)
+* [ControllerChaining] Export state interfaces from chainable controllers (`#1021 `_)
+* Contributors: Sai Kishor Kothakota
+
+4.12.0 (2024-07-01)
+-------------------
+* [rqt_controller_manager] Add hardware components (`#1455 `_)
+* [RM] Rename `load_urdf` method to `load_and_initialize_components` and add error handling there to avoid stack crashing when error happens. (`#1354 `_)
+* Fix update `period` for the first update after activation (`#1551 `_)
+* Bump version of pre-commit hooks (`#1556 `_)
+* Contributors: Christoph Fröhlich, Dr. Denis, github-actions[bot]
+
+4.11.0 (2024-05-14)
+-------------------
+* Add find_package for ament_cmake_gen_version_h (`#1534 `_)
+* Contributors: Christoph Fröhlich
+
+4.10.0 (2024-05-08)
+-------------------
+* allow extra spawner arguments to not declare every argument in launch utils (`#1505 `_)
+* Working async controllers and components [not synchronized] (`#1041 `_)
+* Add fallback controllers list to the ControllerInfo (`#1503 `_)
+* Add a functionality to look for the controller type in the params file when not parsed (`#1502 `_)
+* Add controller exception handling in controller manager (`#1507 `_)
+* Contributors: Márk Szitanics, Sai Kishor Kothakota
+
+4.9.0 (2024-04-30)
+------------------
+* Deactivate the controllers when they return error similar to hardware (`#1499 `_)
+* Component parser: Get mimic information from URDF (`#1256 `_)
+* Contributors: Christoph Fröhlich, Sai Kishor Kothakota
+
+4.8.0 (2024-03-27)
+------------------
+* generate version.h file per package using the ament_generate_version_header (`#1449 `_)
+* Use ament_cmake generated rclcpp version header (`#1448 `_)
+* Replace random_shuffle with shuffle. (`#1446 `_)
+* Contributors: Chris Lalancette, Sai Kishor Kothakota
+
+4.7.0 (2024-03-22)
+------------------
+* add missing compiler definitions of RCLCPP_VERSION_MAJOR (`#1440 `_)
+* Codeformat from new pre-commit config (`#1433 `_)
+* add conditioning to get_parameter_value method import (`#1428 `_)
+* Change the controller sorting with an approach similar to directed acyclic graphs (`#1384 `_)
+* Contributors: Christoph Fröhlich, Sai Kishor Kothakota
+
+4.6.0 (2024-03-02)
+------------------
+* Add -Werror=missing-braces to compile options (`#1423 `_)
+* added conditioning to have rolling tags compilable in older versions (`#1422 `_)
+* [CM] Remove deprecated parameters for initial component states. (`#1357 `_)
+* [BREAKING CHANGE] Use `robot_description` topic instead of `~/robot_description` and update docs regarding this (`#1410 `_)
+* [CI] Code coverage + pre-commit (`#1413 `_)
+* Fix multiple chainable controller activation bug (`#1401 `_)
+* Contributors: Christoph Fröhlich, Dr. Denis, Felix Exner (fexner), Sai Kishor Kothakota
+
+4.5.0 (2024-02-12)
+------------------
+* check for state of the controller node before cleanup (`#1363 `_)
+* [CM] Use explicit constants in controller tests. (`#1356 `_)
+* [CM] Optimized debug output about interfaces when switching controllers. (`#1355 `_)
+* A method to get node options to setup the controller node #api-breaking (`#1169 `_)
+* Contributors: Dr. Denis, Sai Kishor Kothakota
+
4.4.0 (2024-01-31)
------------------
* Move `test_components` to own package (`#1325 `_)
diff --git a/controller_manager/CMakeLists.txt b/controller_manager/CMakeLists.txt
index 0d1b88a55a..7c437d35e0 100644
--- a/controller_manager/CMakeLists.txt
+++ b/controller_manager/CMakeLists.txt
@@ -2,7 +2,8 @@ cmake_minimum_required(VERSION 3.16)
project(controller_manager LANGUAGES CXX)
if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)")
- add_compile_options(-Wall -Wextra -Werror=conversion -Werror=unused-but-set-variable -Werror=return-type -Werror=shadow)
+ add_compile_options(-Wall -Wextra -Werror=conversion -Werror=unused-but-set-variable -Werror=return-type -Werror=shadow
+ -Werror=missing-braces)
endif()
set(THIS_PACKAGE_INCLUDE_DEPENDS
@@ -20,6 +21,7 @@ set(THIS_PACKAGE_INCLUDE_DEPENDS
find_package(ament_cmake REQUIRED)
find_package(ament_cmake_python REQUIRED)
find_package(ament_cmake_core REQUIRED)
+find_package(ament_cmake_gen_version_h REQUIRED)
find_package(backward_ros REQUIRED)
foreach(Dependency IN ITEMS ${THIS_PACKAGE_INCLUDE_DEPENDS})
find_package(${Dependency} REQUIRED)
@@ -190,6 +192,12 @@ if(BUILD_TESTING)
ros2_control_test_assets::ros2_control_test_assets
)
+ install(FILES test/test_controller_spawner_with_fallback_controllers.yaml
+ DESTINATION test)
+
+ install(FILES test/test_controller_spawner_with_type.yaml
+ DESTINATION test)
+
ament_add_gmock(test_hardware_management_srvs
test/test_hardware_management_srvs.cpp
)
@@ -225,3 +233,4 @@ ament_python_install_package(controller_manager
ament_export_targets(export_controller_manager HAS_LIBRARY_TARGET)
ament_export_dependencies(${THIS_PACKAGE_INCLUDE_DEPENDS})
ament_package()
+ament_generate_version_header(${PROJECT_NAME})
diff --git a/controller_manager/controller_manager/launch_utils.py b/controller_manager/controller_manager/launch_utils.py
index 5a23c02cec..01528552d0 100644
--- a/controller_manager/controller_manager/launch_utils.py
+++ b/controller_manager/controller_manager/launch_utils.py
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import warnings
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration, PythonExpression
@@ -20,7 +21,7 @@
def generate_load_controller_launch_description(
- controller_name, controller_type=None, controller_params_file=None
+ controller_name, controller_type=None, controller_params_file=None, extra_spawner_args=[]
):
"""
Generate launch description for loading a controller using spawner.
@@ -39,7 +40,8 @@ def 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')
+ 'config', 'controller_params.yaml'),
+ extra_spawner_args=[--load-only]
)
"""
@@ -61,6 +63,12 @@ def generate_load_controller_launch_description(
]
if controller_type:
+ warnings.warn(
+ "The 'controller_type' argument is deprecated and will be removed in future releases."
+ " Declare the controller type parameter in the param file instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
spawner_arguments += ["--controller-type", controller_type]
if controller_params_file:
@@ -79,6 +87,9 @@ def generate_load_controller_launch_description(
)
]
+ if extra_spawner_args:
+ spawner_arguments += extra_spawner_args
+
spawner = Node(
package="controller_manager",
executable="spawner",
diff --git a/controller_manager/controller_manager/spawner.py b/controller_manager/controller_manager/spawner.py
index cf78442856..dbefd360b9 100644
--- a/controller_manager/controller_manager/spawner.py
+++ b/controller_manager/controller_manager/spawner.py
@@ -19,6 +19,7 @@
import sys
import time
import warnings
+import yaml
from controller_manager import (
configure_controller,
@@ -32,7 +33,13 @@
from rcl_interfaces.msg import Parameter
from rclpy.duration import Duration
from rclpy.node import Node
-from rclpy.parameter import get_parameter_value
+
+# @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 rclpy.signals import SignalHandlerOptions
from ros2param.api import call_set_parameters
@@ -129,6 +136,21 @@ def is_controller_loaded(node, controller_manager, controller_name):
return any(c.name == controller_name for c in controllers)
+def get_parameter_from_param_file(controller_name, parameter_file, parameter_name):
+ with open(parameter_file) as f:
+ parameters = yaml.safe_load(f)
+ if controller_name in parameters:
+ value = parameters[controller_name]
+ 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 : {controller_name}"
+ )
+ if parameter_name in parameters[controller_name]["ros__parameters"]:
+ return parameters[controller_name]["ros__parameters"][parameter_name]
+ else:
+ return None
+
+
def main(args=None):
rclpy.init(args=args, signal_handler_options=SignalHandlerOptions.NO)
parser = argparse.ArgumentParser()
@@ -164,7 +186,7 @@ def main(args=None):
parser.add_argument(
"-t",
"--controller-type",
- help="If not provided it should exist in the controller manager namespace",
+ help="If not provided it should exist in the controller manager namespace (deprecated)",
default=None,
required=False,
)
@@ -188,6 +210,14 @@ def main(args=None):
action="store_true",
required=False,
)
+ parser.add_argument(
+ "--fallback_controllers",
+ help="Fallback controllers list are activated as a fallback strategy when the"
+ " spawned controllers fail. When the argument is provided, it takes precedence over"
+ " the fallback_controllers list in the param file",
+ default=None,
+ nargs="+",
+ )
command_line_args = rclpy.utilities.remove_ros_args(args=sys.argv)[1:]
args = parser.parse_args(command_line_args)
@@ -195,9 +225,16 @@ def main(args=None):
controller_manager_name = args.controller_manager
controller_namespace = args.namespace
param_file = args.param_file
- controller_type = args.controller_type
controller_manager_timeout = args.controller_manager_timeout
+ if args.controller_type:
+ warnings.filterwarnings("always")
+ warnings.warn(
+ "The '--controller-type' argument is deprecated and will be removed in future releases."
+ " Declare the controller type parameter in the param file instead.",
+ DeprecationWarning,
+ )
+
if param_file and not os.path.isfile(param_file):
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), param_file)
@@ -220,6 +257,8 @@ def main(args=None):
return 1
for controller_name in controller_names:
+ fallback_controllers = args.fallback_controllers
+ controller_type = args.controller_type
prefixed_controller_name = controller_name
if controller_namespace:
prefixed_controller_name = controller_namespace + "/" + controller_name
@@ -231,6 +270,10 @@ def main(args=None):
+ bcolors.ENDC
)
else:
+ if not controller_type and param_file:
+ controller_type = get_parameter_from_param_file(
+ controller_name, param_file, "type"
+ )
if controller_type:
parameter = Parameter()
parameter.name = prefixed_controller_name + ".type"
@@ -295,6 +338,43 @@ def main(args=None):
)
return 1
+ if not fallback_controllers and param_file:
+ fallback_controllers = get_parameter_from_param_file(
+ controller_name, param_file, "fallback_controllers"
+ )
+
+ if fallback_controllers:
+ parameter = Parameter()
+ parameter.name = prefixed_controller_name + ".fallback_controllers"
+ parameter.value = get_parameter_value(string_value=str(fallback_controllers))
+
+ 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 fallback_controllers to ["'
+ + ",".join(fallback_controllers)
+ + '"] for '
+ + bcolors.BOLD
+ + prefixed_controller_name
+ + bcolors.ENDC
+ )
+ else:
+ node.get_logger().fatal(
+ bcolors.FAIL
+ + 'Could not set fallback_controllers to ["'
+ + ",".join(fallback_controllers)
+ + '"] for '
+ + bcolors.BOLD
+ + prefixed_controller_name
+ + bcolors.ENDC
+ )
+ return 1
+
ret = load_controller(node, controller_manager_name, controller_name)
if not ret.ok:
node.get_logger().fatal(
diff --git a/controller_manager/doc/controller_chaining.rst b/controller_manager/doc/controller_chaining.rst
index c83ed97ac0..1103a7ae5a 100644
--- a/controller_manager/doc/controller_chaining.rst
+++ b/controller_manager/doc/controller_chaining.rst
@@ -27,7 +27,7 @@ To describe the intent of this document, lets focus on the simple yet sufficient
:alt: Example2
-In this example, we want to chain 'position_tracking' controller with 'diff_drive_controller' and two PID controllers.
+In this example, we want to chain 'position_tracking' controller with 'diff_drive_controller' and two PID controllers as well as the 'robot_localization' controller.
Let's now imagine a use-case where we don't only want to run all those controllers as a group, but also flexibly add preceding steps.
This means the following:
@@ -37,9 +37,19 @@ This means the following:
2. Then "diff_drive_controller" is activated and attaches itself to the virtual input interfaces of PID controllers.
PID controllers also get informed that they are working in chained mode and therefore disable their external interface through subscriber.
Now we check if kinematics of differential robot is running properly.
- 3. After that, "position_tracking" can be activated and attached to "diff_drive_controller" that disables its external interfaces.
- 4. If any of the controllers is deactivated, also all preceding controllers are deactivated.
+ 3. Once the 'diff_drive_controller' is activated, it exposes the 'odom' state interfaces that is used by 'odom_publisher' as well as 'sensor_fusion' controllers.
+ The 'odom_publisher' controller is activated and attaches itself to the exported 'odom' state interfaces of 'diff_drive_controller'.
+ The 'sensor_fusion' controller is activated and attaches itself to the exported 'odom' state interfaces of 'diff_drive_controller' along with the 'imu' state interfaces.
+ 4. Once the 'sensor_fusion' controller is activated, it exposes the 'odom' state interfaces that is used by 'robot_localization' controller.
+ The 'robot_localization' controller is activated and attaches itself to the 'odom' state interfaces of 'sensor_fusion' controller.
+ Once activated, the 'robot_localization' controller exposes the 'actual_pose' state interfaces that is used by 'position_tracking' controller.
+ 5. After that, "position_tracking" can be activated and attached to "diff_drive_controller" that disables its external interfaces and to the 'robot_localization' controller which provides the 'actual_pose' state interface.
+ 6. If any of the controllers is deactivated, also all preceding controllers needs to be deactivated.
+.. note::
+
+ Only the controllers that exposes the reference interfaces are switched to chained mode, when their reference interfaces are used by other controllers. When their reference interfaces are not used by the other controllers, they are switched back to get references from the subscriber.
+ However, the controllers that exposes the state interfaces are not triggered to chained mode, when their state interfaces are used by other controllers.
Implementation
--------------
@@ -47,19 +57,34 @@ Implementation
A Controller Base-Class: ChainableController
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-A ``ChainableController`` extends ``ControllerInterface`` class with ``virtual InterfaceConfiguration input_interface_configuration() const = 0`` method.
-This method should implement for each controller that **can be preceded** by another controller exporting all the input interfaces.
-For simplicity reasons, it is assumed for now that controller's all input interfaces are used.
-Therefore, do not try to implement any exclusive combinations of input interfaces, but rather write multiple controllers if you need exclusivity.
+A ``ChainableController`` extends ``ControllerInterface`` class with ``virtual std::vector export_reference_interfaces() = 0`` method as well as ``virtual std::vector export_state_interfaces() = 0`` method.
+This method should be implemented for each controller that **can be preceded** by another controller exporting all the reference/state interfaces.
+For simplicity reasons, it is assumed for now that controller's all reference interfaces are used by other controller. However, the state interfaces exported by the controller, can be used by multiple controllers at the same time and with the combination they want.
+Therefore, do not try to implement any exclusive combinations of reference interfaces, but rather write multiple controllers if you need exclusivity.
-The ``ChainableController`` base class implements ``void set_chained_mode(bool activate)`` that sets an internal flag that a controller is used by another controller (in chained mode) and calls ``virtual void on_set_chained_mode(bool activate) = 0`` that implements controller's specific actions when chained modes is activated or deactivated, e.g., deactivating subscribers.
+The ``ChainableController`` base class implements ``void set_chained_mode(bool activate)`` that sets an internal flag that a controller is used by another controller (in chained mode) and calls ``virtual void on_set_chained_mode(bool activate) = 0`` that implements controller's specific actions when chained mode is activated or deactivated, e.g., deactivating subscribers.
As an example, PID controllers export one virtual interface ``pid_reference`` and stop their subscriber ``/pid_reference`` when used in chained mode. 'diff_drive_controller' controller exports list of virtual interfaces ``/v_x``, ``/v_y``, and ``/w_z``, and stops subscribers from topics ``/cmd_vel`` and ``/cmd_vel_unstamped``. Its publishers can continue running.
+Nomenclature
+^^^^^^^^^^^^^^
+
+There are two types of interfaces within the context of ``ros2_control``: ``CommandInterface`` and ``StateInterface``.
+
+- The ``CommandInterface`` is a Read-Write type of interface that can be used to get and set values. Its typical use-case is to set command values to the hardware.
+- The ``StateInterface`` is a Read-Only type of interface that can be used to get values. Its typical use-case is to get actual state values from the hardware.
+
+These interfaces are utilized in different places within the controller in order to have a functional controller or controller chain that commands the hardware.
+
+- The ``virtual InterfaceConfiguration command_interface_configuration() const`` method defined in the ``ControllerInterface`` class is used to define the command interfaces used by the controller. These interfaces are used to command the hardware or the exposed reference interfaces from another controller. The ``controller_manager`` uses this configuration to claim the command interfaces from the ``ResourceManager``.
+- The ``virtual InterfaceConfiguration state_interface_configuration() const`` method defined in the ``ControllerInterface`` class is used to define the state interfaces used by the controller. These interfaces are used to get the actual state values from the hardware or the exposed state interfaces from another controller. The ``controller_manager`` uses this configuration to claim the state interfaces from the ``ResourceManager``.
+- The ``std::vector export_reference_interfaces()`` method defined in the ``ChainableController`` class is used to define the reference interfaces exposed by the controller. These interfaces are used to command the controller from other controllers.
+- The ``std::vector export_state_interfaces()`` method defined in the ``ChainableController`` class is used to define the state interfaces exposed by the controller. These interfaces are used to get the actual state values from the controller by other controllers.
+
Inner Resource Management
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-After configuring a chainable controller, controller manager calls ``input_interface_configuration`` method and takes ownership over controller's input interfaces.
+After configuring a chainable controller, controller manager calls ``export_reference_interfaces`` and ``export_state_interfaces`` method and takes ownership over controller's exported reference/state interfaces.
This is the same process as done by ``ResourceManager`` and hardware interfaces.
Controller manager maintains "claimed" status of interface in a vector (the same as done in ``ResourceManager``).
@@ -71,6 +96,8 @@ Chained controllers must be activated and deactivated together or in the proper
This means you must first activate all following controllers to have the preceding one activated.
For the deactivation there is the inverse rule - all preceding controllers have to be deactivated before the following controller is deactivated.
One can also think of it as an actual chain, you can not add a chain link or break the chain in the middle.
+The chained controllers can also be activated when parsed as in a single list through the fields ``activate_controllers`` or ``deactivate_controllers`` in the ``switch_controllers`` service provided by the controller_manager.
+The controller_manager ``spawner`` can also be used to activate all the controllers of the chain in a single call, by parsing the argument ``--activate-as-group``.
Debugging outputs
@@ -84,4 +111,4 @@ Debugging outputs
Closing remarks
----------------------------
-- Maybe addition of the new controller's type ``ChainableController`` is not necessary. It would also be feasible to add implementation of ``input_interface_configuration()`` method into ``ControllerInterface`` class with default result ``interface_configuration_type::NONE``.
+- Maybe addition of the new controller's type ``ChainableController`` is not necessary. It would also be feasible to add implementation of ``export_reference_interfaces()`` and ``export_state_interfaces()`` method into ``ControllerInterface`` class with default result ``interface_configuration_type::NONE``.
diff --git a/controller_manager/doc/images/chaining_example2.png b/controller_manager/doc/images/chaining_example2.png
index 1ba49a116e..c21ab88d72 100644
Binary files a/controller_manager/doc/images/chaining_example2.png and b/controller_manager/doc/images/chaining_example2.png differ
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 46c46fa028..02e532866d 100644
--- a/controller_manager/doc/userdoc.rst
+++ b/controller_manager/doc/userdoc.rst
@@ -45,6 +45,15 @@ Alternatives to the standard kernel include
Though installing a realtime-kernel will definitely get the best results when it comes to low
jitter, using a lowlatency kernel can improve things a lot with being really easy to install.
+Subscribers
+-----------
+
+robot_description [std_msgs::msg::String]
+ String with the URDF xml, e.g., from ``robot_state_publisher``.
+ Reloading of the URDF is not supported yet.
+ All joints defined in the ````-tag have to be present in the URDF.
+
+
Parameters
-----------
@@ -70,19 +79,56 @@ hardware_components_initial_state.unconfigured (optional; list; default:
hardware_components_initial_state.inactive (optional; list; default: empty)
Defines which hardware components will be configured immediately when controller manager is started.
-robot_description (mandatory; string)
- String with the URDF string as robot description.
- This is usually result of the parsed description files by ``xacro`` command.
-
update_rate (mandatory; integer)
The frequency of controller manager's real-time update loop.
This loop reads states from hardware, updates controller and writes commands to hardware.
-
.type
Name of a plugin exported using ``pluginlib`` for a controller.
This is a class from which controller's instance with name "``controller_name``" is created.
+Handling Multiple Controller Managers
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When dealing with multiple controller managers, you have two options for managing different robot descriptions:
+
+1. **Using Namespaces:** You can place both the ``robot_state_publisher`` and the ``controller_manager`` nodes into the same namespace.
+
+.. code-block:: python
+
+ control_node = Node(
+ package="controller_manager",
+ executable="ros2_control_node",
+ parameters=[robot_controllers],
+ output="both",
+ namespace="rrbot",
+ )
+ robot_state_pub_node = Node(
+ package="robot_state_publisher",
+ executable="robot_state_publisher",
+ output="both",
+ parameters=[robot_description],
+ namespace="rrbot",
+ )
+
+2. **Using Remappings:** You can use remappings to handle different robot descriptions. This involves relaying topics using the ``remappings`` tag, allowing you to specify custom topics for each controller manager.
+
+.. code-block:: python
+
+ control_node = Node(
+ package="controller_manager",
+ executable="ros2_control_node",
+ parameters=[robot_controllers],
+ output="both",
+ remappings=[('robot_description', '/rrbot/robot_description')]
+ )
+ robot_state_pub_node = Node(
+ package="robot_state_publisher",
+ executable="robot_state_publisher",
+ output="both",
+ parameters=[robot_description],
+ namespace="rrbot",
+ )
Helper scripts
--------------
@@ -138,6 +184,21 @@ There are two scripts to interact with controller manager from launch files:
-c CONTROLLER_MANAGER, --controller-manager CONTROLLER_MANAGER
Name of the controller manager ROS node
+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
-----------------------------------------
@@ -179,6 +240,10 @@ Note that not all controllers have to be restarted, e.g., broadcasters.
Restarting hardware
^^^^^^^^^^^^^^^^^^^^^
-If hardware gets restarted then you should go through its lifecycle again.
-This can be simply achieved by returning ``ERROR`` from ``write`` and ``read`` methods of interface implementation.
-**NOT IMPLEMENTED YET - PLEASE STOP/RESTART ALL CONTROLLERS MANUALLY FOR NOW** The controller manager detects that and stops all the controllers that are commanding that hardware and restarts broadcasters that are listening to its states.
+If hardware gets restarted then you should go through its lifecycle again in order to reconfigure and export the interfaces
+
+Hardware and Controller Errors
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If the hardware during it's ``read`` or ``write`` method returns ``return_type::ERROR``, the controller manager will stop all controllers that are using the hardware's command and state interfaces.
+Likewise, if a controller returns ``return_type::ERROR`` from its ``update`` method, the controller manager will deactivate the respective controller. In future, the controller manager will try to start any fallback controllers if available.
diff --git a/controller_manager/include/controller_manager/controller_manager.hpp b/controller_manager/include/controller_manager/controller_manager.hpp
index c0a22fe5bd..393c0e64f9 100644
--- a/controller_manager/include/controller_manager/controller_manager.hpp
+++ b/controller_manager/include/controller_manager/controller_manager.hpp
@@ -23,6 +23,7 @@
#include
#include
+#include "controller_interface/async_controller.hpp"
#include "controller_interface/chainable_controller_interface.hpp"
#include "controller_interface/controller_interface.hpp"
#include "controller_interface/controller_interface_base.hpp"
@@ -81,6 +82,13 @@ class ControllerManager : public rclcpp::Node
const std::string & node_namespace = "",
const rclcpp::NodeOptions & options = get_cm_node_options());
+ CONTROLLER_MANAGER_PUBLIC
+ ControllerManager(
+ std::shared_ptr executor, const std::string & urdf,
+ bool activate_all_hw_components, const std::string & manager_node_name = "controller_manager",
+ const std::string & node_namespace = "",
+ const rclcpp::NodeOptions & options = get_cm_node_options());
+
CONTROLLER_MANAGER_PUBLIC
virtual ~ControllerManager() = default;
@@ -192,10 +200,36 @@ class ControllerManager : public rclcpp::Node
// the executor (see issue #260).
// rclcpp::CallbackGroup::SharedPtr deterministic_callback_group_;
- // Per controller update rate support
+ /// Interface for external components to check if Resource Manager is initialized.
+ /**
+ * Checks if components in Resource Manager are loaded and initialized.
+ * \returns true if they are initialized, false otherwise.
+ */
+ CONTROLLER_MANAGER_PUBLIC
+ bool is_resource_manager_initialized() const
+ {
+ return resource_manager_ && resource_manager_->are_components_initialized();
+ }
+
+ /// Update rate of the main control loop in the controller manager.
+ /**
+ * Update rate of the main control loop in the controller manager.
+ * The method is used for per-controller update rate support.
+ *
+ * \returns update rate of the controller manager.
+ */
CONTROLLER_MANAGER_PUBLIC
unsigned int get_update_rate() const;
+ /// Deletes all async controllers and components.
+ /**
+ * Needed to join the threads immediately after the control loop is ended
+ * to avoid unnecessary iterations. Otherwise
+ * the threads will be joined only when the controller manager gets destroyed.
+ */
+ CONTROLLER_MANAGER_PUBLIC
+ void shutdown_async_controllers_and_components();
+
protected:
CONTROLLER_MANAGER_PUBLIC
void init_services();
@@ -390,28 +424,28 @@ class ControllerManager : public rclcpp::Node
const std::vector & controllers, int strictness,
const ControllersListIterator controller_it);
- /// A method to be used in the std::sort method to sort the controllers to be able to
- /// execute them in a proper order
/**
- * Compares the controllers ctrl_a and ctrl_b and then returns which comes first in the sequence
- *
- * @note The following conditions needs to be handled while ordering the controller list
- * 1. The controllers that do not use any state or command interfaces are updated first
- * 2. The controllers that use only the state system interfaces only are updated next
- * 3. The controllers that use any of an another controller's reference interface are updated
- * before the preceding controller
- * 4. The controllers that use the controller's estimated interfaces are updated after the
- * preceding controller
- * 5. The controllers that only use the hardware command interfaces are updated last
- * 6. All inactive controllers go at the end of the list
+ * @brief Inserts a controller into an ordered list based on dependencies to compute the
+ * controller chain.
*
- * \param[in] controllers list of controllers to compare their names to interface's prefix.
+ * This method computes the controller chain by inserting the provided controller name into an
+ * ordered list of controllers based on dependencies. It ensures that controllers are inserted in
+ * the correct order so that dependencies are satisfied.
*
- * @return true, if ctrl_a needs to execute first, else false
+ * @param ctrl_name The name of the controller to be inserted into the chain.
+ * @param controller_iterator An iterator pointing to the position in the ordered list where the
+ * controller should be inserted.
+ * @param append_to_controller Flag indicating whether the controller should be appended or
+ * prepended to the parsed iterator.
+ * @note The specification of controller dependencies is in the ControllerChainSpec,
+ * containing information about following and preceding controllers. This struct should include
+ * the neighboring controllers with their relationships to the provided controller.
+ * `following_controllers` specify controllers that come after the provided controller.
+ * `preceding_controllers` specify controllers that come before the provided controller.
*/
- bool controller_sorting(
- const ControllerSpec & ctrl_a, const ControllerSpec & ctrl_b,
- const std::vector & controllers);
+ void update_list_with_controller_chain(
+ const std::string & ctrl_name, std::vector::iterator controller_iterator,
+ bool append_to_controller);
void controller_activity_diagnostic_callback(diagnostic_updater::DiagnosticStatusWrapper & stat);
@@ -515,6 +549,8 @@ class ControllerManager : public rclcpp::Node
};
RTControllerListWrapper rt_controllers_wrapper_;
+ std::unordered_map controller_chain_spec_;
+ std::vector ordered_controllers_names_;
/// mutex copied from ROS1 Control, protects service callbacks
/// not needed if we're guaranteed that the callbacks don't come from multiple threads
std::mutex services_lock_;
@@ -544,6 +580,9 @@ class ControllerManager : public rclcpp::Node
std::vector activate_command_interface_request_,
deactivate_command_interface_request_;
+ std::map> controller_chained_reference_interfaces_cache_;
+ std::map> controller_chained_state_interfaces_cache_;
+
std::string robot_description_;
rclcpp::Subscription::SharedPtr robot_description_subscription_;
rclcpp::TimerBase::SharedPtr robot_description_notification_timer_;
@@ -562,65 +601,7 @@ class ControllerManager : public rclcpp::Node
SwitchParams switch_params_;
- class ControllerThreadWrapper
- {
- public:
- ControllerThreadWrapper(
- std::shared_ptr & controller,
- int cm_update_rate)
- : terminated_(false), controller_(controller), thread_{}, cm_update_rate_(cm_update_rate)
- {
- }
-
- ControllerThreadWrapper(const ControllerThreadWrapper & t) = delete;
- ControllerThreadWrapper(ControllerThreadWrapper && t) = default;
- ~ControllerThreadWrapper()
- {
- terminated_.store(true, std::memory_order_seq_cst);
- if (thread_.joinable())
- {
- thread_.join();
- }
- }
-
- void activate()
- {
- thread_ = std::thread(&ControllerThreadWrapper::call_controller_update, this);
- }
-
- void call_controller_update()
- {
- using TimePoint = std::chrono::system_clock::time_point;
- unsigned int used_update_rate =
- controller_->get_update_rate() == 0
- ? cm_update_rate_
- : controller_
- ->get_update_rate(); // determines if the controller's or CM's update rate is used
-
- while (!terminated_.load(std::memory_order_relaxed))
- {
- auto const period = std::chrono::nanoseconds(1'000'000'000 / used_update_rate);
- TimePoint next_iteration_time =
- TimePoint(std::chrono::nanoseconds(controller_->get_node()->now().nanoseconds()));
-
- if (controller_->get_state().id() == lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE)
- {
- // critical section, not implemented yet
- }
-
- next_iteration_time += period;
- std::this_thread::sleep_until(next_iteration_time);
- }
- }
-
- private:
- std::atomic terminated_;
- std::shared_ptr controller_;
- std::thread thread_;
- unsigned int cm_update_rate_;
- };
-
- std::unordered_map>
+ std::unordered_map>
async_controller_threads_;
};
diff --git a/controller_manager/include/controller_manager/controller_spec.hpp b/controller_manager/include/controller_manager/controller_spec.hpp
index 6f7483f3ec..d13e9c56bd 100644
--- a/controller_manager/include/controller_manager/controller_spec.hpp
+++ b/controller_manager/include/controller_manager/controller_spec.hpp
@@ -41,5 +41,10 @@ struct ControllerSpec
std::shared_ptr next_update_cycle_time;
};
+struct ControllerChainSpec
+{
+ std::vector following_controllers;
+ std::vector preceding_controllers;
+};
} // namespace controller_manager
#endif // CONTROLLER_MANAGER__CONTROLLER_SPEC_HPP_
diff --git a/controller_manager/package.xml b/controller_manager/package.xml
index e58ff84c3d..c8da38364a 100644
--- a/controller_manager/package.xml
+++ b/controller_manager/package.xml
@@ -2,13 +2,14 @@
controller_manager
- 4.4.0
+ 4.13.0
Description of controller_manager
Bence Magyar
Denis Štogl
Apache License 2.0
ament_cmake
+ ament_cmake_gen_version_h
ament_cmake_python
ament_index_cpp
diff --git a/controller_manager/src/controller_manager.cpp b/controller_manager/src/controller_manager.cpp
index 932dfd392b..fa1fd067da 100644
--- a/controller_manager/src/controller_manager.cpp
+++ b/controller_manager/src/controller_manager.cpp
@@ -25,6 +25,7 @@
#include "hardware_interface/types/lifecycle_state_names.hpp"
#include "lifecycle_msgs/msg/state.hpp"
#include "rclcpp/rclcpp.hpp"
+#include "rclcpp/version.h"
#include "rclcpp_lifecycle/state.hpp"
namespace // utility
@@ -36,10 +37,24 @@ static constexpr const char * kChainableControllerInterfaceClassName =
"controller_interface::ChainableControllerInterface";
// Changed services history QoS to keep all so we don't lose any client service calls
+// \note The versions conditioning is added here to support the source-compatibility with Humble
+#if RCLCPP_VERSION_MAJOR >= 17
rclcpp::QoS qos_services =
rclcpp::QoS(rclcpp::QoSInitialization(RMW_QOS_POLICY_HISTORY_KEEP_ALL, 1))
.reliable()
.durability_volatile();
+#else
+static const rmw_qos_profile_t qos_services = {
+ RMW_QOS_POLICY_HISTORY_KEEP_ALL,
+ 1, // message queue depth
+ RMW_QOS_POLICY_RELIABILITY_RELIABLE,
+ RMW_QOS_POLICY_DURABILITY_VOLATILE,
+ RMW_QOS_DEADLINE_DEFAULT,
+ RMW_QOS_LIFESPAN_DEFAULT,
+ RMW_QOS_POLICY_LIVELINESS_SYSTEM_DEFAULT,
+ RMW_QOS_LIVELINESS_LEASE_DURATION_DEFAULT,
+ false};
+#endif
inline bool is_controller_inactive(const controller_interface::ControllerInterfaceBase & controller)
{
@@ -68,11 +83,11 @@ bool controller_name_compare(const controller_manager::ControllerSpec & a, const
return a.info.name == name;
}
-/// Checks if a command interface belongs to a controller based on its prefix.
+/// Checks if an interface belongs to a controller based on its prefix.
/**
- * A command interface can be provided by a controller in which case is called "reference"
- * interface.
- * This means that the @interface_name starts with the name of a controller.
+ * A State/Command interface can be provided by a controller in which case is called
+ * "state/reference" interface. This means that the @interface_name starts with the name of a
+ * controller.
*
* \param[in] interface_name to be found in the map.
* \param[in] controllers list of controllers to compare their names to interface's prefix.
@@ -80,7 +95,7 @@ bool controller_name_compare(const controller_manager::ControllerSpec & a, const
* @interface_name belongs to.
* \return true if interface has a controller name as prefix, false otherwise.
*/
-bool command_interface_is_reference_interface_of_controller(
+bool is_interface_a_chained_interface(
const std::string interface_name,
const std::vector & controllers,
controller_manager::ControllersListIterator & following_controller_it)
@@ -110,136 +125,50 @@ bool command_interface_is_reference_interface_of_controller(
{
RCLCPP_DEBUG(
rclcpp::get_logger("ControllerManager::utils"),
- "Required command interface '%s' with prefix '%s' is not reference interface.",
- interface_name.c_str(), interface_prefix.c_str());
+ "Required interface '%s' with prefix '%s' is not a chain interface.", interface_name.c_str(),
+ interface_prefix.c_str());
return false;
}
return true;
}
-/**
- * A method to retrieve the names of all it's following controllers given a controller name
- * For instance, for the following case
- * A -> B -> C -> D
- * When called with B, returns C and D
- * NOTE: A -> B signifies that the controller A is utilizing the reference interfaces exported from
- * the controller B (or) the controller B is utilizing the expected interfaces exported from the
- * controller A
- *
- * @param controller_name - Name of the controller for checking the tree
- * \param[in] controllers list of controllers to compare their names to interface's prefix.
- * @return list of controllers that are following the given controller in a chain. If none, return
- * empty.
- */
-std::vector get_following_controller_names(
- const std::string controller_name,
- const std::vector & controllers)
+template
+void add_element_to_list(std::vector & list, const T & element)
{
- std::vector following_controllers;
- auto controller_it = std::find_if(
- controllers.begin(), controllers.end(),
- std::bind(controller_name_compare, std::placeholders::_1, controller_name));
- if (controller_it == controllers.end())
+ if (std::find(list.begin(), list.end(), element) == list.end())
{
- RCLCPP_DEBUG(
- rclcpp::get_logger("ControllerManager::utils"),
- "Required controller : '%s' is not found in the controller list ", controller_name.c_str());
-
- return following_controllers;
+ // Only add to the list if it doesn't exist
+ list.push_back(element);
}
- // If the controller is not configured, return empty
- if (!(is_controller_active(controller_it->c) || is_controller_inactive(controller_it->c)))
+}
+
+template
+void remove_element_from_list(std::vector & list, const T & element)
+{
+ auto itr = std::find(list.begin(), list.end(), element);
+ if (itr != list.end())
{
- return following_controllers;
+ list.erase(itr);
}
- const auto cmd_itfs = controller_it->c->command_interface_configuration().names;
- for (const auto & itf : cmd_itfs)
- {
- controller_manager::ControllersListIterator ctrl_it;
- if (command_interface_is_reference_interface_of_controller(itf, controllers, ctrl_it))
- {
- RCLCPP_DEBUG(
- rclcpp::get_logger("ControllerManager::utils"),
- "The interface is a reference interface of controller : %s", ctrl_it->info.name.c_str());
- following_controllers.push_back(ctrl_it->info.name);
- const std::vector ctrl_names =
- get_following_controller_names(ctrl_it->info.name, controllers);
- for (const std::string & controller : ctrl_names)
- {
- if (
- std::find(following_controllers.begin(), following_controllers.end(), controller) ==
- following_controllers.end())
- {
- // Only add to the list if it doesn't exist
- following_controllers.push_back(controller);
- }
- }
- }
- }
- return following_controllers;
}
-/**
- * A method to retrieve the names of all it's preceding controllers given a controller name
- * For instance, for the following case
- * A -> B -> C -> D
- * When called with C, returns A and B
- * NOTE: A -> B signifies that the controller A is utilizing the reference interfaces exported from
- * the controller B (or) the controller B is utilizing the expected interfaces exported from the
- * controller A
- *
- * @param controller_name - Name of the controller for checking the tree
- * \param[in] controllers list of controllers to compare their names to interface's prefix.
- * @return list of controllers that are preceding the given controller in a chain. If none, return
- * empty.
- */
-std::vector get_preceding_controller_names(
- const std::string controller_name,
- const std::vector & controllers)
+void controller_chain_spec_cleanup(
+ std::unordered_map & ctrl_chain_spec,
+ const std::string & controller)
{
- std::vector preceding_controllers;
- auto controller_it = std::find_if(
- controllers.begin(), controllers.end(),
- std::bind(controller_name_compare, std::placeholders::_1, controller_name));
- if (controller_it == controllers.end())
+ const auto following_controllers = ctrl_chain_spec[controller].following_controllers;
+ const auto preceding_controllers = ctrl_chain_spec[controller].preceding_controllers;
+ for (const auto & flwg_ctrl : following_controllers)
{
- RCLCPP_DEBUG(
- rclcpp::get_logger("ControllerManager::utils"),
- "Required controller : '%s' is not found in the controller list ", controller_name.c_str());
- return preceding_controllers;
+ remove_element_from_list(ctrl_chain_spec[flwg_ctrl].preceding_controllers, controller);
}
- for (const auto & ctrl : controllers)
+ for (const auto & preced_ctrl : preceding_controllers)
{
- // If the controller is not configured, then continue
- if (!(is_controller_active(ctrl.c) || is_controller_inactive(ctrl.c)))
- {
- continue;
- }
- auto cmd_itfs = ctrl.c->command_interface_configuration().names;
- for (const auto & itf : cmd_itfs)
- {
- auto split_pos = itf.find_first_of('/');
- if ((split_pos != std::string::npos) && (itf.substr(0, split_pos) == controller_name))
- {
- preceding_controllers.push_back(ctrl.info.name);
- auto ctrl_names = get_preceding_controller_names(ctrl.info.name, controllers);
- for (const std::string & controller : ctrl_names)
- {
- if (
- std::find(preceding_controllers.begin(), preceding_controllers.end(), controller) ==
- preceding_controllers.end())
- {
- // Only add to the list if it doesn't exist
- preceding_controllers.push_back(controller);
- }
- }
- }
- }
+ remove_element_from_list(ctrl_chain_spec[preced_ctrl].following_controllers, controller);
}
- return preceding_controllers;
+ ctrl_chain_spec.erase(controller);
}
-
} // namespace
namespace controller_manager
@@ -250,6 +179,10 @@ rclcpp::NodeOptions get_cm_node_options()
// Required for getting types of controllers to be loaded via service call
node_options.allow_undeclared_parameters(true);
node_options.automatically_declare_parameters_from_overrides(true);
+// \note The versions conditioning is added here to support the source-compatibility until Humble
+#if RCLCPP_VERSION_MAJOR >= 21
+ node_options.enable_logger_service(true);
+#endif
return node_options;
}
@@ -258,7 +191,7 @@ ControllerManager::ControllerManager(
const std::string & node_namespace, const rclcpp::NodeOptions & options)
: rclcpp::Node(manager_node_name, node_namespace, options),
resource_manager_(std::make_unique(
- update_rate_, this->get_node_clock_interface())),
+ this->get_node_clock_interface(), this->get_node_logging_interface())),
diagnostics_updater_(this),
executor_(executor),
loader_(std::make_shared>(
@@ -270,6 +203,39 @@ ControllerManager::ControllerManager(
init_controller_manager();
}
+ControllerManager::ControllerManager(
+ std::shared_ptr executor, const std::string & urdf,
+ bool activate_all_hw_components, const std::string & manager_node_name,
+ const std::string & node_namespace, const rclcpp::NodeOptions & options)
+: rclcpp::Node(manager_node_name, node_namespace, options),
+ update_rate_(get_parameter_or("update_rate", 100)),
+ resource_manager_(std::make_unique(
+ urdf, this->get_node_clock_interface(), this->get_node_logging_interface(),
+ activate_all_hw_components, update_rate_)),
+ diagnostics_updater_(this),
+ executor_(executor),
+ loader_(std::make_shared>(
+ kControllerInterfaceNamespace, kControllerInterfaceClassName)),
+ chainable_loader_(
+ std::make_shared>(
+ kControllerInterfaceNamespace, kChainableControllerInterfaceClassName))
+{
+ if (!get_parameter("update_rate", update_rate_))
+ {
+ RCLCPP_WARN(
+ get_logger(), "'update_rate' parameter not set, using default value of %d Hz.", update_rate_);
+ }
+
+ if (is_resource_manager_initialized())
+ {
+ init_services();
+ }
+
+ diagnostics_updater_.setHardwareID("ros2_control");
+ diagnostics_updater_.add(
+ "Controllers Activity", this, &ControllerManager::controller_activity_diagnostic_callback);
+}
+
ControllerManager::ControllerManager(
std::unique_ptr resource_manager,
std::shared_ptr executor, const std::string & manager_node_name,
@@ -292,7 +258,8 @@ void ControllerManager::init_controller_manager()
// Get parameters needed for RT "update" loop to work
if (!get_parameter("update_rate", update_rate_))
{
- RCLCPP_WARN(get_logger(), "'update_rate' parameter not set, using default value.");
+ RCLCPP_WARN(
+ get_logger(), "'update_rate' parameter not set, using default value of %d Hz.", update_rate_);
}
robot_description_notification_timer_ = create_wall_timer(
@@ -300,13 +267,17 @@ void ControllerManager::init_controller_manager()
[&]()
{
RCLCPP_WARN(
- get_logger(), "Waiting for data on '~/robot_description' topic to finish initialization");
+ get_logger(), "Waiting for data on 'robot_description' topic to finish initialization");
});
+ if (is_resource_manager_initialized())
+ {
+ init_services();
+ }
// set QoS to transient local to get messages that have already been published
// (if robot state publisher starts before controller manager)
robot_description_subscription_ = create_subscription(
- "~/robot_description", rclcpp::QoS(1).transient_local(),
+ "robot_description", rclcpp::QoS(1).transient_local(),
std::bind(&ControllerManager::robot_description_callback, this, std::placeholders::_1));
RCLCPP_INFO(
get_logger(), "Subscribing to '%s' topic for robot description.",
@@ -323,27 +294,30 @@ void ControllerManager::robot_description_callback(const std_msgs::msg::String &
RCLCPP_INFO(get_logger(), "Received robot description from topic.");
RCLCPP_DEBUG(
get_logger(), "'Content of robot description file: %s", robot_description.data.c_str());
- if (resource_manager_->is_urdf_already_loaded())
+ robot_description_ = robot_description.data;
+ if (is_resource_manager_initialized())
{
RCLCPP_WARN(
get_logger(),
- "ResourceManager has already loaded an urdf file. Ignoring attempt to reload a robot "
- "description file.");
+ "ResourceManager has already loaded a urdf. Ignoring attempt to reload a robot description.");
return;
}
- robot_description_ = robot_description.data;
init_resource_manager(robot_description_);
+ if (is_resource_manager_initialized())
+ {
+ init_services();
+ }
}
void ControllerManager::init_resource_manager(const std::string & robot_description)
{
- if (!resource_manager_->load_urdf(robot_description))
+ if (!resource_manager_->load_and_initialize_components(robot_description, update_rate_))
{
RCLCPP_WARN(
get_logger(),
- "URDF validation went wrong check the previous output. This might only mean that interfaces "
- "defined in URDF and exported by the hardware do not match. Therefore continue initializing "
- "controller manager...");
+ "Could not load and initialize hardware. Please check previous output for more details. "
+ "After you have corrected your URDF, try to publish robot description again.");
+ return;
}
// Get all components and if they are not defined in parameters activate them automatically
@@ -388,56 +362,17 @@ void ControllerManager::init_resource_manager(const std::string & robot_descript
State::PRIMARY_STATE_UNCONFIGURED, hardware_interface::lifecycle_state_names::UNCONFIGURED));
// inactive (configured)
- // BEGIN: Keep old functionality on for backwards compatibility (Remove at the end of 2023)
- std::vector configure_components_on_start = std::vector({});
- get_parameter("configure_components_on_start", configure_components_on_start);
- if (!configure_components_on_start.empty())
- {
- RCLCPP_WARN(
- get_logger(),
- "Parameter 'configure_components_on_start' is deprecated. "
- "Use 'hardware_components_initial_state.inactive' instead, to set component's initial "
- "state to 'inactive'. Don't use this parameters in combination with the new "
- "'hardware_components_initial_state' parameter structure.");
- set_components_to_state(
- "configure_components_on_start",
- rclcpp_lifecycle::State(
- State::PRIMARY_STATE_INACTIVE, hardware_interface::lifecycle_state_names::INACTIVE));
- }
- // END: Keep old functionality on humble backwards compatibility (Remove at the end of 2023)
- else
- {
- set_components_to_state(
- "hardware_components_initial_state.inactive",
- rclcpp_lifecycle::State(
- State::PRIMARY_STATE_INACTIVE, hardware_interface::lifecycle_state_names::INACTIVE));
- }
+ set_components_to_state(
+ "hardware_components_initial_state.inactive",
+ rclcpp_lifecycle::State(
+ State::PRIMARY_STATE_INACTIVE, hardware_interface::lifecycle_state_names::INACTIVE));
- // BEGIN: Keep old functionality on for backwards compatibility (Remove at the end of 2023)
- 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())
+ // activate all other components
+ for (const auto & [component, state] : components_to_activate)
{
- RCLCPP_WARN(
- get_logger(),
- "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.");
- for (const auto & component : activate_components_on_start)
- {
- resource_manager_->set_component_state(component, active_state);
- }
- }
- // END: Keep old functionality on humble for backwards compatibility (Remove at the end of 2023)
- else
- {
- // activate all other components
- for (const auto & [component, state] : components_to_activate)
- {
- resource_manager_->set_component_state(component, active_state);
- }
+ rclcpp_lifecycle::State active_state(
+ State::PRIMARY_STATE_ACTIVE, hardware_interface::lifecycle_state_names::ACTIVE);
+ resource_manager_->set_component_state(component, active_state);
}
// Init CM services first after the URDF is loaded an components are set
@@ -569,6 +504,17 @@ controller_interface::ControllerInterfaceBaseSharedPtr ControllerManager::load_c
controller_spec.info.parameters_file = parameters_file;
}
+ const std::string fallback_ctrl_param = controller_name + ".fallback_controllers";
+ std::vector fallback_controllers;
+ if (!has_parameter(fallback_ctrl_param))
+ {
+ declare_parameter(fallback_ctrl_param, rclcpp::ParameterType::PARAMETER_STRING_ARRAY);
+ }
+ if (get_parameter(fallback_ctrl_param, fallback_controllers) && !fallback_controllers.empty())
+ {
+ controller_spec.info.fallback_controllers_names = fallback_controllers;
+ }
+
return add_controller_impl(controller_spec);
}
@@ -640,9 +586,38 @@ controller_interface::return_type ControllerManager::unload_controller(
}
RCLCPP_DEBUG(get_logger(), "Cleanup controller");
+ controller_chain_spec_cleanup(controller_chain_spec_, controller_name);
// TODO(destogl): remove reference interface if chainable; i.e., add a separate method for
// cleaning-up controllers?
- controller.c->get_node()->cleanup();
+ if (is_controller_inactive(*controller.c))
+ {
+ RCLCPP_DEBUG(
+ get_logger(), "Controller '%s' is cleaned-up before unloading!", controller_name.c_str());
+ // TODO(destogl): remove reference interface if chainable; i.e., add a separate method for
+ // cleaning-up controllers?
+ try
+ {
+ const auto new_state = controller.c->get_node()->cleanup();
+ if (new_state.id() != lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED)
+ {
+ RCLCPP_WARN(
+ get_logger(), "Failed to clean-up the controller '%s' before unloading!",
+ controller_name.c_str());
+ }
+ }
+ catch (const std::exception & e)
+ {
+ RCLCPP_ERROR(
+ get_logger(), "Failed to clean-up the controller '%s' before unloading: %s",
+ controller_name.c_str(), e.what());
+ }
+ catch (...)
+ {
+ RCLCPP_ERROR(
+ get_logger(), "Failed to clean-up the controller '%s' before unloading",
+ controller_name.c_str());
+ }
+ }
executor_->remove_node(controller.c->get_node()->get_node_base_interface());
to.erase(found_it);
@@ -704,22 +679,49 @@ controller_interface::return_type ControllerManager::configure_controller(
get_logger(), "Controller '%s' is cleaned-up before configuring", controller_name.c_str());
// TODO(destogl): remove reference interface if chainable; i.e., add a separate method for
// cleaning-up controllers?
- new_state = controller->get_node()->cleanup();
- if (new_state.id() != lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED)
+ try
+ {
+ new_state = controller->get_node()->cleanup();
+ if (new_state.id() != lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED)
+ {
+ RCLCPP_ERROR(
+ get_logger(), "Controller '%s' can not be cleaned-up before configuring",
+ controller_name.c_str());
+ return controller_interface::return_type::ERROR;
+ }
+ }
+ catch (...)
{
RCLCPP_ERROR(
- get_logger(), "Controller '%s' can not be cleaned-up before configuring",
+ get_logger(), "Caught exception while cleaning-up controller '%s' before configuring",
controller_name.c_str());
return controller_interface::return_type::ERROR;
}
}
- new_state = controller->configure();
- if (new_state.id() != lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE)
+ try
+ {
+ new_state = controller->configure();
+ if (new_state.id() != lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE)
+ {
+ RCLCPP_ERROR(
+ get_logger(), "After configuring, controller '%s' is in state '%s' , expected inactive.",
+ controller_name.c_str(), new_state.label().c_str());
+ return controller_interface::return_type::ERROR;
+ }
+ }
+ catch (const std::exception & e)
+ {
+ RCLCPP_ERROR(
+ get_logger(), "Caught exception while configuring controller '%s': %s",
+ controller_name.c_str(), e.what());
+ return controller_interface::return_type::ERROR;
+ }
+ catch (...)
{
RCLCPP_ERROR(
- get_logger(), "After configuring, controller '%s' is in state '%s' , expected inactive.",
- controller_name.c_str(), new_state.label().c_str());
+ get_logger(), "Caught unknown exception while configuring controller '%s'",
+ controller_name.c_str());
return controller_interface::return_type::ERROR;
}
@@ -727,7 +729,8 @@ controller_interface::return_type ControllerManager::configure_controller(
if (controller->is_async())
{
async_controller_threads_.emplace(
- controller_name, std::make_unique(controller, update_rate_));
+ controller_name,
+ std::make_unique(controller, update_rate_));
}
const auto controller_update_rate = controller->get_update_rate();
@@ -759,16 +762,51 @@ controller_interface::return_type ControllerManager::configure_controller(
get_logger(),
"Controller '%s' is chainable. Interfaces are being exported to resource manager.",
controller_name.c_str());
- auto interfaces = controller->export_reference_interfaces();
- if (interfaces.empty())
+ auto state_interfaces = controller->export_state_interfaces();
+ auto ref_interfaces = controller->export_reference_interfaces();
+ if (ref_interfaces.empty() && state_interfaces.empty())
{
// TODO(destogl): Add test for this!
RCLCPP_ERROR(
- get_logger(), "Controller '%s' is chainable, but does not export any reference interfaces.",
+ get_logger(),
+ "Controller '%s' is chainable, but does not export any state or reference interfaces.",
controller_name.c_str());
return controller_interface::return_type::ERROR;
}
- resource_manager_->import_controller_reference_interfaces(controller_name, interfaces);
+ resource_manager_->import_controller_reference_interfaces(controller_name, ref_interfaces);
+ resource_manager_->import_controller_exported_state_interfaces(
+ controller_name, state_interfaces);
+ }
+
+ // let's update the list of following and preceding controllers
+ const auto cmd_itfs = controller->command_interface_configuration().names;
+ const auto state_itfs = controller->state_interface_configuration().names;
+ for (const auto & cmd_itf : cmd_itfs)
+ {
+ controller_manager::ControllersListIterator ctrl_it;
+ if (is_interface_a_chained_interface(cmd_itf, controllers, ctrl_it))
+ {
+ add_element_to_list(
+ controller_chain_spec_[controller_name].following_controllers, ctrl_it->info.name);
+ add_element_to_list(
+ controller_chain_spec_[ctrl_it->info.name].preceding_controllers, controller_name);
+ add_element_to_list(
+ controller_chained_reference_interfaces_cache_[ctrl_it->info.name], controller_name);
+ }
+ }
+ // This is needed when we start exporting the state interfaces from the controllers
+ for (const auto & state_itf : state_itfs)
+ {
+ controller_manager::ControllersListIterator ctrl_it;
+ if (is_interface_a_chained_interface(state_itf, controllers, ctrl_it))
+ {
+ add_element_to_list(
+ controller_chain_spec_[controller_name].preceding_controllers, ctrl_it->info.name);
+ add_element_to_list(
+ controller_chain_spec_[ctrl_it->info.name].following_controllers, controller_name);
+ add_element_to_list(
+ controller_chained_state_interfaces_cache_[ctrl_it->info.name], controller_name);
+ }
}
// Now let's reorder the controllers
@@ -779,14 +817,32 @@ controller_interface::return_type ControllerManager::configure_controller(
// Copy all controllers from the 'from' list to the 'to' list
to = from;
+ std::vector sorted_list;
- // Reordering the controllers
- std::stable_sort(
- to.begin(), to.end(),
- std::bind(
- &ControllerManager::controller_sorting, this, std::placeholders::_1, std::placeholders::_2,
- to));
+ // clear the list before reordering it again
+ ordered_controllers_names_.clear();
+ for (const auto & [ctrl_name, chain_spec] : controller_chain_spec_)
+ {
+ auto it =
+ std::find(ordered_controllers_names_.begin(), ordered_controllers_names_.end(), ctrl_name);
+ if (it == ordered_controllers_names_.end())
+ {
+ update_list_with_controller_chain(ctrl_name, ordered_controllers_names_.end(), false);
+ }
+ }
+ std::vector new_list;
+ for (const auto & ctrl : ordered_controllers_names_)
+ {
+ auto controller_it = std::find_if(
+ to.begin(), to.end(), std::bind(controller_name_compare, std::placeholders::_1, ctrl));
+ if (controller_it != to.end())
+ {
+ new_list.push_back(*controller_it);
+ }
+ }
+
+ to = new_list;
RCLCPP_DEBUG(get_logger(), "Reordered controllers list is:");
for (const auto & ctrl : to)
{
@@ -805,6 +861,13 @@ void ControllerManager::clear_requests()
{
deactivate_request_.clear();
activate_request_.clear();
+ // Set these interfaces as unavailable when clearing requests to avoid leaving them in available
+ // state without the controller being in active state
+ for (const auto & controller_name : to_chained_mode_request_)
+ {
+ resource_manager_->make_controller_exported_state_interfaces_unavailable(controller_name);
+ resource_manager_->make_controller_reference_interfaces_unavailable(controller_name);
+ }
to_chained_mode_request_.clear();
from_chained_mode_request_.clear();
activate_command_interface_request_.clear();
@@ -816,12 +879,12 @@ controller_interface::return_type ControllerManager::switch_controller(
const std::vector & deactivate_controllers, int strictness, bool activate_asap,
const rclcpp::Duration & timeout)
{
- if (!resource_manager_->is_urdf_already_loaded())
+ if (!is_resource_manager_initialized())
{
RCLCPP_ERROR(
get_logger(),
"Resource Manager is not initialized yet! Please provide robot description on "
- "'~/robot_description' topic before trying to switch controllers.");
+ "'robot_description' topic before trying to switch controllers.");
return controller_interface::return_type::ERROR;
}
@@ -864,14 +927,15 @@ controller_interface::return_type ControllerManager::switch_controller(
strictness = controller_manager_msgs::srv::SwitchController::Request::BEST_EFFORT;
}
- RCLCPP_DEBUG(get_logger(), "Switching controllers:");
+ RCLCPP_DEBUG(get_logger(), "Activating controllers:");
for (const auto & controller : activate_controllers)
{
- RCLCPP_DEBUG(get_logger(), "- Activating controller '%s'", controller.c_str());
+ RCLCPP_DEBUG(get_logger(), " - %s", controller.c_str());
}
+ RCLCPP_DEBUG(get_logger(), "Deactivating controllers:");
for (const auto & controller : deactivate_controllers)
{
- RCLCPP_DEBUG(get_logger(), "- Deactivating controller '%s'", controller.c_str());
+ RCLCPP_DEBUG(get_logger(), " - %s", controller.c_str());
}
const auto list_controllers = [this, strictness](
@@ -1040,6 +1104,14 @@ controller_interface::return_type ControllerManager::switch_controller(
}
}
+ // Check after the check if the activate and deactivate list is empty or not
+ if (activate_request_.empty() && deactivate_request_.empty())
+ {
+ RCLCPP_INFO(get_logger(), "Empty activate and deactivate list, not requesting switch");
+ clear_requests();
+ return controller_interface::return_type::OK;
+ }
+
for (const auto & controller : controllers)
{
auto to_chained_mode_list_it = std::find(
@@ -1209,6 +1281,17 @@ controller_interface::return_type ControllerManager::switch_controller(
return controller_interface::return_type::OK;
}
+ RCLCPP_DEBUG(get_logger(), "Request for command interfaces from activating controllers:");
+ for (const auto & interface : activate_command_interface_request_)
+ {
+ RCLCPP_DEBUG(get_logger(), " - %s", interface.c_str());
+ }
+ RCLCPP_DEBUG(get_logger(), "Release of command interfaces from deactivating controllers:");
+ for (const auto & interface : deactivate_command_interface_request_)
+ {
+ RCLCPP_DEBUG(get_logger(), " - %s", interface.c_str());
+ }
+
if (
!activate_command_interface_request_.empty() || !deactivate_command_interface_request_.empty())
{
@@ -1305,17 +1388,44 @@ controller_interface::ControllerInterfaceBaseSharedPtr ControllerManager::add_co
}
const rclcpp::NodeOptions controller_node_options = determine_controller_node_options(controller);
- if (
- controller.c->init(
- controller.info.name, robot_description_, get_update_rate(), get_namespace(),
- controller_node_options) == controller_interface::return_type::ERROR)
+ // Catch whatever exception the controller might throw
+ try
+ {
+ if (
+ controller.c->init(
+ controller.info.name, robot_description_, get_update_rate(), get_namespace(),
+ controller_node_options) == controller_interface::return_type::ERROR)
+ {
+ to.clear();
+ RCLCPP_ERROR(
+ get_logger(), "Could not initialize the controller named '%s'",
+ controller.info.name.c_str());
+ return nullptr;
+ }
+ }
+ catch (const std::exception & e)
+ {
+ to.clear();
+ RCLCPP_ERROR(
+ get_logger(), "Caught exception while initializing controller '%s': %s",
+ controller.info.name.c_str(), e.what());
+ return nullptr;
+ }
+ catch (...)
{
to.clear();
RCLCPP_ERROR(
- get_logger(), "Could not initialize the controller named '%s'", controller.info.name.c_str());
+ get_logger(), "Caught unknown exception while initializing controller '%s'",
+ controller.info.name.c_str());
return nullptr;
}
+ // initialize the data for the controller chain spec once it is loaded. It is needed, so when we
+ // sort the controllers later, they will be added to the list
+ controller_chain_spec_[controller.info.name] = ControllerChainSpec();
+ controller_chained_state_interfaces_cache_[controller.info.name] = {};
+ controller_chained_reference_interfaces_cache_[controller.info.name] = {};
+
executor_->add_node(controller.c->get_node()->get_node_base_interface());
to.emplace_back(controller);
@@ -1351,18 +1461,38 @@ void ControllerManager::deactivate_controllers(
auto controller = found_it->c;
if (is_controller_active(*controller))
{
- const auto new_state = controller->get_node()->deactivate();
- controller->release_interfaces();
- // if it is a chainable controller, make the reference interfaces unavailable on deactivation
- if (controller->is_chainable())
+ try
{
- resource_manager_->make_controller_reference_interfaces_unavailable(controller_name);
+ const auto new_state = controller->get_node()->deactivate();
+ controller->release_interfaces();
+
+ // if it is a chainable controller, make the reference interfaces unavailable on
+ // deactivation
+ if (controller->is_chainable())
+ {
+ resource_manager_->make_controller_exported_state_interfaces_unavailable(controller_name);
+ resource_manager_->make_controller_reference_interfaces_unavailable(controller_name);
+ }
+ if (new_state.id() != lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE)
+ {
+ RCLCPP_ERROR(
+ get_logger(), "After deactivating, controller '%s' is in state '%s', expected Inactive",
+ controller_name.c_str(), new_state.label().c_str());
+ }
}
- if (new_state.id() != lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE)
+ catch (const std::exception & e)
+ {
+ RCLCPP_ERROR(
+ get_logger(), "Caught exception while deactivating the controller '%s': %s",
+ controller_name.c_str(), e.what());
+ continue;
+ }
+ catch (...)
{
RCLCPP_ERROR(
- get_logger(), "After deactivating, controller '%s' is in state '%s', expected Inactive",
- controller_name.c_str(), new_state.label().c_str());
+ get_logger(), "Caught unknown exception while deactivating the controller '%s'",
+ controller_name.c_str());
+ continue;
}
}
}
@@ -1391,9 +1521,6 @@ void ControllerManager::switch_chained_mode(
auto controller = found_it->c;
if (!is_controller_active(*controller))
{
- // if it is a chainable controller, make the reference interfaces available on preactivation
- // (This is needed when you activate a couple of chainable controller altogether)
- resource_manager_->make_controller_reference_interfaces_available(controller_name);
if (!controller->set_chained_mode(to_chained_mode))
{
RCLCPP_ERROR(
@@ -1521,20 +1648,39 @@ void ControllerManager::activate_controllers(
}
controller->assign_interfaces(std::move(command_loans), std::move(state_loans));
- const auto new_state = controller->get_node()->activate();
- if (new_state.id() != lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE)
+ try
+ {
+ const auto new_state = controller->get_node()->activate();
+ if (new_state.id() != lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE)
+ {
+ RCLCPP_ERROR(
+ get_logger(),
+ "After activation, controller '%s' is in state '%s' (%d), expected '%s' (%d).",
+ controller->get_node()->get_name(), new_state.label().c_str(), new_state.id(),
+ hardware_interface::lifecycle_state_names::ACTIVE,
+ lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE);
+ }
+ }
+ catch (const std::exception & e)
{
RCLCPP_ERROR(
- get_logger(),
- "After activation, controller '%s' is in state '%s' (%d), expected '%s' (%d).",
- controller->get_node()->get_name(), new_state.label().c_str(), new_state.id(),
- hardware_interface::lifecycle_state_names::ACTIVE,
- lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE);
+ get_logger(), "Caught exception while activating the controller '%s': %s",
+ controller_name.c_str(), e.what());
+ continue;
+ }
+ catch (...)
+ {
+ RCLCPP_ERROR(
+ get_logger(), "Caught unknown exception while activating the controller '%s'",
+ controller_name.c_str());
+ continue;
}
// if it is a chainable controller, make the reference interfaces available on activation
if (controller->is_chainable())
{
+ // make all the exported interfaces of the controller available
+ resource_manager_->make_controller_exported_state_interfaces_available(controller_name);
resource_manager_->make_controller_reference_interfaces_available(controller_name);
}
@@ -1629,13 +1775,23 @@ void ControllerManager::list_controllers_srv_cb(
{
auto references =
resource_manager_->get_controller_reference_interface_names(controllers[i].info.name);
+ auto exported_state_interfaces =
+ resource_manager_->get_controller_exported_state_interface_names(
+ controllers[i].info.name);
controller_state.reference_interfaces.reserve(references.size());
+ controller_state.exported_state_interfaces.reserve(exported_state_interfaces.size());
for (const auto & reference : references)
{
const std::string prefix_name = controllers[i].c->get_node()->get_name();
const std::string interface_name = reference.substr(prefix_name.size() + 1);
controller_state.reference_interfaces.push_back(interface_name);
}
+ for (const auto & state_interface : exported_state_interfaces)
+ {
+ const std::string prefix_name = controllers[i].c->get_node()->get_name();
+ const std::string interface_name = state_interface.substr(prefix_name.size() + 1);
+ controller_state.exported_state_interfaces.push_back(interface_name);
+ }
}
}
response->controller.push_back(controller_state);
@@ -2041,6 +2197,7 @@ controller_interface::return_type ControllerManager::update(
++update_loop_counter_;
update_loop_counter_ %= update_rate_;
+ std::vector failed_controllers_list;
for (const auto & loaded_controller : rt_controller_list)
{
// TODO(v-lopez) we could cache this information
@@ -2053,6 +2210,17 @@ controller_interface::return_type ControllerManager::update(
run_controller_at_cm_rate ? period
: rclcpp::Duration::from_seconds((1.0 / controller_update_rate));
+ if (
+ *loaded_controller.next_update_cycle_time ==
+ rclcpp::Time(0, 0, this->get_node_clock_interface()->get_clock()->get_clock_type()))
+ {
+ // it is zero after activation
+ RCLCPP_DEBUG(
+ get_logger(), "Setting next_update_cycle_time to %fs for the controller : %s",
+ time.seconds(), loaded_controller.info.name.c_str());
+ *loaded_controller.next_update_cycle_time = time;
+ }
+
bool controller_go =
(time ==
rclcpp::Time(0, 0, this->get_node_clock_interface()->get_clock()->get_clock_type())) ||
@@ -2067,23 +2235,50 @@ controller_interface::return_type ControllerManager::update(
{
const auto controller_actual_period =
(time - *loaded_controller.next_update_cycle_time) + controller_period;
- auto controller_ret = loaded_controller.c->update(time, controller_actual_period);
-
- if (
- *loaded_controller.next_update_cycle_time ==
- rclcpp::Time(0, 0, this->get_node_clock_interface()->get_clock()->get_clock_type()))
+ auto controller_ret = controller_interface::return_type::OK;
+ // Catch exceptions thrown by the controller update function
+ try
+ {
+ controller_ret = loaded_controller.c->update(time, controller_actual_period);
+ }
+ catch (const std::exception & e)
{
- *loaded_controller.next_update_cycle_time = time;
+ RCLCPP_ERROR(
+ get_logger(), "Caught exception while updating controller '%s': %s",
+ loaded_controller.info.name.c_str(), e.what());
+ controller_ret = controller_interface::return_type::ERROR;
}
+ catch (...)
+ {
+ RCLCPP_ERROR(
+ get_logger(), "Caught unknown exception while updating controller '%s'",
+ loaded_controller.info.name.c_str());
+ controller_ret = controller_interface::return_type::ERROR;
+ }
+
*loaded_controller.next_update_cycle_time += controller_period;
if (controller_ret != controller_interface::return_type::OK)
{
+ failed_controllers_list.push_back(loaded_controller.info.name);
ret = controller_ret;
}
}
}
}
+ if (!failed_controllers_list.empty())
+ {
+ std::string failed_controllers;
+ for (const auto & controller : failed_controllers_list)
+ {
+ failed_controllers += "\n\t- " + controller;
+ }
+ RCLCPP_ERROR(
+ get_logger(), "Deactivating following controllers as their update resulted in an error :%s",
+ failed_controllers.c_str());
+
+ deactivate_controllers(rt_controller_list, failed_controllers_list);
+ }
// there are controllers to (de)activate
if (switch_params_.do_switch)
@@ -2191,6 +2386,13 @@ std::pair ControllerManager::split_command_interface(
unsigned int ControllerManager::get_update_rate() const { return update_rate_; }
+void ControllerManager::shutdown_async_controllers_and_components()
+{
+ async_controller_threads_.erase(
+ async_controller_threads_.begin(), async_controller_threads_.end());
+ resource_manager_->shutdown_async_components();
+}
+
void ControllerManager::propagate_deactivation_of_chained_mode(
const std::vector & controllers)
{
@@ -2215,12 +2417,16 @@ void ControllerManager::propagate_deactivation_of_chained_mode(
break;
}
- for (const auto & cmd_itf_name : controller.c->command_interface_configuration().names)
+ const auto ctrl_cmd_itf_names = controller.c->command_interface_configuration().names;
+ const auto ctrl_state_itf_names = controller.c->state_interface_configuration().names;
+ auto ctrl_itf_names = ctrl_cmd_itf_names;
+ ctrl_itf_names.insert(
+ ctrl_itf_names.end(), ctrl_state_itf_names.begin(), ctrl_state_itf_names.end());
+ for (const auto & ctrl_itf_name : ctrl_itf_names)
{
// controller that 'cmd_tf_name' belongs to
ControllersListIterator following_ctrl_it;
- if (command_interface_is_reference_interface_of_controller(
- cmd_itf_name, controllers, following_ctrl_it))
+ if (is_interface_a_chained_interface(ctrl_itf_name, controllers, following_ctrl_it))
{
// currently iterated "controller" is preceding controller --> add following controller
// with matching interface name to "from" chained mode list (if not already in it)
@@ -2249,12 +2455,21 @@ controller_interface::return_type ControllerManager::check_following_controllers
get_logger(), "Checking following controllers of preceding controller with name '%s'.",
controller_it->info.name.c_str());
- for (const auto & cmd_itf_name : controller_it->c->command_interface_configuration().names)
+ const auto controller_cmd_interfaces = controller_it->c->command_interface_configuration().names;
+ const auto controller_state_interfaces = controller_it->c->state_interface_configuration().names;
+ // get all interfaces of the controller
+ auto controller_interfaces = controller_cmd_interfaces;
+ controller_interfaces.insert(
+ controller_interfaces.end(), controller_state_interfaces.begin(),
+ controller_state_interfaces.end());
+ for (const auto & ctrl_itf_name : controller_interfaces)
{
+ RCLCPP_DEBUG(
+ get_logger(), "Checking interface '%s' of controller '%s'.", ctrl_itf_name.c_str(),
+ controller_it->info.name.c_str());
ControllersListIterator following_ctrl_it;
// Check if interface if reference interface and following controller exist.
- if (!command_interface_is_reference_interface_of_controller(
- cmd_itf_name, controllers, following_ctrl_it))
+ if (!is_interface_a_chained_interface(ctrl_itf_name, controllers, following_ctrl_it))
{
continue;
}
@@ -2274,9 +2489,9 @@ controller_interface::return_type ControllerManager::check_following_controllers
{
RCLCPP_WARN(
get_logger(),
- "No reference interface '%s' exist, since the following controller with name '%s' "
- "is not chainable.",
- cmd_itf_name.c_str(), following_ctrl_it->info.name.c_str());
+ "No state/reference interface '%s' exist, since the following controller with name "
+ "'%s' is not chainable.",
+ ctrl_itf_name.c_str(), following_ctrl_it->info.name.c_str());
return controller_interface::return_type::ERROR;
}
@@ -2329,10 +2544,23 @@ controller_interface::return_type ControllerManager::check_following_controllers
following_ctrl_it->info.name);
if (found_it == to_chained_mode_request_.end())
{
- to_chained_mode_request_.push_back(following_ctrl_it->info.name);
- RCLCPP_DEBUG(
- get_logger(), "Adding controller '%s' in 'to chained mode' request.",
- following_ctrl_it->info.name.c_str());
+ // if it is a chainable controller, make the reference interfaces available on preactivation
+ // (This is needed when you activate a couple of chainable controller altogether)
+ // make all the exported interfaces of the controller available
+ resource_manager_->make_controller_exported_state_interfaces_available(
+ following_ctrl_it->info.name);
+ if (
+ std::find(
+ controller_cmd_interfaces.begin(), controller_cmd_interfaces.end(), ctrl_itf_name) !=
+ controller_cmd_interfaces.end())
+ {
+ resource_manager_->make_controller_reference_interfaces_available(
+ following_ctrl_it->info.name);
+ to_chained_mode_request_.push_back(following_ctrl_it->info.name);
+ RCLCPP_DEBUG(
+ get_logger(), "Adding controller '%s' in 'to chained mode' request.",
+ following_ctrl_it->info.name.c_str());
+ }
}
}
else
@@ -2365,225 +2593,163 @@ controller_interface::return_type ControllerManager::check_preceeding_controller
return controller_interface::return_type::OK;
}
- if (!controller_it->c->is_in_chained_mode())
- {
- RCLCPP_DEBUG(
- get_logger(),
- "Controller with name '%s' is chainable but not in chained mode. "
- "No need to do any checks of preceding controllers when stopping it.",
- controller_it->info.name.c_str());
- return controller_interface::return_type::OK;
- }
-
RCLCPP_DEBUG(
get_logger(), "Checking preceding controller of following controller with name '%s'.",
controller_it->info.name.c_str());
- for (const auto & ref_itf_name :
- resource_manager_->get_controller_reference_interface_names(controller_it->info.name))
+ auto preceeding_controllers_list =
+ controller_chained_state_interfaces_cache_[controller_it->info.name];
+ preceeding_controllers_list.insert(
+ preceeding_controllers_list.end(),
+ controller_chained_reference_interfaces_cache_[controller_it->info.name].cbegin(),
+ controller_chained_reference_interfaces_cache_[controller_it->info.name].cend());
+
+ for (const auto & preceeding_controller : preceeding_controllers_list)
{
- std::vector preceding_controllers_using_ref_itf;
+ RCLCPP_DEBUG(get_logger(), "\t Preceding controller : '%s'.", preceeding_controller.c_str());
+ auto found_it = std::find_if(
+ controllers.begin(), controllers.end(),
+ std::bind(controller_name_compare, std::placeholders::_1, preceeding_controller));
- // TODO(destogl): This data could be cached after configuring controller into a map for faster
- // access here
- for (auto preceding_ctrl_it = controllers.begin(); preceding_ctrl_it != controllers.end();
- ++preceding_ctrl_it)
+ if (found_it != controllers.end())
{
- const auto preceding_ctrl_cmd_itfs =
- preceding_ctrl_it->c->command_interface_configuration().names;
-
- // if controller is not preceding go the next one
if (
- std::find(preceding_ctrl_cmd_itfs.begin(), preceding_ctrl_cmd_itfs.end(), ref_itf_name) ==
- preceding_ctrl_cmd_itfs.end())
- {
- continue;
- }
-
- // check if preceding controller will be activated
- if (
- is_controller_inactive(preceding_ctrl_it->c) &&
- std::find(
- activate_request_.begin(), activate_request_.end(), preceding_ctrl_it->info.name) !=
+ is_controller_inactive(found_it->c) &&
+ std::find(activate_request_.begin(), activate_request_.end(), preceeding_controller) !=
activate_request_.end())
{
RCLCPP_WARN(
get_logger(),
"Could not deactivate controller with name '%s' because "
- "preceding controller with name '%s' will be activated. ",
- controller_it->info.name.c_str(), preceding_ctrl_it->info.name.c_str());
+ "preceding controller with name '%s' is inactive and will be activated.",
+ controller_it->info.name.c_str(), preceeding_controller.c_str());
return controller_interface::return_type::ERROR;
}
- // check if preceding controller will not be deactivated
- else if (
- is_controller_active(preceding_ctrl_it->c) &&
- std::find(
- deactivate_request_.begin(), deactivate_request_.end(), preceding_ctrl_it->info.name) ==
+ if (
+ is_controller_active(found_it->c) &&
+ std::find(deactivate_request_.begin(), deactivate_request_.end(), preceeding_controller) ==
deactivate_request_.end())
{
RCLCPP_WARN(
get_logger(),
"Could not deactivate controller with name '%s' because "
"preceding controller with name '%s' is active and will not be deactivated.",
- controller_it->info.name.c_str(), preceding_ctrl_it->info.name.c_str());
+ controller_it->info.name.c_str(), preceeding_controller.c_str());
return controller_interface::return_type::ERROR;
}
- // TODO(destogl): this should be discussed how to it the best - just a placeholder for now
- // else if (
- // strictness ==
- // controller_manager_msgs::srv::SwitchController::Request::MANIPULATE_CONTROLLERS_CHAIN)
- // {
- // // insert to the begin of activate request list to be activated before preceding controller
- // activate_request_.insert(activate_request_.begin(), preceding_ctrl_name);
- // }
}
}
+
+ // TODO(destogl): this should be discussed how to it the best - just a placeholder for now
+ // else if (
+ // strictness ==
+ // controller_manager_msgs::srv::SwitchController::Request::MANIPULATE_CONTROLLERS_CHAIN)
+ // {
+ // // insert to the begin of activate request list to be activated before preceding
+ // controller
+ // activate_request_.insert(activate_request_.begin(), preceding_ctrl_name);
+ // }
+
return controller_interface::return_type::OK;
}
-bool ControllerManager::controller_sorting(
- const ControllerSpec & ctrl_a, const ControllerSpec & ctrl_b,
- const std::vector & controllers)
+void ControllerManager::controller_activity_diagnostic_callback(
+ diagnostic_updater::DiagnosticStatusWrapper & stat)
{
- // If the neither of the controllers are configured, then return false
- if (!((is_controller_active(ctrl_a.c) || is_controller_inactive(ctrl_a.c)) &&
- (is_controller_active(ctrl_b.c) || is_controller_inactive(ctrl_b.c))))
+ // lock controllers
+ std::lock_guard guard(rt_controllers_wrapper_.controllers_lock_);
+ const std::vector & controllers = rt_controllers_wrapper_.get_updated_list(guard);
+ bool all_active = true;
+ for (size_t i = 0; i < controllers.size(); ++i)
{
- if (is_controller_active(ctrl_a.c) || is_controller_inactive(ctrl_a.c))
+ if (!is_controller_active(controllers[i].c))
{
- return true;
+ all_active = false;
}
- return false;
+ stat.add(controllers[i].info.name, controllers[i].c->get_state().label());
}
- const std::vector cmd_itfs = ctrl_a.c->command_interface_configuration().names;
- const std::vector state_itfs = ctrl_a.c->state_interface_configuration().names;
- if (cmd_itfs.empty() || !ctrl_a.c->is_chainable())
- {
- // The case of the controllers that don't have any command interfaces. For instance,
- // joint_state_broadcaster
- // If the controller b is also under the same condition, then maintain their initial order
- const auto command_interfaces_exist =
- !ctrl_b.c->command_interface_configuration().names.empty();
- return ctrl_b.c->is_chainable() && command_interfaces_exist;
- }
- else if (ctrl_b.c->command_interface_configuration().names.empty() || !ctrl_b.c->is_chainable())
+ if (all_active)
{
- // If only the controller b is a broadcaster or non chainable type , then swap the controllers
- return false;
+ stat.summary(diagnostic_msgs::msg::DiagnosticStatus::OK, "All controllers are active");
}
else
{
- auto following_ctrls = get_following_controller_names(ctrl_a.info.name, controllers);
- if (following_ctrls.empty())
- {
- return false;
- }
- // If the ctrl_b is any of the following controllers of ctrl_a, then place ctrl_a before ctrl_b
- if (
- std::find(following_ctrls.begin(), following_ctrls.end(), ctrl_b.info.name) !=
- following_ctrls.end())
- {
- return true;
- }
- else
- {
- auto ctrl_a_preceding_ctrls = get_preceding_controller_names(ctrl_a.info.name, controllers);
- // This is to check that the ctrl_b is in the preceding controllers list of ctrl_a - This
- // check is useful when there is a chained controller branching, but they belong to same
- // branch
- if (
- std::find(ctrl_a_preceding_ctrls.begin(), ctrl_a_preceding_ctrls.end(), ctrl_b.info.name) !=
- ctrl_a_preceding_ctrls.end())
- {
- return false;
- }
+ stat.summary(diagnostic_msgs::msg::DiagnosticStatus::OK, "Not all controllers are active");
+ }
+}
- // This is to handle the cases where, the parsed ctrl_a and ctrl_b are not directly related
- // but might have a common parent - happens in branched chained controller
- auto ctrl_b_preceding_ctrls = get_preceding_controller_names(ctrl_b.info.name, controllers);
- std::sort(ctrl_a_preceding_ctrls.begin(), ctrl_a_preceding_ctrls.end());
- std::sort(ctrl_b_preceding_ctrls.begin(), ctrl_b_preceding_ctrls.end());
- std::list intersection;
- std::set_intersection(
- ctrl_a_preceding_ctrls.begin(), ctrl_a_preceding_ctrls.end(),
- ctrl_b_preceding_ctrls.begin(), ctrl_b_preceding_ctrls.end(),
- std::back_inserter(intersection));
- if (!intersection.empty())
- {
- // If there is an intersection, then there is a common parent controller for both ctrl_a and
- // ctrl_b
- return true;
- }
+void ControllerManager::update_list_with_controller_chain(
+ const std::string & ctrl_name, std::vector::iterator controller_iterator,
+ bool append_to_controller)
+{
+ auto new_ctrl_it =
+ std::find(ordered_controllers_names_.begin(), ordered_controllers_names_.end(), ctrl_name);
+ if (new_ctrl_it == ordered_controllers_names_.end())
+ {
+ RCLCPP_DEBUG(get_logger(), "Adding controller chain : %s", ctrl_name.c_str());
- // If there is no common parent, then they belong to 2 different sets
- auto following_ctrls_b = get_following_controller_names(ctrl_b.info.name, controllers);
- if (following_ctrls_b.empty())
+ auto iterator = controller_iterator;
+ for (const auto & ctrl : controller_chain_spec_[ctrl_name].following_controllers)
+ {
+ auto it =
+ std::find(ordered_controllers_names_.begin(), ordered_controllers_names_.end(), ctrl);
+ if (it != ordered_controllers_names_.end())
{
- return true;
+ if (
+ std::distance(ordered_controllers_names_.begin(), it) <
+ std::distance(ordered_controllers_names_.begin(), iterator))
+ {
+ iterator = it;
+ }
}
- auto find_first_element = [&](const auto & controllers_list) -> size_t
+ }
+ for (const auto & ctrl : controller_chain_spec_[ctrl_name].preceding_controllers)
+ {
+ auto it =
+ std::find(ordered_controllers_names_.begin(), ordered_controllers_names_.end(), ctrl);
+ if (it != ordered_controllers_names_.end())
{
- auto it = std::find_if(
- controllers.begin(), controllers.end(),
- std::bind(controller_name_compare, std::placeholders::_1, controllers_list.back()));
- if (it != controllers.end())
+ if (
+ std::distance(ordered_controllers_names_.begin(), it) >
+ std::distance(ordered_controllers_names_.begin(), iterator))
{
- return std::distance(controllers.begin(), it);
+ iterator = it;
}
- return 0;
- };
- const auto ctrl_a_chain_first_controller = find_first_element(following_ctrls);
- const auto ctrl_b_chain_first_controller = find_first_element(following_ctrls_b);
- if (ctrl_a_chain_first_controller < ctrl_b_chain_first_controller)
- {
- return true;
}
}
- // If the ctrl_a's state interface is the one exported by the ctrl_b then ctrl_b should be
- // infront of ctrl_a
- // TODO(saikishor): deal with the state interface chaining in the sorting algorithm
- auto state_it = std::find_if(
- state_itfs.begin(), state_itfs.end(),
- [ctrl_b](auto itf)
- {
- auto index = itf.find_first_of('/');
- return ((index != std::string::npos) && (itf.substr(0, index) == ctrl_b.info.name));
- });
- if (state_it != state_itfs.end())
+ if (append_to_controller)
{
- return false;
+ ordered_controllers_names_.insert(iterator + 1, ctrl_name);
}
-
- // The rest of the cases, basically end up at the end of the list
- return false;
- }
-};
-
-void ControllerManager::controller_activity_diagnostic_callback(
- diagnostic_updater::DiagnosticStatusWrapper & stat)
-{
- // lock controllers
- std::lock_guard guard(rt_controllers_wrapper_.controllers_lock_);
- const std::vector & controllers = rt_controllers_wrapper_.get_updated_list(guard);
- bool all_active = true;
- for (size_t i = 0; i < controllers.size(); ++i)
- {
- if (!is_controller_active(controllers[i].c))
+ else
{
- all_active = false;
+ ordered_controllers_names_.insert(iterator, ctrl_name);
}
- stat.add(controllers[i].info.name, controllers[i].c->get_state().label());
- }
- if (all_active)
- {
- stat.summary(diagnostic_msgs::msg::DiagnosticStatus::OK, "All controllers are active");
- }
- else
- {
- stat.summary(diagnostic_msgs::msg::DiagnosticStatus::OK, "Not all controllers are active");
+ RCLCPP_DEBUG_EXPRESSION(
+ get_logger(), !controller_chain_spec_[ctrl_name].following_controllers.empty(),
+ "\t[%s] Following controllers : %ld", ctrl_name.c_str(),
+ controller_chain_spec_[ctrl_name].following_controllers.size());
+ for (const std::string & flwg_ctrl : controller_chain_spec_[ctrl_name].following_controllers)
+ {
+ new_ctrl_it =
+ std::find(ordered_controllers_names_.begin(), ordered_controllers_names_.end(), ctrl_name);
+ RCLCPP_DEBUG(get_logger(), "\t\t[%s] : %s", ctrl_name.c_str(), flwg_ctrl.c_str());
+ update_list_with_controller_chain(flwg_ctrl, new_ctrl_it, true);
+ }
+ RCLCPP_DEBUG_EXPRESSION(
+ get_logger(), !controller_chain_spec_[ctrl_name].preceding_controllers.empty(),
+ "\t[%s] Preceding controllers : %ld", ctrl_name.c_str(),
+ controller_chain_spec_[ctrl_name].preceding_controllers.size());
+ for (const std::string & preced_ctrl : controller_chain_spec_[ctrl_name].preceding_controllers)
+ {
+ new_ctrl_it =
+ std::find(ordered_controllers_names_.begin(), ordered_controllers_names_.end(), ctrl_name);
+ RCLCPP_DEBUG(get_logger(), "\t\t[%s]: %s", ctrl_name.c_str(), preced_ctrl.c_str());
+ update_list_with_controller_chain(preced_ctrl, new_ctrl_it, false);
+ }
}
}
diff --git a/controller_manager/src/ros2_control_node.cpp b/controller_manager/src/ros2_control_node.cpp
index 2747e79a1b..6dd7d72fb2 100644
--- a/controller_manager/src/ros2_control_node.cpp
+++ b/controller_manager/src/ros2_control_node.cpp
@@ -83,6 +83,8 @@ int main(int argc, char ** argv)
next_iteration_time += period;
std::this_thread::sleep_until(next_iteration_time);
}
+
+ cm->shutdown_async_controllers_and_components();
});
executor->add_node(cm);
diff --git a/controller_manager/test/controller_manager_test_common.hpp b/controller_manager/test/controller_manager_test_common.hpp
index 8b6608bbb3..8b6bd91376 100644
--- a/controller_manager/test/controller_manager_test_common.hpp
+++ b/controller_manager/test/controller_manager_test_common.hpp
@@ -70,7 +70,9 @@ class ControllerManagerFixture : public ::testing::Test
{
executor_ = std::make_shared();
cm_ = std::make_shared(
- std::make_unique(), executor_, TEST_CM_NAME);
+ std::make_unique(
+ rm_node_->get_node_clock_interface(), rm_node_->get_node_logging_interface()),
+ executor_, TEST_CM_NAME);
// We want to be able to not pass robot description immediately
if (!robot_description_.empty())
{
@@ -144,6 +146,9 @@ class ControllerManagerFixture : public ::testing::Test
bool run_updater_;
const std::string robot_description_;
rclcpp::Time time_;
+
+protected:
+ rclcpp::Node::SharedPtr rm_node_ = std::make_shared("ResourceManager");
};
class TestControllerManagerSrvs
diff --git a/controller_manager/test/test_chainable_controller/test_chainable_controller.cpp b/controller_manager/test/test_chainable_controller/test_chainable_controller.cpp
index d21957a0b4..e43f2a13a1 100644
--- a/controller_manager/test/test_chainable_controller/test_chainable_controller.cpp
+++ b/controller_manager/test/test_chainable_controller/test_chainable_controller.cpp
@@ -52,6 +52,13 @@ TestChainableController::state_interface_configuration() const
get_state().id() == lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE ||
get_state().id() == lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE)
{
+ auto state_iface_cfg = state_iface_cfg_;
+ if (imu_sensor_)
+ {
+ auto imu_interfaces = imu_sensor_->get_state_interface_names();
+ state_iface_cfg.names.insert(
+ state_iface_cfg.names.end(), imu_interfaces.begin(), imu_interfaces.end());
+ }
return state_iface_cfg_;
}
else
@@ -96,6 +103,20 @@ controller_interface::return_type TestChainableController::update_and_write_comm
{
command_interfaces_[i].set_value(reference_interfaces_[i] - state_interfaces_[i].get_value());
}
+ // If there is a command interface then integrate and set it to the exported state interface data
+ for (size_t i = 0; i < exported_state_interface_names_.size() && i < command_interfaces_.size();
+ ++i)
+ {
+ state_interfaces_values_[i] = command_interfaces_[i].get_value() * CONTROLLER_DT;
+ }
+ // If there is no command interface and if there is a state interface then just forward the same
+ // value as in the state interface
+ for (size_t i = 0; i < exported_state_interface_names_.size() && i < state_interfaces_.size() &&
+ command_interfaces_.empty();
+ ++i)
+ {
+ state_interfaces_values_[i] = state_interfaces_[i].get_value();
+ }
return controller_interface::return_type::OK;
}
@@ -150,6 +171,20 @@ CallbackReturn TestChainableController::on_cleanup(
return CallbackReturn::SUCCESS;
}
+std::vector
+TestChainableController::on_export_state_interfaces()
+{
+ std::vector state_interfaces;
+
+ for (size_t i = 0; i < exported_state_interface_names_.size(); ++i)
+ {
+ state_interfaces.push_back(hardware_interface::StateInterface(
+ get_node()->get_name(), exported_state_interface_names_[i], &state_interfaces_values_[i]));
+ }
+
+ return state_interfaces;
+}
+
std::vector
TestChainableController::on_export_reference_interfaces()
{
@@ -184,6 +219,31 @@ void TestChainableController::set_reference_interface_names(
reference_interfaces_.resize(reference_interface_names.size(), 0.0);
}
+void TestChainableController::set_exported_state_interface_names(
+ const std::vector & state_interface_names)
+{
+ exported_state_interface_names_ = state_interface_names;
+
+ state_interfaces_values_.resize(exported_state_interface_names_.size(), 0.0);
+}
+
+void TestChainableController::set_imu_sensor_name(const std::string & name)
+{
+ if (!name.empty())
+ {
+ imu_sensor_ = std::make_unique(name);
+ }
+}
+
+std::vector TestChainableController::get_state_interface_data() const
+{
+ std::vector state_intr_data;
+ for (const auto & interface : state_interfaces_)
+ {
+ state_intr_data.push_back(interface.get_value());
+ }
+ return state_intr_data;
+}
} // namespace test_chainable_controller
#include "pluginlib/class_list_macros.hpp"
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 5925ed8d11..f4f59ad9df 100644
--- a/controller_manager/test/test_chainable_controller/test_chainable_controller.hpp
+++ b/controller_manager/test/test_chainable_controller/test_chainable_controller.hpp
@@ -23,6 +23,7 @@
#include "controller_manager/visibility_control.h"
#include "rclcpp/subscription.hpp"
#include "realtime_tools/realtime_buffer.h"
+#include "semantic_components/imu_sensor.hpp"
#include "std_msgs/msg/float64_multi_array.hpp"
namespace test_chainable_controller
@@ -35,6 +36,7 @@ using CallbackReturn = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface
constexpr char TEST_CONTROLLER_NAME[] = "test_chainable_controller_name";
// corresponds to the name listed within the pluginlib xml
constexpr char TEST_CONTROLLER_CLASS_NAME[] = "controller_manager/test_chainable_controller";
+constexpr double CONTROLLER_DT = 0.001;
class TestChainableController : public controller_interface::ChainableControllerInterface
{
public:
@@ -60,6 +62,9 @@ class TestChainableController : public controller_interface::ChainableController
CONTROLLER_MANAGER_PUBLIC
CallbackReturn on_cleanup(const rclcpp_lifecycle::State & previous_state) override;
+ CONTROLLER_MANAGER_PUBLIC
+ std::vector on_export_state_interfaces() override;
+
CONTROLLER_MANAGER_PUBLIC
std::vector on_export_reference_interfaces() override;
@@ -80,10 +85,21 @@ class TestChainableController : public controller_interface::ChainableController
CONTROLLER_MANAGER_PUBLIC
void set_reference_interface_names(const std::vector & reference_interface_names);
+ CONTROLLER_MANAGER_PUBLIC
+ void set_exported_state_interface_names(const std::vector & state_interface_names);
+
+ CONTROLLER_MANAGER_PUBLIC
+ void set_imu_sensor_name(const std::string & name);
+
+ CONTROLLER_MANAGER_PUBLIC
+ std::vector get_state_interface_data() const;
+
size_t internal_counter;
controller_interface::InterfaceConfiguration cmd_iface_cfg_;
controller_interface::InterfaceConfiguration state_iface_cfg_;
std::vector reference_interface_names_;
+ std::vector exported_state_interface_names_;
+ std::unique_ptr imu_sensor_;
realtime_tools::RealtimeBuffer> rt_command_ptr_;
rclcpp::Subscription::SharedPtr joints_command_subscriber_;
diff --git a/controller_manager/test/test_controller/test_controller.cpp b/controller_manager/test/test_controller/test_controller.cpp
index 7585ae36e5..625d7ed90f 100644
--- a/controller_manager/test/test_controller/test_controller.cpp
+++ b/controller_manager/test/test_controller/test_controller.cpp
@@ -62,7 +62,7 @@ controller_interface::InterfaceConfiguration TestController::state_interface_con
controller_interface::return_type TestController::update(
const rclcpp::Time & /*time*/, const rclcpp::Duration & period)
{
- update_period_ = period.seconds();
+ update_period_ = period;
++internal_counter;
// set value to hardware to produce and test different behaviors there
@@ -76,6 +76,14 @@ controller_interface::return_type TestController::update(
{
for (size_t i = 0; i < command_interfaces_.size(); ++i)
{
+ if (!std::isfinite(external_commands_for_testing_[i]))
+ {
+ RCLCPP_ERROR(
+ get_node()->get_logger(),
+ "External command value for command interface '%s' is not finite",
+ command_interfaces_[i].get_name().c_str());
+ return controller_interface::return_type::ERROR;
+ }
RCLCPP_INFO(
get_node()->get_logger(), "Setting value of command interface '%s' to %f",
command_interfaces_[i].get_name().c_str(), external_commands_for_testing_[i]);
@@ -120,6 +128,16 @@ void TestController::set_state_interface_configuration(
state_iface_cfg_ = cfg;
}
+std::vector TestController::get_state_interface_data() const
+{
+ std::vector state_intr_data;
+ for (const auto & interface : state_interfaces_)
+ {
+ state_intr_data.push_back(interface.get_value());
+ }
+ return state_intr_data;
+}
+
} // namespace test_controller
#include "pluginlib/class_list_macros.hpp"
diff --git a/controller_manager/test/test_controller/test_controller.hpp b/controller_manager/test/test_controller/test_controller.hpp
index 14ad753803..bf183c7bad 100644
--- a/controller_manager/test/test_controller/test_controller.hpp
+++ b/controller_manager/test/test_controller/test_controller.hpp
@@ -66,6 +66,9 @@ class TestController : public controller_interface::ControllerInterface
CONTROLLER_MANAGER_PUBLIC
void set_state_interface_configuration(const controller_interface::InterfaceConfiguration & cfg);
+ CONTROLLER_MANAGER_PUBLIC
+ std::vector get_state_interface_data() const;
+
const std::string & getRobotDescription() const;
unsigned int internal_counter = 0;
@@ -80,7 +83,7 @@ class TestController : public controller_interface::ControllerInterface
// enables external setting of values to command interfaces - used for simulation of hardware
// errors
double set_first_command_interface_value_to;
- double update_period_ = 0;
+ rclcpp::Duration update_period_ = rclcpp::Duration::from_seconds(0.);
};
} // namespace test_controller
diff --git a/controller_manager/test/test_controller_manager.cpp b/controller_manager/test/test_controller_manager.cpp
index e88b41f222..3e2f91e91a 100644
--- a/controller_manager/test/test_controller_manager.cpp
+++ b/controller_manager/test/test_controller_manager.cpp
@@ -374,9 +374,12 @@ TEST_P(TestControllerManagerWithUpdateRates, per_controller_equal_and_higher_upd
controller_interface::return_type::OK,
cm_->update(time_, rclcpp::Duration::from_seconds(0.01)));
// In case of a non perfect divisor, the update period should respect the rule
- // [controller_update_rate, 2*controller_update_rate)
- ASSERT_GE(test_controller->update_period_, 1.0 / cm_update_rate);
- ASSERT_LT(test_controller->update_period_, 2.0 / cm_update_rate);
+ // [cm_update_rate, 2*cm_update_rate)
+ EXPECT_THAT(
+ test_controller->update_period_,
+ testing::AllOf(
+ testing::Ge(rclcpp::Duration::from_seconds(1.0 / cm_update_rate)),
+ testing::Lt(rclcpp::Duration::from_seconds(2.0 / cm_update_rate))));
loop_rate.sleep();
}
// if we do 2 times of the controller_manager update rate, the internal counter should be
@@ -445,6 +448,7 @@ TEST_P(TestControllerUpdateRates, check_the_controller_update_rate)
EXPECT_EQ(lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE, test_controller->get_state().id());
// Start controller, will take effect at the end of the update function
+ time_ = test_controller->get_node()->now(); // set to something nonzero
const auto strictness = controller_manager_msgs::srv::SwitchController::Request::STRICT;
std::vector start_controllers = {test_controller::TEST_CONTROLLER_NAME};
std::vector stop_controllers = {};
@@ -472,6 +476,7 @@ TEST_P(TestControllerUpdateRates, check_the_controller_update_rate)
const auto controller_update_rate = test_controller->get_update_rate();
const auto initial_counter = test_controller->internal_counter;
+ // don't start with zero to check if the period is correct if controller is activated anytime
rclcpp::Time time = time_;
for (size_t update_counter = 0; update_counter <= 10 * cm_update_rate; ++update_counter)
{
@@ -480,8 +485,12 @@ TEST_P(TestControllerUpdateRates, check_the_controller_update_rate)
cm_->update(time, rclcpp::Duration::from_seconds(0.01)));
// In case of a non perfect divisor, the update period should respect the rule
// [controller_update_rate, 2*controller_update_rate)
- ASSERT_GE(test_controller->update_period_, 1.0 / controller_update_rate);
- ASSERT_LT(test_controller->update_period_, 2.0 / controller_update_rate);
+ EXPECT_THAT(
+ test_controller->update_period_,
+ testing::AllOf(
+ testing::Ge(rclcpp::Duration::from_seconds(1.0 / controller_update_rate)),
+ testing::Lt(rclcpp::Duration::from_seconds(2.0 / controller_update_rate))))
+ << "update_counter: " << update_counter;
time += rclcpp::Duration::from_seconds(0.01);
if (update_counter % cm_update_rate == 0)
diff --git a/controller_manager/test/test_controller_manager_hardware_error_handling.cpp b/controller_manager/test/test_controller_manager_hardware_error_handling.cpp
index 4c800d41c2..6e2fba23db 100644
--- a/controller_manager/test/test_controller_manager_hardware_error_handling.cpp
+++ b/controller_manager/test/test_controller_manager_hardware_error_handling.cpp
@@ -405,6 +405,106 @@ TEST_P(TestControllerManagerWithTestableCM, stop_controllers_on_hardware_read_er
}
}
+TEST_P(TestControllerManagerWithTestableCM, stop_controllers_on_controller_error)
+{
+ auto strictness = GetParam().strictness;
+ SetupAndConfigureControllers(strictness);
+
+ rclcpp_lifecycle::State state_active(
+ lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE,
+ hardware_interface::lifecycle_state_names::ACTIVE);
+
+ {
+ EXPECT_EQ(controller_interface::return_type::OK, cm_->update(time_, PERIOD));
+ EXPECT_GE(test_controller_actuator->internal_counter, 1u)
+ << "Controller is started at the end of update";
+ EXPECT_GE(test_controller_system->internal_counter, 1u)
+ << "Controller is started at the end of update";
+ EXPECT_GE(test_broadcaster_all->internal_counter, 1u)
+ << "Controller is started at the end of update";
+ EXPECT_GE(test_broadcaster_sensor->internal_counter, 1u)
+ << "Controller is started at the end of update";
+ }
+
+ EXPECT_EQ(
+ lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE, test_controller_actuator->get_state().id());
+ EXPECT_EQ(
+ lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE, test_controller_system->get_state().id());
+ EXPECT_EQ(
+ lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE, test_broadcaster_all->get_state().id());
+ EXPECT_EQ(
+ lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE, test_broadcaster_sensor->get_state().id());
+
+ // Execute first time without any errors
+ {
+ auto new_counter = test_controller_actuator->internal_counter + 1;
+ EXPECT_EQ(controller_interface::return_type::OK, cm_->update(time_, PERIOD));
+ EXPECT_EQ(test_controller_actuator->internal_counter, new_counter) << "Execute without errors";
+ EXPECT_EQ(test_controller_system->internal_counter, new_counter) << "Execute without errors";
+ EXPECT_EQ(test_broadcaster_all->internal_counter, new_counter) << "Execute without errors";
+ EXPECT_EQ(test_broadcaster_sensor->internal_counter, new_counter) << "Execute without errors";
+ }
+
+ // Simulate error in update method of the controllers but not in hardware
+ test_controller_actuator->external_commands_for_testing_[0] =
+ std::numeric_limits::quiet_NaN();
+ test_controller_system->external_commands_for_testing_[0] =
+ std::numeric_limits::quiet_NaN();
+ {
+ auto new_counter = test_controller_actuator->internal_counter + 1;
+ EXPECT_EQ(controller_interface::return_type::ERROR, cm_->update(time_, PERIOD));
+ EXPECT_EQ(test_controller_actuator->internal_counter, new_counter)
+ << "Executes the current cycle and returns ERROR";
+ EXPECT_EQ(
+ test_controller_actuator->get_state().id(),
+ lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE);
+ EXPECT_EQ(test_controller_system->internal_counter, new_counter)
+ << "Executes the current cycle and returns ERROR";
+ EXPECT_EQ(
+ test_controller_system->get_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE);
+ EXPECT_EQ(test_broadcaster_all->internal_counter, new_counter)
+ << "Execute without errors to write value";
+ EXPECT_EQ(test_broadcaster_sensor->internal_counter, new_counter)
+ << "Execute without errors to write value";
+ }
+
+ {
+ auto previous_counter = test_controller_actuator->internal_counter;
+ auto new_counter = test_controller_system->internal_counter + 1;
+
+ EXPECT_EQ(
+ lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE,
+ test_controller_actuator->get_state().id());
+ EXPECT_EQ(
+ lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE, test_controller_system->get_state().id());
+ EXPECT_EQ(
+ lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE, test_broadcaster_all->get_state().id());
+ EXPECT_EQ(
+ lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE, test_broadcaster_sensor->get_state().id());
+
+ EXPECT_EQ(controller_interface::return_type::OK, cm_->update(time_, PERIOD));
+ EXPECT_EQ(test_controller_actuator->internal_counter, previous_counter)
+ << "Cannot execute as it should be currently deactivated";
+ EXPECT_EQ(test_controller_system->internal_counter, previous_counter)
+ << "Cannot execute as it should be currently deactivated";
+ EXPECT_EQ(test_broadcaster_all->internal_counter, new_counter)
+ << "Broadcaster all interfaces without errors";
+ EXPECT_EQ(test_broadcaster_sensor->internal_counter, new_counter)
+ << "Execute without errors to write value";
+
+ // The states shouldn't change as there are no more controller errors
+ EXPECT_EQ(
+ lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE,
+ test_controller_actuator->get_state().id());
+ EXPECT_EQ(
+ lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE, test_controller_system->get_state().id());
+ EXPECT_EQ(
+ lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE, test_broadcaster_all->get_state().id());
+ EXPECT_EQ(
+ lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE, test_broadcaster_sensor->get_state().id());
+ }
+}
+
TEST_P(TestControllerManagerWithTestableCM, stop_controllers_on_hardware_write_error)
{
auto strictness = GetParam().strictness;
diff --git a/controller_manager/test/test_controller_manager_srvs.cpp b/controller_manager/test/test_controller_manager_srvs.cpp
index 2cd5645bd8..4fdfe3fb9f 100644
--- a/controller_manager/test/test_controller_manager_srvs.cpp
+++ b/controller_manager/test/test_controller_manager_srvs.cpp
@@ -15,6 +15,7 @@
#include
#include
#include
+#include
#include
#include
@@ -456,6 +457,8 @@ TEST_F(TestControllerManagerSrvs, unload_controller_srv)
result = call_service_and_wait(*client, request, srv_executor, true);
ASSERT_TRUE(result->ok);
+ EXPECT_EQ(
+ lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED, test_controller->get_state().id());
EXPECT_EQ(0u, cm_->get_loaded_controllers().size());
}
@@ -488,6 +491,8 @@ TEST_F(TestControllerManagerSrvs, robot_description_on_load_and_unload_controlle
auto unload_request = std::make_shared();
unload_request->name = test_controller::TEST_CONTROLLER_NAME;
auto result = call_service_and_wait(*unload_client, unload_request, srv_executor, true);
+ EXPECT_EQ(
+ lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED, test_controller->get_state().id());
EXPECT_EQ(0u, cm_->get_loaded_controllers().size());
// now load it and check if it got the new robot description
@@ -508,6 +513,9 @@ TEST_F(TestControllerManagerSrvs, configure_controller_srv)
rclcpp::Client::SharedPtr client =
srv_node->create_client(
"test_controller_manager/configure_controller");
+ rclcpp::Client::SharedPtr unload_client =
+ srv_node->create_client(
+ "test_controller_manager/unload_controller");
auto request = std::make_shared();
request->name = test_controller::TEST_CONTROLLER_NAME;
@@ -526,6 +534,15 @@ TEST_F(TestControllerManagerSrvs, configure_controller_srv)
EXPECT_EQ(
lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE,
cm_->get_loaded_controllers()[0].c->get_state().id());
+ EXPECT_EQ(lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE, test_controller->get_state().id());
+
+ // now unload the controller and check the state
+ auto unload_request = std::make_shared();
+ unload_request->name = test_controller::TEST_CONTROLLER_NAME;
+ ASSERT_TRUE(call_service_and_wait(*unload_client, unload_request, srv_executor, true)->ok);
+ EXPECT_EQ(
+ lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED, test_controller->get_state().id());
+ EXPECT_EQ(0u, cm_->get_loaded_controllers().size());
}
TEST_F(TestControllerManagerSrvs, list_sorted_chained_controllers)
@@ -816,8 +833,7 @@ TEST_F(TestControllerManagerSrvs, list_sorted_complex_chained_controllers)
auto get_ctrl_pos = [result](const std::string & controller_name) -> int64_t
{
auto it = std::find_if(
- result->controller.begin(), result->controller.end(),
- [controller_name](auto itf)
+ result->controller.begin(), result->controller.end(), [controller_name](auto itf)
{ return (itf.name.find(controller_name) != std::string::npos); });
return std::distance(result->controller.begin(), it);
};
@@ -849,7 +865,7 @@ TEST_F(TestControllerManagerSrvs, list_sorted_independent_chained_controllers)
/// &&
/// test_controller_name_2 -> chain_ctrl_6 -> chain_ctrl_5 -> chain_ctrl_4
/// &&
- /// test_controller_name_7 -> test_controller_name_8
+ /// test_controller_name_8 -> test_controller_name_7
///
/// NOTE: A -> B signifies that the controller A is utilizing the reference interfaces exported
/// from the controller B (or) the controller B is utilizing the expected interfaces exported from
@@ -964,34 +980,37 @@ TEST_F(TestControllerManagerSrvs, list_sorted_independent_chained_controllers)
// add controllers
/// @todo add controllers in random order
/// For now, adding the ordered case to see that current sorting doesn't change order
- cm_->add_controller(
- test_chained_controller_2, TEST_CHAINED_CONTROLLER_2,
- test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
- cm_->add_controller(
- test_chained_controller_6, TEST_CHAINED_CONTROLLER_6,
- test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
- cm_->add_controller(
- test_chained_controller_1, TEST_CHAINED_CONTROLLER_1,
- test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
- cm_->add_controller(
- test_chained_controller_7, TEST_CHAINED_CONTROLLER_7,
- test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
- cm_->add_controller(
- test_controller_1, TEST_CONTROLLER_1, test_controller::TEST_CONTROLLER_CLASS_NAME);
- cm_->add_controller(
- test_chained_controller_5, TEST_CHAINED_CONTROLLER_5,
- test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
- cm_->add_controller(
- test_chained_controller_3, TEST_CHAINED_CONTROLLER_3,
- test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
- cm_->add_controller(
- test_chained_controller_4, TEST_CHAINED_CONTROLLER_4,
- test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
- cm_->add_controller(
- test_controller_2, TEST_CONTROLLER_2, test_controller::TEST_CONTROLLER_CLASS_NAME);
- cm_->add_controller(
- test_chained_controller_8, TEST_CHAINED_CONTROLLER_8,
- test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ {
+ ControllerManagerRunner cm_runner(this);
+ cm_->add_controller(
+ test_chained_controller_2, TEST_CHAINED_CONTROLLER_2,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_chained_controller_6, TEST_CHAINED_CONTROLLER_6,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_chained_controller_1, TEST_CHAINED_CONTROLLER_1,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_chained_controller_7, TEST_CHAINED_CONTROLLER_7,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_controller_1, TEST_CONTROLLER_1, test_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_chained_controller_5, TEST_CHAINED_CONTROLLER_5,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_chained_controller_3, TEST_CHAINED_CONTROLLER_3,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_chained_controller_4, TEST_CHAINED_CONTROLLER_4,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_controller_2, TEST_CONTROLLER_2, test_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_chained_controller_8, TEST_CHAINED_CONTROLLER_8,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ }
// get controller list before configure
auto result = call_service_and_wait(*client, request, srv_executor);
@@ -1028,8 +1047,7 @@ TEST_F(TestControllerManagerSrvs, list_sorted_independent_chained_controllers)
auto get_ctrl_pos = [result](const std::string & controller_name) -> int64_t
{
auto it = std::find_if(
- result->controller.begin(), result->controller.end(),
- [controller_name](auto itf)
+ result->controller.begin(), result->controller.end(), [controller_name](auto itf)
{ return (itf.name.find(controller_name) != std::string::npos); });
return std::distance(result->controller.begin(), it);
};
@@ -1218,40 +1236,40 @@ TEST_F(TestControllerManagerSrvs, list_large_number_of_controllers_with_chains)
// add controllers
/// @todo add controllers in random order
/// For now, adding the ordered case to see that current sorting doesn't change order
- cm_->add_controller(
- test_chained_controller_2, TEST_CHAINED_CONTROLLER_2,
- test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
- cm_->add_controller(
- test_chained_controller_6, TEST_CHAINED_CONTROLLER_6,
- test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
- cm_->add_controller(
- test_chained_controller_1, TEST_CHAINED_CONTROLLER_1,
- test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
- cm_->add_controller(
- test_chained_controller_7, TEST_CHAINED_CONTROLLER_7,
- test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
- cm_->add_controller(
- test_controller_1, TEST_CONTROLLER_1, test_controller::TEST_CONTROLLER_CLASS_NAME);
- cm_->add_controller(
- test_chained_controller_5, TEST_CHAINED_CONTROLLER_5,
- test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
- cm_->add_controller(
- test_chained_controller_3, TEST_CHAINED_CONTROLLER_3,
- test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
- cm_->add_controller(
- test_chained_controller_4, TEST_CHAINED_CONTROLLER_4,
- test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
- cm_->add_controller(
- test_controller_2, TEST_CONTROLLER_2, test_controller::TEST_CONTROLLER_CLASS_NAME);
- cm_->add_controller(
- test_chained_controller_8, TEST_CHAINED_CONTROLLER_8,
- test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
- cm_->add_controller(
- test_chained_controller_9, TEST_CHAINED_CONTROLLER_9,
- test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
-
{
ControllerManagerRunner cm_runner(this);
+ cm_->add_controller(
+ test_chained_controller_2, TEST_CHAINED_CONTROLLER_2,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_chained_controller_6, TEST_CHAINED_CONTROLLER_6,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_chained_controller_1, TEST_CHAINED_CONTROLLER_1,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_chained_controller_7, TEST_CHAINED_CONTROLLER_7,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_controller_1, TEST_CONTROLLER_1, test_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_chained_controller_5, TEST_CHAINED_CONTROLLER_5,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_chained_controller_3, TEST_CHAINED_CONTROLLER_3,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_chained_controller_4, TEST_CHAINED_CONTROLLER_4,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_controller_2, TEST_CONTROLLER_2, test_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_chained_controller_8, TEST_CHAINED_CONTROLLER_8,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_chained_controller_9, TEST_CHAINED_CONTROLLER_9,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+
for (auto random_ctrl : random_controllers_list)
{
cm_->add_controller(
@@ -1305,8 +1323,7 @@ TEST_F(TestControllerManagerSrvs, list_large_number_of_controllers_with_chains)
auto get_ctrl_pos = [result](const std::string & controller_name) -> int64_t
{
auto it = std::find_if(
- result->controller.begin(), result->controller.end(),
- [controller_name](auto itf)
+ result->controller.begin(), result->controller.end(), [controller_name](auto itf)
{ return (itf.name.find(controller_name) != std::string::npos); });
return std::distance(result->controller.begin(), it);
};
@@ -1490,7 +1507,9 @@ TEST_F(TestControllerManagerSrvs, list_sorted_large_chained_controller_tree)
}
// Now shuffle the list to be able to configure controller later randomly
- std::random_shuffle(controllers_list.begin(), controllers_list.end());
+ std::random_device rnd;
+ std::mt19937 mers(rnd());
+ std::shuffle(controllers_list.begin(), controllers_list.end(), mers);
{
ControllerManagerRunner cm_runner(this);
@@ -1530,8 +1549,7 @@ TEST_F(TestControllerManagerSrvs, list_sorted_large_chained_controller_tree)
auto get_ctrl_pos = [result](const std::string & controller_name) -> int64_t
{
auto it = std::find_if(
- result->controller.begin(), result->controller.end(),
- [controller_name](auto itf)
+ result->controller.begin(), result->controller.end(), [controller_name](auto itf)
{ return (itf.name.find(controller_name) != std::string::npos); });
return std::distance(result->controller.begin(), it);
};
@@ -1713,3 +1731,200 @@ TEST_F(TestControllerManagerSrvs, list_hardware_interfaces_srv)
}
}
}
+
+TEST_F(TestControllerManagerSrvs, activate_chained_controllers_one_by_one)
+{
+ /// The simulated controller chaining is:
+ /// test_controller_name -> chain_ctrl_2 -> chain_ctrl_1
+ ///
+ /// NOTE: A -> B signifies that the controller A is utilizing the reference interfaces exported
+ /// from the controller B (or) the controller B is utilizing the expected interfaces exported from
+ /// the controller A
+
+ // create server client and request
+ rclcpp::executors::SingleThreadedExecutor srv_executor;
+ rclcpp::Node::SharedPtr srv_node = std::make_shared("srv_client");
+ srv_executor.add_node(srv_node);
+ rclcpp::Client::SharedPtr client =
+ srv_node->create_client("test_controller_manager/list_controllers");
+ auto request = std::make_shared();
+
+ // create set of chained controllers
+ static constexpr char TEST_CHAINED_CONTROLLER_1[] = "test_chainable_controller_name_1";
+ static constexpr char TEST_CHAINED_CONTROLLER_2[] = "test_chainable_controller_name_2";
+ auto test_chained_controller_1 = std::make_shared();
+ controller_interface::InterfaceConfiguration chained_cmd_cfg = {
+ controller_interface::interface_configuration_type::INDIVIDUAL, {"joint1/position"}};
+ controller_interface::InterfaceConfiguration chained_state_cfg = {
+ controller_interface::interface_configuration_type::INDIVIDUAL,
+ {"joint1/position", "joint1/velocity"}};
+ test_chained_controller_1->set_command_interface_configuration(chained_cmd_cfg);
+ test_chained_controller_1->set_state_interface_configuration(chained_state_cfg);
+ test_chained_controller_1->set_reference_interface_names({"joint1/position", "joint1/velocity"});
+
+ auto test_chained_controller_2 = std::make_shared();
+ chained_cmd_cfg = {
+ controller_interface::interface_configuration_type::INDIVIDUAL,
+ {std::string(TEST_CHAINED_CONTROLLER_1) + "/joint1/position"}};
+ test_chained_controller_2->set_command_interface_configuration(chained_cmd_cfg);
+ test_chained_controller_2->set_state_interface_configuration(chained_state_cfg);
+ test_chained_controller_2->set_reference_interface_names({"joint1/position", "joint1/velocity"});
+
+ // create non-chained controller
+ auto test_controller = std::make_shared();
+ controller_interface::InterfaceConfiguration cmd_cfg = {
+ controller_interface::interface_configuration_type::INDIVIDUAL,
+ {std::string(TEST_CHAINED_CONTROLLER_2) + "/joint1/position",
+ std::string(TEST_CHAINED_CONTROLLER_2) + "/joint1/velocity", "joint2/velocity"}};
+ controller_interface::InterfaceConfiguration state_cfg = {
+ controller_interface::interface_configuration_type::INDIVIDUAL,
+ {"joint1/position", "joint1/velocity"}};
+ test_controller->set_command_interface_configuration(cmd_cfg);
+ test_controller->set_state_interface_configuration(state_cfg);
+ // add controllers
+ cm_->add_controller(
+ test_chained_controller_1, TEST_CHAINED_CONTROLLER_1,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_controller, test_controller::TEST_CONTROLLER_NAME,
+ test_controller::TEST_CONTROLLER_CLASS_NAME);
+ cm_->add_controller(
+ test_chained_controller_2, TEST_CHAINED_CONTROLLER_2,
+ test_chainable_controller::TEST_CONTROLLER_CLASS_NAME);
+ // get controller list before configure
+ auto result = call_service_and_wait(*client, request, srv_executor);
+ // check chainable controller
+ ASSERT_EQ(3u, result->controller.size());
+ ASSERT_EQ(result->controller[0].name, TEST_CHAINED_CONTROLLER_1);
+ ASSERT_EQ(result->controller[2].name, TEST_CHAINED_CONTROLLER_2);
+ // check test controller
+ ASSERT_EQ(result->controller[1].name, "test_controller_name");
+
+ // configure controllers
+ for (const auto & controller :
+ {TEST_CHAINED_CONTROLLER_1, test_controller::TEST_CONTROLLER_NAME,
+ TEST_CHAINED_CONTROLLER_2})
+ {
+ cm_->configure_controller(controller);
+ }
+
+ // get controller list after configure
+ result = call_service_and_wait(*client, request, srv_executor);
+ ASSERT_EQ(3u, result->controller.size());
+
+ // reordered controllers
+ ASSERT_EQ(result->controller[0].name, "test_controller_name");
+ ASSERT_EQ(result->controller[1].name, TEST_CHAINED_CONTROLLER_2);
+ ASSERT_EQ(result->controller[2].name, TEST_CHAINED_CONTROLLER_1);
+
+ // activate controllers one by one
+ auto res1 = cm_->switch_controller(
+ {TEST_CHAINED_CONTROLLER_1}, {},
+ controller_manager_msgs::srv::SwitchController::Request::STRICT, true, rclcpp::Duration(0, 0));
+ ASSERT_EQ(res1, controller_interface::return_type::OK);
+ auto res2 = cm_->switch_controller(
+ {TEST_CHAINED_CONTROLLER_2}, {},
+ controller_manager_msgs::srv::SwitchController::Request::STRICT, true, rclcpp::Duration(0, 0));
+ ASSERT_EQ(res2, controller_interface::return_type::OK);
+ auto res3 = cm_->switch_controller(
+ {test_controller::TEST_CONTROLLER_NAME}, {},
+ controller_manager_msgs::srv::SwitchController::Request::STRICT, true, rclcpp::Duration(0, 0));
+ ASSERT_EQ(res3, controller_interface::return_type::OK);
+
+ RCLCPP_ERROR(srv_node->get_logger(), "Check successful!");
+}
+
+TEST_F(TestControllerManagerSrvs, activate_chained_controllers_all_at_once)
+{
+ /// The simulated controller chaining is:
+ /// test_controller_name -> chain_ctrl_2 -> chain_ctrl_1
+ ///
+ /// NOTE: A -> B signifies that the controller A is utilizing the reference interfaces exported
+ /// from the controller B (or) the controller B is utilizing the expected interfaces exported from
+ /// the controller A
+
+ // create server client and request
+ rclcpp::executors::SingleThreadedExecutor srv_executor;
+ rclcpp::Node::SharedPtr srv_node = std::make_shared("srv_client");
+ srv_executor.add_node(srv_node);
+ rclcpp::Client::SharedPtr client =
+ srv_node->create_client("test_controller_manager/list_controllers");
+ auto request = std::make_shared();
+
+ // create set of chained controllers
+ static constexpr char TEST_CHAINED_CONTROLLER_1[] = "test_chainable_controller_name_1";
+ static constexpr char TEST_CHAINED_CONTROLLER_2[] = "test_chainable_controller_name_2";
+ auto test_chained_controller_1 = std::make_shared();
+ controller_interface::InterfaceConfiguration chained_cmd_cfg = {
+ controller_interface::interface_configuration_type::INDIVIDUAL, {"joint1/position"}};
+ controller_interface::InterfaceConfiguration chained_state_cfg = {
+ controller_interface::interface_configuration_type::INDIVIDUAL,
+ {"joint1/position", "joint1/velocity"}};
+ test_chained_controller_1->set_command_interface_configuration(chained_cmd_cfg);
+ test_chained_controller_1->set_state_interface_configuration(chained_state_cfg);
+ test_chained_controller_1->set_reference_interface_names({"joint1/position", "joint1/velocity"});
+
+ auto test_chained_controller_2 = std::make_shared();
+ chained_cmd_cfg = {
+ controller_interface::interface_configuration_type::INDIVIDUAL,
+ {std::string(TEST_CHAINED_CONTROLLER_1) + "/joint1/position"}};
+ test_chained_controller_2->set_command_interface_configuration(chained_cmd_cfg);
+ test_chained_controller_2->set_state_interface_configuration(chained_state_cfg);
+ test_chained_controller_2->set_reference_interface_names({"joint1/position", "joint1/velocity"});
+
+ // create non-chained controller
+ auto test_controller = std::make_shared