├── ros2-pkg-create
├── README.md
├── src
│ └── ros2_pkg_create
│ │ ├── copier.yml
│ │ ├── templates
│ │ ├── __init__.py
│ │ └── __main__.py
└── pyproject.toml
├── samples
├── ros2_python_pkg
│ ├── resource
│ │ └── ros2_python_pkg
│ ├── ros2_python_pkg
│ │ ├── __init__.py
│ │ └── ros2_python_node.py
│ ├── config
│ │ └── params.yml
│ ├── setup.cfg
│ ├── .gitlab-ci.yml
│ ├── package.xml
│ ├── README.md
│ ├── setup.py
│ └── launch
│ │ └── ros2_python_node_launch.py
├── ros2_interfaces_pkg
│ ├── msg
│ │ └── Message.msg
│ ├── srv
│ │ └── Service.srv
│ ├── action
│ │ └── Action.action
│ ├── README.md
│ ├── CMakeLists.txt
│ └── package.xml
├── ros2_python_all_pkg
│ ├── resource
│ │ └── ros2_python_all_pkg
│ ├── ros2_python_all_pkg
│ │ ├── __init__.py
│ │ └── ros2_python_node.py
│ ├── config
│ │ └── params.yml
│ ├── setup.cfg
│ ├── .gitlab-ci.yml
│ ├── package.xml
│ ├── README.md
│ ├── setup.py
│ └── launch
│ │ └── ros2_python_node_launch.py
├── ros2_cpp_pkg
│ ├── config
│ │ └── params.yml
│ ├── .gitlab-ci.yml
│ ├── package.xml
│ ├── README.md
│ ├── CMakeLists.txt
│ ├── launch
│ │ └── ros2_cpp_node_launch.py
│ ├── include
│ │ └── ros2_cpp_pkg
│ │ │ └── ros2_cpp_node.hpp
│ └── src
│ │ └── ros2_cpp_node.cpp
├── ros2_cpp_all_pkg
│ ├── config
│ │ └── params.yml
│ ├── .gitlab-ci.yml
│ ├── README.md
│ ├── package.xml
│ ├── CMakeLists.txt
│ ├── launch
│ │ └── ros2_cpp_node_launch.py
│ └── include
│ │ └── ros2_cpp_all_pkg
│ │ └── ros2_cpp_node.hpp
├── ros2_cpp_component_pkg
│ ├── config
│ │ └── params.yml
│ ├── .gitlab-ci.yml
│ ├── package.xml
│ ├── README.md
│ ├── CMakeLists.txt
│ ├── launch
│ │ └── ros2_cpp_node_launch.py
│ ├── include
│ │ └── ros2_cpp_component_pkg
│ │ │ └── ros2_cpp_node.hpp
│ └── src
│ │ └── ros2_cpp_node.cpp
├── ros2_cpp_lifecycle_pkg
│ ├── config
│ │ └── params.yml
│ ├── .gitlab-ci.yml
│ ├── package.xml
│ ├── README.md
│ ├── CMakeLists.txt
│ ├── launch
│ │ └── ros2_cpp_node_launch.py
│ ├── include
│ │ └── ros2_cpp_lifecycle_pkg
│ │ │ └── ros2_cpp_node.hpp
│ └── src
│ │ └── ros2_cpp_node.cpp
├── ros2_cpp_multi_threaded_pkg
│ ├── config
│ │ └── params.yml
│ ├── .gitlab-ci.yml
│ ├── package.xml
│ ├── README.md
│ ├── CMakeLists.txt
│ ├── launch
│ │ └── ros2_cpp_node_launch.py
│ ├── include
│ │ └── ros2_cpp_multi_threaded_pkg
│ │ │ └── ros2_cpp_node.hpp
│ └── src
│ │ └── ros2_cpp_node.cpp
├── ros2_cpp_all_pkg_interfaces
│ ├── action
│ │ └── Fibonacci.action
│ ├── CMakeLists.txt
│ ├── README.md
│ └── package.xml
├── ros2_python_all_pkg_interfaces
│ ├── action
│ │ └── Fibonacci.action
│ ├── CMakeLists.txt
│ ├── README.md
│ └── package.xml
└── generate_samples.sh
├── templates
├── ros2_python_pkg
│ ├── {{ package_name }}
│ │ ├── resource
│ │ │ └── {{ package_name }}.jinja
│ │ ├── {{ package_name }}
│ │ │ ├── __init__.py
│ │ │ └── {{ node_name }}.py.jinja
│ │ ├── {% if has_params %}config{% endif %}
│ │ │ └── params.yml.jinja
│ │ ├── setup.cfg.jinja
│ │ ├── {% if has_docker_ros and docker_ros_type == 'gitlab' %}.gitlab-ci.yml{% endif %}.jinja
│ │ ├── {% if has_docker_ros and docker_ros_type == 'github' %}.github{% endif %}
│ │ │ └── workflows
│ │ │ │ └── docker-ros.yml.jinja
│ │ ├── {% if has_launch_file %}launch{% endif %}
│ │ │ ├── {% if launch_file_type == 'yml' %}{{ node_name }}_launch.yml{% endif %}.jinja
│ │ │ ├── {% if launch_file_type == 'xml' %}{{ node_name }}_launch.xml{% endif %}.jinja
│ │ │ └── {% if launch_file_type == 'py' %}{{ node_name }}_launch.py{% endif %}.jinja
│ │ ├── package.xml.jinja
│ │ ├── README.md.jinja
│ │ └── setup.py.jinja
│ └── {% if has_action_server %}{{ package_name }}_interfaces{% endif %}
│ │ ├── action
│ │ └── Fibonacci.action
│ │ ├── CMakeLists.txt.jinja
│ │ ├── README.md.jinja
│ │ └── package.xml.jinja
├── ros2_interfaces_pkg
│ └── {{ package_name }}
│ │ ├── {% if 'msg' in interface_types %}msg{% endif %}
│ │ └── {{ msg_name }}.msg
│ │ ├── {% if 'srv' in interface_types %}srv{% endif %}
│ │ └── {{ srv_name }}.srv
│ │ ├── {% if 'action' in interface_types %}action{% endif %}
│ │ └── {{ action_name }}.action
│ │ ├── README.md.jinja
│ │ ├── CMakeLists.txt.jinja
│ │ └── package.xml.jinja
└── ros2_cpp_pkg
│ ├── {{ package_name }}
│ ├── {% if has_params %}config{% endif %}
│ │ └── params.yml.jinja
│ ├── {% if has_docker_ros and docker_ros_type == 'gitlab' %}.gitlab-ci.yml{% endif %}.jinja
│ ├── {% if has_docker_ros and docker_ros_type == 'github' %}.github{% endif %}
│ │ └── workflows
│ │ │ └── docker-ros.yml.jinja
│ ├── {% if has_launch_file %}launch{% endif %}
│ │ ├── {% if launch_file_type == 'yml' %}{{ node_name }}_launch.yml{% endif %}.jinja
│ │ ├── {% if launch_file_type == 'xml' %}{{ node_name }}_launch.xml{% endif %}.jinja
│ │ └── {% if launch_file_type == 'py' %}{{ node_name }}_launch.py{% endif %}.jinja
│ ├── README.md.jinja
│ ├── package.xml.jinja
│ ├── CMakeLists.txt.jinja
│ └── include
│ │ └── {{ package_name }}
│ │ └── {{ node_name }}.hpp.jinja
│ └── {% if has_action_server %}{{ package_name }}_interfaces{% endif %}
│ ├── action
│ └── Fibonacci.action
│ ├── CMakeLists.txt.jinja
│ ├── README.md.jinja
│ └── package.xml.jinja
├── assets
└── cli.png
├── LICENSE
├── .github
└── workflows
│ ├── publish.yml
│ ├── check-samples.yml
│ └── generate-and-test.yml
├── .gitignore
├── copier.yml
└── README.md
/ros2-pkg-create/README.md:
--------------------------------------------------------------------------------
1 | ../README.md
--------------------------------------------------------------------------------
/samples/ros2_python_pkg/resource/ros2_python_pkg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/samples/ros2_python_pkg/ros2_python_pkg/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/samples/ros2_interfaces_pkg/msg/Message.msg:
--------------------------------------------------------------------------------
1 | int32 data
2 |
--------------------------------------------------------------------------------
/samples/ros2_python_all_pkg/resource/ros2_python_all_pkg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/samples/ros2_python_all_pkg/ros2_python_all_pkg/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ros2-pkg-create/src/ros2_pkg_create/copier.yml:
--------------------------------------------------------------------------------
1 | ../../../copier.yml
--------------------------------------------------------------------------------
/ros2-pkg-create/src/ros2_pkg_create/templates:
--------------------------------------------------------------------------------
1 | ../../../templates/
--------------------------------------------------------------------------------
/templates/ros2_python_pkg/{{ package_name }}/resource/{{ package_name }}.jinja:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/ros2_python_pkg/{{ package_name }}/{{ package_name }}/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/samples/ros2_interfaces_pkg/srv/Service.srv:
--------------------------------------------------------------------------------
1 | int32 request
2 | ---
3 | int32 response
4 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_pkg/config/params.yml:
--------------------------------------------------------------------------------
1 | /**/*:
2 | ros__parameters:
3 | param: 1.0
4 |
--------------------------------------------------------------------------------
/assets/cli.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ika-rwth-aachen/ros2-pkg-create/HEAD/assets/cli.png
--------------------------------------------------------------------------------
/samples/ros2_cpp_all_pkg/config/params.yml:
--------------------------------------------------------------------------------
1 | /**/*:
2 | ros__parameters:
3 | param: 1.0
4 |
--------------------------------------------------------------------------------
/samples/ros2_python_all_pkg/config/params.yml:
--------------------------------------------------------------------------------
1 | /**/*:
2 | ros__parameters:
3 | param: 1.0
4 |
--------------------------------------------------------------------------------
/samples/ros2_python_pkg/config/params.yml:
--------------------------------------------------------------------------------
1 | /**/*:
2 | ros__parameters:
3 | param: 1.0
4 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_component_pkg/config/params.yml:
--------------------------------------------------------------------------------
1 | /**/*:
2 | ros__parameters:
3 | param: 1.0
4 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_lifecycle_pkg/config/params.yml:
--------------------------------------------------------------------------------
1 | /**/*:
2 | ros__parameters:
3 | param: 1.0
4 |
--------------------------------------------------------------------------------
/ros2-pkg-create/src/ros2_pkg_create/__init__.py:
--------------------------------------------------------------------------------
1 | __name__ = "ros2-pkg-create"
2 | __version__ = "1.2.0"
3 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_multi_threaded_pkg/config/params.yml:
--------------------------------------------------------------------------------
1 | /**/*:
2 | ros__parameters:
3 | param: 1.0
4 |
--------------------------------------------------------------------------------
/samples/ros2_interfaces_pkg/action/Action.action:
--------------------------------------------------------------------------------
1 | int32 goal
2 | ---
3 | int32 feedback
4 | ---
5 | int32 result
6 |
--------------------------------------------------------------------------------
/templates/ros2_interfaces_pkg/{{ package_name }}/{% if 'msg' in interface_types %}msg{% endif %}/{{ msg_name }}.msg:
--------------------------------------------------------------------------------
1 | int32 data
2 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_all_pkg_interfaces/action/Fibonacci.action:
--------------------------------------------------------------------------------
1 | int32 order
2 | ---
3 | int32[] sequence
4 | ---
5 | int32[] partial_sequence
6 |
--------------------------------------------------------------------------------
/samples/ros2_python_all_pkg_interfaces/action/Fibonacci.action:
--------------------------------------------------------------------------------
1 | int32 order
2 | ---
3 | int32[] sequence
4 | ---
5 | int32[] partial_sequence
6 |
--------------------------------------------------------------------------------
/samples/ros2_python_pkg/setup.cfg:
--------------------------------------------------------------------------------
1 | [develop]
2 | script_dir=$base/lib/ros2_python_pkg
3 | [install]
4 | install_scripts=$base/lib/ros2_python_pkg
5 |
--------------------------------------------------------------------------------
/templates/ros2_cpp_pkg/{{ package_name }}/{% if has_params %}config{% endif %}/params.yml.jinja:
--------------------------------------------------------------------------------
1 | /**/*:
2 | ros__parameters:
3 | param: 1.0
4 |
--------------------------------------------------------------------------------
/templates/ros2_python_pkg/{{ package_name }}/{% if has_params %}config{% endif %}/params.yml.jinja:
--------------------------------------------------------------------------------
1 | /**/*:
2 | ros__parameters:
3 | param: 1.0
4 |
--------------------------------------------------------------------------------
/samples/ros2_python_all_pkg/setup.cfg:
--------------------------------------------------------------------------------
1 | [develop]
2 | script_dir=$base/lib/ros2_python_all_pkg
3 | [install]
4 | install_scripts=$base/lib/ros2_python_all_pkg
5 |
--------------------------------------------------------------------------------
/templates/ros2_interfaces_pkg/{{ package_name }}/{% if 'srv' in interface_types %}srv{% endif %}/{{ srv_name }}.srv:
--------------------------------------------------------------------------------
1 | int32 request
2 | ---
3 | int32 response
4 |
--------------------------------------------------------------------------------
/templates/ros2_python_pkg/{{ package_name }}/setup.cfg.jinja:
--------------------------------------------------------------------------------
1 | [develop]
2 | script_dir=$base/lib/{{ package_name }}
3 | [install]
4 | install_scripts=$base/lib/{{ package_name }}
5 |
--------------------------------------------------------------------------------
/templates/ros2_cpp_pkg/{% if has_action_server %}{{ package_name }}_interfaces{% endif %}/action/Fibonacci.action:
--------------------------------------------------------------------------------
1 | int32 order
2 | ---
3 | int32[] sequence
4 | ---
5 | int32[] partial_sequence
6 |
--------------------------------------------------------------------------------
/templates/ros2_interfaces_pkg/{{ package_name }}/{% if 'action' in interface_types %}action{% endif %}/{{ action_name }}.action:
--------------------------------------------------------------------------------
1 | int32 goal
2 | ---
3 | int32 feedback
4 | ---
5 | int32 result
6 |
--------------------------------------------------------------------------------
/templates/ros2_python_pkg/{% if has_action_server %}{{ package_name }}_interfaces{% endif %}/action/Fibonacci.action:
--------------------------------------------------------------------------------
1 | int32 order
2 | ---
3 | int32[] sequence
4 | ---
5 | int32[] partial_sequence
6 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_pkg/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | include:
2 | - remote: https://raw.githubusercontent.com/ika-rwth-aachen/docker-ros/main/.gitlab-ci/docker-ros.yml
3 |
4 | variables:
5 | PLATFORM: amd64,arm64
6 | TARGET: dev,run
7 | BASE_IMAGE: rwthika/ros2:latest
8 | COMMAND: ros2 run ros2_cpp_pkg ros2_cpp_node
9 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_all_pkg/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | include:
2 | - remote: https://raw.githubusercontent.com/ika-rwth-aachen/docker-ros/main/.gitlab-ci/docker-ros.yml
3 |
4 | variables:
5 | PLATFORM: amd64,arm64
6 | TARGET: dev,run
7 | BASE_IMAGE: rwthika/ros2:latest
8 | COMMAND: ros2 run ros2_cpp_all_pkg ros2_cpp_node
9 |
--------------------------------------------------------------------------------
/samples/ros2_python_pkg/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | include:
2 | - remote: https://raw.githubusercontent.com/ika-rwth-aachen/docker-ros/main/.gitlab-ci/docker-ros.yml
3 |
4 | variables:
5 | PLATFORM: amd64,arm64
6 | TARGET: dev,run
7 | BASE_IMAGE: rwthika/ros2:latest
8 | COMMAND: ros2 run ros2_python_pkg ros2_python_node
9 |
--------------------------------------------------------------------------------
/samples/ros2_python_all_pkg/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | include:
2 | - remote: https://raw.githubusercontent.com/ika-rwth-aachen/docker-ros/main/.gitlab-ci/docker-ros.yml
3 |
4 | variables:
5 | PLATFORM: amd64,arm64
6 | TARGET: dev,run
7 | BASE_IMAGE: rwthika/ros2:latest
8 | COMMAND: ros2 run ros2_python_all_pkg ros2_python_node
9 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_component_pkg/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | include:
2 | - remote: https://raw.githubusercontent.com/ika-rwth-aachen/docker-ros/main/.gitlab-ci/docker-ros.yml
3 |
4 | variables:
5 | PLATFORM: amd64,arm64
6 | TARGET: dev,run
7 | BASE_IMAGE: rwthika/ros2:latest
8 | COMMAND: ros2 run ros2_cpp_component_pkg ros2_cpp_node
9 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_lifecycle_pkg/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | include:
2 | - remote: https://raw.githubusercontent.com/ika-rwth-aachen/docker-ros/main/.gitlab-ci/docker-ros.yml
3 |
4 | variables:
5 | PLATFORM: amd64,arm64
6 | TARGET: dev,run
7 | BASE_IMAGE: rwthika/ros2:latest
8 | COMMAND: ros2 run ros2_cpp_lifecycle_pkg ros2_cpp_node
9 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_multi_threaded_pkg/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | include:
2 | - remote: https://raw.githubusercontent.com/ika-rwth-aachen/docker-ros/main/.gitlab-ci/docker-ros.yml
3 |
4 | variables:
5 | PLATFORM: amd64,arm64
6 | TARGET: dev,run
7 | BASE_IMAGE: rwthika/ros2:latest
8 | COMMAND: ros2 run ros2_cpp_multi_threaded_pkg ros2_cpp_node
9 |
--------------------------------------------------------------------------------
/samples/ros2_interfaces_pkg/README.md:
--------------------------------------------------------------------------------
1 | # ros2_interfaces_pkg
2 |
3 | TODO
4 |
5 |
6 | ### Messages
7 |
8 | | Type | Description |
9 | | --- | --- |
10 | | | |
11 |
12 | ### Services
13 |
14 | | Type | Description |
15 | | --- | --- |
16 | | | |
17 |
18 | ### Actions
19 |
20 | | Type | Description |
21 | | --- | --- |
22 | | | |
23 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_all_pkg_interfaces/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.8)
2 | project(ros2_cpp_all_pkg_interfaces)
3 |
4 | find_package(rosidl_default_generators REQUIRED)
5 |
6 | rosidl_generate_interfaces(${PROJECT_NAME}
7 | action/Fibonacci.action
8 | )
9 |
10 | ament_export_dependencies(rosidl_default_runtime)
11 |
12 | ament_package()
13 |
--------------------------------------------------------------------------------
/samples/ros2_python_all_pkg_interfaces/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.8)
2 | project(ros2_python_all_pkg_interfaces)
3 |
4 | find_package(rosidl_default_generators REQUIRED)
5 |
6 | rosidl_generate_interfaces(${PROJECT_NAME}
7 | action/Fibonacci.action
8 | )
9 |
10 | ament_export_dependencies(rosidl_default_runtime)
11 |
12 | ament_package()
13 |
--------------------------------------------------------------------------------
/templates/ros2_interfaces_pkg/{{ package_name }}/README.md.jinja:
--------------------------------------------------------------------------------
1 | # {{ package_name }}
2 |
3 | {{ description }}
4 |
5 |
6 | ### Messages
7 |
8 | | Type | Description |
9 | | --- | --- |
10 | | | |
11 |
12 | ### Services
13 |
14 | | Type | Description |
15 | | --- | --- |
16 | | | |
17 |
18 | ### Actions
19 |
20 | | Type | Description |
21 | | --- | --- |
22 | | | |
23 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_all_pkg_interfaces/README.md:
--------------------------------------------------------------------------------
1 | # ros2_cpp_all_pkg_interfaces
2 |
3 | ROS interface definitions for ros2_cpp_all_pkg
4 |
5 |
6 | ### Messages
7 |
8 | | Type | Description |
9 | | --- | --- |
10 | | | |
11 |
12 | ### Services
13 |
14 | | Type | Description |
15 | | --- | --- |
16 | | | |
17 |
18 | ### Actions
19 |
20 | | Type | Description |
21 | | --- | --- |
22 | | | |
23 |
--------------------------------------------------------------------------------
/templates/ros2_cpp_pkg/{{ package_name }}/{% if has_docker_ros and docker_ros_type == 'gitlab' %}.gitlab-ci.yml{% endif %}.jinja:
--------------------------------------------------------------------------------
1 | include:
2 | - remote: https://raw.githubusercontent.com/ika-rwth-aachen/docker-ros/main/.gitlab-ci/docker-ros.yml
3 |
4 | variables:
5 | PLATFORM: amd64,arm64
6 | TARGET: dev,run
7 | BASE_IMAGE: rwthika/ros2:latest
8 | COMMAND: ros2 run {{ package_name }} {{ node_name }}
9 |
--------------------------------------------------------------------------------
/samples/ros2_interfaces_pkg/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.8)
2 | project(ros2_interfaces_pkg)
3 |
4 | find_package(rosidl_default_generators REQUIRED)
5 |
6 | rosidl_generate_interfaces(${PROJECT_NAME}
7 | msg/Message.msg
8 | srv/Service.srv
9 | action/Action.action
10 | DEPENDENCIES
11 | )
12 |
13 | ament_export_dependencies(rosidl_default_runtime)
14 |
15 | ament_package()
16 |
--------------------------------------------------------------------------------
/samples/ros2_python_all_pkg_interfaces/README.md:
--------------------------------------------------------------------------------
1 | # ros2_python_all_pkg_interfaces
2 |
3 | ROS interface definitions for ros2_python_all_pkg
4 |
5 |
6 | ### Messages
7 |
8 | | Type | Description |
9 | | --- | --- |
10 | | | |
11 |
12 | ### Services
13 |
14 | | Type | Description |
15 | | --- | --- |
16 | | | |
17 |
18 | ### Actions
19 |
20 | | Type | Description |
21 | | --- | --- |
22 | | | |
23 |
--------------------------------------------------------------------------------
/templates/ros2_python_pkg/{{ package_name }}/{% if has_docker_ros and docker_ros_type == 'gitlab' %}.gitlab-ci.yml{% endif %}.jinja:
--------------------------------------------------------------------------------
1 | include:
2 | - remote: https://raw.githubusercontent.com/ika-rwth-aachen/docker-ros/main/.gitlab-ci/docker-ros.yml
3 |
4 | variables:
5 | PLATFORM: amd64,arm64
6 | TARGET: dev,run
7 | BASE_IMAGE: rwthika/ros2:latest
8 | COMMAND: ros2 run {{ package_name }} {{ node_name }}
9 |
--------------------------------------------------------------------------------
/templates/ros2_cpp_pkg/{% if has_action_server %}{{ package_name }}_interfaces{% endif %}/CMakeLists.txt.jinja:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.8)
2 | project({{ package_name }}_interfaces)
3 |
4 | find_package(rosidl_default_generators REQUIRED)
5 |
6 | rosidl_generate_interfaces(${PROJECT_NAME}
7 | action/Fibonacci.action
8 | )
9 |
10 | ament_export_dependencies(rosidl_default_runtime)
11 |
12 | ament_package()
13 |
--------------------------------------------------------------------------------
/templates/ros2_python_pkg/{% if has_action_server %}{{ package_name }}_interfaces{% endif %}/CMakeLists.txt.jinja:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.8)
2 | project({{ package_name }}_interfaces)
3 |
4 | find_package(rosidl_default_generators REQUIRED)
5 |
6 | rosidl_generate_interfaces(${PROJECT_NAME}
7 | action/Fibonacci.action
8 | )
9 |
10 | ament_export_dependencies(rosidl_default_runtime)
11 |
12 | ament_package()
13 |
--------------------------------------------------------------------------------
/templates/ros2_cpp_pkg/{% if has_action_server %}{{ package_name }}_interfaces{% endif %}/README.md.jinja:
--------------------------------------------------------------------------------
1 | # {{ package_name }}_interfaces
2 |
3 | ROS interface definitions for {{ package_name }}
4 |
5 |
6 | ### Messages
7 |
8 | | Type | Description |
9 | | --- | --- |
10 | | | |
11 |
12 | ### Services
13 |
14 | | Type | Description |
15 | | --- | --- |
16 | | | |
17 |
18 | ### Actions
19 |
20 | | Type | Description |
21 | | --- | --- |
22 | | | |
23 |
--------------------------------------------------------------------------------
/templates/ros2_python_pkg/{% if has_action_server %}{{ package_name }}_interfaces{% endif %}/README.md.jinja:
--------------------------------------------------------------------------------
1 | # {{ package_name }}_interfaces
2 |
3 | ROS interface definitions for {{ package_name }}
4 |
5 |
6 | ### Messages
7 |
8 | | Type | Description |
9 | | --- | --- |
10 | | | |
11 |
12 | ### Services
13 |
14 | | Type | Description |
15 | | --- | --- |
16 | | | |
17 |
18 | ### Actions
19 |
20 | | Type | Description |
21 | | --- | --- |
22 | | | |
23 |
--------------------------------------------------------------------------------
/templates/ros2_cpp_pkg/{{ package_name }}/{% if has_docker_ros and docker_ros_type == 'github' %}.github{% endif %}/workflows/docker-ros.yml.jinja:
--------------------------------------------------------------------------------
1 | name: docker-ros
2 | on: push
3 | jobs:
4 | latest:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - uses: ika-rwth-aachen/docker-ros@main
8 | with:
9 | platform: amd64
10 | target: dev,run
11 | base-image: rwthika/ros2:latest
12 | command: ros2 run {{ package_name }} {{ node_name }}
13 |
--------------------------------------------------------------------------------
/templates/ros2_python_pkg/{{ package_name }}/{% if has_docker_ros and docker_ros_type == 'github' %}.github{% endif %}/workflows/docker-ros.yml.jinja:
--------------------------------------------------------------------------------
1 | name: docker-ros
2 | on: push
3 | jobs:
4 | latest:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - uses: ika-rwth-aachen/docker-ros@main
8 | with:
9 | platform: amd64
10 | target: dev,run
11 | base-image: rwthika/ros2:latest
12 | command: ros2 run {{ package_name }} {{ node_name }}
13 |
--------------------------------------------------------------------------------
/templates/ros2_interfaces_pkg/{{ package_name }}/CMakeLists.txt.jinja:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.8)
2 | project({{ package_name }})
3 |
4 | find_package(rosidl_default_generators REQUIRED)
5 |
6 | rosidl_generate_interfaces(${PROJECT_NAME}
7 | {% if 'msg' in interface_types %}
8 | msg/{{ msg_name }}.msg
9 | {% endif %}
10 | {% if 'srv' in interface_types %}
11 | srv/{{ srv_name }}.srv
12 | {% endif %}
13 | {% if 'action' in interface_types %}
14 | action/{{ action_name }}.action
15 | {% endif %}
16 | DEPENDENCIES
17 | )
18 |
19 | ament_export_dependencies(rosidl_default_runtime)
20 |
21 | ament_package()
22 |
--------------------------------------------------------------------------------
/samples/ros2_python_pkg/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ros2_python_pkg
6 | 0.0.0
7 | TODO
8 |
9 | TODO
10 | TODO
11 |
12 | TODO
13 |
14 | rclpy
15 | std_msgs
16 |
17 |
18 | ament_python
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/templates/ros2_cpp_pkg/{{ package_name }}/{% if has_launch_file %}launch{% endif %}/{% if launch_file_type == 'yml' %}{{ node_name }}_launch.yml{% endif %}.jinja:
--------------------------------------------------------------------------------
1 | launch:
2 | - arg:
3 | name: name
4 | default: {{ node_name }}
5 | description: node name
6 | - arg:
7 | name: namespace
8 | default: ""
9 | description: node namespace
10 | - node:
11 | pkg: {{ package_name }}
12 | exec: {{ node_name }}
13 | namespace: "$(var namespace)"
14 | name: "$(var name)"
15 | output: screen
16 | {% if has_params %}
17 | param:
18 | - name: param
19 | value: 1.0
20 | {% endif %}
21 |
--------------------------------------------------------------------------------
/templates/ros2_python_pkg/{{ package_name }}/{% if has_launch_file %}launch{% endif %}/{% if launch_file_type == 'yml' %}{{ node_name }}_launch.yml{% endif %}.jinja:
--------------------------------------------------------------------------------
1 | launch:
2 | - arg:
3 | name: name
4 | default: {{ node_name }}
5 | description: node name
6 | - arg:
7 | name: namespace
8 | default: ""
9 | description: node namespace
10 | - node:
11 | pkg: {{ package_name }}
12 | exec: {{ node_name }}
13 | namespace: "$(var namespace)"
14 | name: "$(var name)"
15 | output: screen
16 | {% if has_params %}
17 | param:
18 | - name: param
19 | value: 1.0
20 | {% endif %}
21 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_pkg/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ros2_cpp_pkg
6 | 0.0.0
7 | TODO
8 |
9 | TODO
10 | TODO
11 |
12 | TODO
13 |
14 | ament_cmake
15 |
16 | rclcpp
17 | std_msgs
18 |
19 |
20 | ament_cmake
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/templates/ros2_cpp_pkg/{{ package_name }}/{% if has_launch_file %}launch{% endif %}/{% if launch_file_type == 'xml' %}{{ node_name }}_launch.xml{% endif %}.jinja:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {% if has_params %}
9 |
10 | {% endif %}
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/templates/ros2_python_pkg/{{ package_name }}/{% if has_launch_file %}launch{% endif %}/{% if launch_file_type == 'xml' %}{{ node_name }}_launch.xml{% endif %}.jinja:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {% if has_params %}
9 |
10 | {% endif %}
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_multi_threaded_pkg/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ros2_cpp_multi_threaded_pkg
6 | 0.0.0
7 | TODO
8 |
9 | TODO
10 | TODO
11 |
12 | TODO
13 |
14 | ament_cmake
15 |
16 | rclcpp
17 | std_msgs
18 |
19 |
20 | ament_cmake
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/samples/ros2_python_all_pkg/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ros2_python_all_pkg
6 | 0.0.0
7 | TODO
8 |
9 | TODO
10 | TODO
11 |
12 | TODO
13 |
14 | rclpy
15 | std_msgs
16 | std_srvs
17 | ros2_python_all_pkg_interfaces
18 |
19 |
20 | ament_python
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_component_pkg/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ros2_cpp_component_pkg
6 | 0.0.0
7 | TODO
8 |
9 | TODO
10 | TODO
11 |
12 | TODO
13 |
14 | ament_cmake
15 |
16 | rclcpp
17 | rclcpp_components
18 | std_msgs
19 |
20 |
21 | ament_cmake
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_lifecycle_pkg/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ros2_cpp_lifecycle_pkg
6 | 0.0.0
7 | TODO
8 |
9 | TODO
10 | TODO
11 |
12 | TODO
13 |
14 | ament_cmake
15 |
16 | rclcpp
17 | rclcpp_lifecycle
18 | std_msgs
19 |
20 |
21 | ament_cmake
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/samples/ros2_interfaces_pkg/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ros2_interfaces_pkg
6 | 0.0.0
7 | TODO
8 |
9 | TODO
10 | TODO
11 |
12 | TODO
13 |
14 | ament_cmake
15 | rosidl_default_generators
16 | rosidl_default_runtime
17 |
18 | rosidl_interface_packages
19 |
20 |
21 | ament_cmake
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_all_pkg_interfaces/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ros2_cpp_all_pkg_interfaces
6 | 0.0.0
7 | ROS interface definitions for ros2_cpp_all_pkg
8 |
9 | TODO
10 | TODO
11 |
12 | TODO
13 |
14 | ament_cmake
15 | rosidl_default_generators
16 | rosidl_default_runtime
17 |
18 | rosidl_interface_packages
19 |
20 |
21 | ament_cmake
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_pkg/README.md:
--------------------------------------------------------------------------------
1 | # ros2_cpp_pkg
2 |
3 | TODO
4 |
5 | - [Container Images](#container-images)
6 | - [ros2_cpp_node](#ros2_cpp_node)
7 |
8 |
9 | ### Container Images
10 |
11 | | Description | Image:Tag | Default Command |
12 | | --- | --- | -- |
13 | | | | |
14 |
15 |
16 | ## `ros2_cpp_node`
17 |
18 | ### Subscribed Topics
19 |
20 | | Topic | Type | Description |
21 | | --- | --- | --- |
22 | | | | |
23 |
24 | ### Published Topics
25 |
26 | | Topic | Type | Description |
27 | | --- | --- | --- |
28 | | | | |
29 |
30 | ### Services
31 |
32 | | Service | Type | Description |
33 | | --- | --- | --- |
34 | | | | |
35 |
36 | ### Actions
37 |
38 | | Action | Type | Description |
39 | | --- | --- | --- |
40 | | | | |
41 |
42 | ### Parameters
43 |
44 | | Parameter | Type | Default | Description |
45 | | --- | --- | --- | --- |
46 | | | | | |
47 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_all_pkg/README.md:
--------------------------------------------------------------------------------
1 | # ros2_cpp_all_pkg
2 |
3 | TODO
4 |
5 | - [Container Images](#container-images)
6 | - [ros2_cpp_node](#ros2_cpp_node)
7 |
8 |
9 | ### Container Images
10 |
11 | | Description | Image:Tag | Default Command |
12 | | --- | --- | -- |
13 | | | | |
14 |
15 |
16 | ## `ros2_cpp_node`
17 |
18 | ### Subscribed Topics
19 |
20 | | Topic | Type | Description |
21 | | --- | --- | --- |
22 | | | | |
23 |
24 | ### Published Topics
25 |
26 | | Topic | Type | Description |
27 | | --- | --- | --- |
28 | | | | |
29 |
30 | ### Services
31 |
32 | | Service | Type | Description |
33 | | --- | --- | --- |
34 | | | | |
35 |
36 | ### Actions
37 |
38 | | Action | Type | Description |
39 | | --- | --- | --- |
40 | | | | |
41 |
42 | ### Parameters
43 |
44 | | Parameter | Type | Default | Description |
45 | | --- | --- | --- | --- |
46 | | | | | |
47 |
--------------------------------------------------------------------------------
/samples/ros2_python_all_pkg_interfaces/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ros2_python_all_pkg_interfaces
6 | 0.0.0
7 | ROS interface definitions for ros2_python_all_pkg
8 |
9 | TODO
10 | TODO
11 |
12 | TODO
13 |
14 | ament_cmake
15 | rosidl_default_generators
16 | rosidl_default_runtime
17 |
18 | rosidl_interface_packages
19 |
20 |
21 | ament_cmake
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/samples/ros2_python_pkg/README.md:
--------------------------------------------------------------------------------
1 | # ros2_python_pkg
2 |
3 | TODO
4 |
5 | - [Container Images](#container-images)
6 | - [ros2_python_node](#ros2_python_node)
7 |
8 |
9 | ### Container Images
10 |
11 | | Description | Image:Tag | Default Command |
12 | | --- | --- | -- |
13 | | | | |
14 |
15 |
16 | ## `ros2_python_node`
17 |
18 | ### Subscribed Topics
19 |
20 | | Topic | Type | Description |
21 | | --- | --- | --- |
22 | | | | |
23 |
24 | ### Published Topics
25 |
26 | | Topic | Type | Description |
27 | | --- | --- | --- |
28 | | | | |
29 |
30 | ### Services
31 |
32 | | Service | Type | Description |
33 | | --- | --- | --- |
34 | | | | |
35 |
36 | ### Actions
37 |
38 | | Action | Type | Description |
39 | | --- | --- | --- |
40 | | | | |
41 |
42 | ### Parameters
43 |
44 | | Parameter | Type | Default | Description |
45 | | --- | --- | --- | --- |
46 | | | | | |
47 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_all_pkg/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ros2_cpp_all_pkg
6 | 0.0.0
7 | TODO
8 |
9 | TODO
10 | TODO
11 |
12 | TODO
13 |
14 | ament_cmake
15 |
16 | rclcpp
17 | rclcpp_action
18 | rclcpp_components
19 | rclcpp_lifecycle
20 | std_msgs
21 | std_srvs
22 | ros2_cpp_all_pkg_interfaces
23 |
24 |
25 | ament_cmake
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_component_pkg/README.md:
--------------------------------------------------------------------------------
1 | # ros2_cpp_component_pkg
2 |
3 | TODO
4 |
5 | - [Container Images](#container-images)
6 | - [ros2_cpp_node](#ros2_cpp_node)
7 |
8 |
9 | ### Container Images
10 |
11 | | Description | Image:Tag | Default Command |
12 | | --- | --- | -- |
13 | | | | |
14 |
15 |
16 | ## `ros2_cpp_node`
17 |
18 | ### Subscribed Topics
19 |
20 | | Topic | Type | Description |
21 | | --- | --- | --- |
22 | | | | |
23 |
24 | ### Published Topics
25 |
26 | | Topic | Type | Description |
27 | | --- | --- | --- |
28 | | | | |
29 |
30 | ### Services
31 |
32 | | Service | Type | Description |
33 | | --- | --- | --- |
34 | | | | |
35 |
36 | ### Actions
37 |
38 | | Action | Type | Description |
39 | | --- | --- | --- |
40 | | | | |
41 |
42 | ### Parameters
43 |
44 | | Parameter | Type | Default | Description |
45 | | --- | --- | --- | --- |
46 | | | | | |
47 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_lifecycle_pkg/README.md:
--------------------------------------------------------------------------------
1 | # ros2_cpp_lifecycle_pkg
2 |
3 | TODO
4 |
5 | - [Container Images](#container-images)
6 | - [ros2_cpp_node](#ros2_cpp_node)
7 |
8 |
9 | ### Container Images
10 |
11 | | Description | Image:Tag | Default Command |
12 | | --- | --- | -- |
13 | | | | |
14 |
15 |
16 | ## `ros2_cpp_node`
17 |
18 | ### Subscribed Topics
19 |
20 | | Topic | Type | Description |
21 | | --- | --- | --- |
22 | | | | |
23 |
24 | ### Published Topics
25 |
26 | | Topic | Type | Description |
27 | | --- | --- | --- |
28 | | | | |
29 |
30 | ### Services
31 |
32 | | Service | Type | Description |
33 | | --- | --- | --- |
34 | | | | |
35 |
36 | ### Actions
37 |
38 | | Action | Type | Description |
39 | | --- | --- | --- |
40 | | | | |
41 |
42 | ### Parameters
43 |
44 | | Parameter | Type | Default | Description |
45 | | --- | --- | --- | --- |
46 | | | | | |
47 |
--------------------------------------------------------------------------------
/samples/ros2_python_all_pkg/README.md:
--------------------------------------------------------------------------------
1 | # ros2_python_all_pkg
2 |
3 | TODO
4 |
5 | - [Container Images](#container-images)
6 | - [ros2_python_node](#ros2_python_node)
7 |
8 |
9 | ### Container Images
10 |
11 | | Description | Image:Tag | Default Command |
12 | | --- | --- | -- |
13 | | | | |
14 |
15 |
16 | ## `ros2_python_node`
17 |
18 | ### Subscribed Topics
19 |
20 | | Topic | Type | Description |
21 | | --- | --- | --- |
22 | | | | |
23 |
24 | ### Published Topics
25 |
26 | | Topic | Type | Description |
27 | | --- | --- | --- |
28 | | | | |
29 |
30 | ### Services
31 |
32 | | Service | Type | Description |
33 | | --- | --- | --- |
34 | | | | |
35 |
36 | ### Actions
37 |
38 | | Action | Type | Description |
39 | | --- | --- | --- |
40 | | | | |
41 |
42 | ### Parameters
43 |
44 | | Parameter | Type | Default | Description |
45 | | --- | --- | --- | --- |
46 | | | | | |
47 |
--------------------------------------------------------------------------------
/templates/ros2_interfaces_pkg/{{ package_name }}/package.xml.jinja:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ package_name }}
6 | 0.0.0
7 | {{ description }}
8 |
9 | {{ maintainer }}
10 | {{ author }}
11 |
12 | {{ license }}
13 |
14 | ament_cmake
15 | rosidl_default_generators
16 | rosidl_default_runtime
17 |
18 | rosidl_interface_packages
19 |
20 |
21 | ament_cmake
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/templates/ros2_python_pkg/{{ package_name }}/package.xml.jinja:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ package_name }}
6 | 0.0.0
7 | {{ description }}
8 |
9 | {{ maintainer }}
10 | {{ author }}
11 |
12 | {{ license }}
13 |
14 | rclpy
15 | std_msgs
16 | {% if has_service_server %}
17 | std_srvs
18 | {% endif %}
19 | {% if has_action_server %}
20 | {{ package_name }}_interfaces
21 | {% endif %}
22 |
23 |
24 | ament_python
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_multi_threaded_pkg/README.md:
--------------------------------------------------------------------------------
1 | # ros2_cpp_multi_threaded_pkg
2 |
3 | TODO
4 |
5 | - [Container Images](#container-images)
6 | - [ros2_cpp_node](#ros2_cpp_node)
7 |
8 |
9 | ### Container Images
10 |
11 | | Description | Image:Tag | Default Command |
12 | | --- | --- | -- |
13 | | | | |
14 |
15 |
16 | ## `ros2_cpp_node`
17 |
18 | ### Subscribed Topics
19 |
20 | | Topic | Type | Description |
21 | | --- | --- | --- |
22 | | | | |
23 |
24 | ### Published Topics
25 |
26 | | Topic | Type | Description |
27 | | --- | --- | --- |
28 | | | | |
29 |
30 | ### Services
31 |
32 | | Service | Type | Description |
33 | | --- | --- | --- |
34 | | | | |
35 |
36 | ### Actions
37 |
38 | | Action | Type | Description |
39 | | --- | --- | --- |
40 | | | | |
41 |
42 | ### Parameters
43 |
44 | | Parameter | Type | Default | Description |
45 | | --- | --- | --- | --- |
46 | | | | | |
47 |
--------------------------------------------------------------------------------
/templates/ros2_cpp_pkg/{{ package_name }}/README.md.jinja:
--------------------------------------------------------------------------------
1 | # {{ package_name }}
2 |
3 | {{ description }}
4 |
5 | - [Container Images](#container-images)
6 | - [{{ node_name }}](#{{ node_name }})
7 |
8 |
9 | ### Container Images
10 |
11 | | Description | Image:Tag | Default Command |
12 | | --- | --- | -- |
13 | | | | |
14 |
15 |
16 | ## `{{ node_name }}`
17 |
18 | ### Subscribed Topics
19 |
20 | | Topic | Type | Description |
21 | | --- | --- | --- |
22 | | | | |
23 |
24 | ### Published Topics
25 |
26 | | Topic | Type | Description |
27 | | --- | --- | --- |
28 | | | | |
29 |
30 | ### Services
31 |
32 | | Service | Type | Description |
33 | | --- | --- | --- |
34 | | | | |
35 |
36 | ### Actions
37 |
38 | | Action | Type | Description |
39 | | --- | --- | --- |
40 | | | | |
41 |
42 | ### Parameters
43 |
44 | | Parameter | Type | Default | Description |
45 | | --- | --- | --- | --- |
46 | | | | | |
47 |
--------------------------------------------------------------------------------
/templates/ros2_python_pkg/{{ package_name }}/README.md.jinja:
--------------------------------------------------------------------------------
1 | # {{ package_name }}
2 |
3 | {{ description }}
4 |
5 | - [Container Images](#container-images)
6 | - [{{ node_name }}](#{{ node_name }})
7 |
8 |
9 | ### Container Images
10 |
11 | | Description | Image:Tag | Default Command |
12 | | --- | --- | -- |
13 | | | | |
14 |
15 |
16 | ## `{{ node_name }}`
17 |
18 | ### Subscribed Topics
19 |
20 | | Topic | Type | Description |
21 | | --- | --- | --- |
22 | | | | |
23 |
24 | ### Published Topics
25 |
26 | | Topic | Type | Description |
27 | | --- | --- | --- |
28 | | | | |
29 |
30 | ### Services
31 |
32 | | Service | Type | Description |
33 | | --- | --- | --- |
34 | | | | |
35 |
36 | ### Actions
37 |
38 | | Action | Type | Description |
39 | | --- | --- | --- |
40 | | | | |
41 |
42 | ### Parameters
43 |
44 | | Parameter | Type | Default | Description |
45 | | --- | --- | --- | --- |
46 | | | | | |
47 |
--------------------------------------------------------------------------------
/templates/ros2_cpp_pkg/{% if has_action_server %}{{ package_name }}_interfaces{% endif %}/package.xml.jinja:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ package_name }}_interfaces
6 | 0.0.0
7 | ROS interface definitions for {{ package_name }}
8 |
9 | {{ maintainer }}
10 | {{ author }}
11 |
12 | {{ license }}
13 |
14 | ament_cmake
15 | rosidl_default_generators
16 | rosidl_default_runtime
17 |
18 | rosidl_interface_packages
19 |
20 |
21 | ament_cmake
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/templates/ros2_python_pkg/{% if has_action_server %}{{ package_name }}_interfaces{% endif %}/package.xml.jinja:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ package_name }}_interfaces
6 | 0.0.0
7 | ROS interface definitions for {{ package_name }}
8 |
9 | {{ maintainer }}
10 | {{ author }}
11 |
12 | {{ license }}
13 |
14 | ament_cmake
15 | rosidl_default_generators
16 | rosidl_default_runtime
17 |
18 | rosidl_interface_packages
19 |
20 |
21 | ament_cmake
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_pkg/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.8)
2 | project(ros2_cpp_pkg)
3 |
4 | if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
5 | add_compile_options(-Wall -Wextra -Wpedantic)
6 | endif()
7 |
8 | find_package(ament_cmake REQUIRED)
9 | find_package(rclcpp REQUIRED)
10 | find_package(std_msgs REQUIRED)
11 |
12 | set(TARGET_NAME ros2_cpp_node)
13 |
14 | add_executable(${TARGET_NAME} src/ros2_cpp_node.cpp)
15 |
16 | target_include_directories(${TARGET_NAME} PUBLIC
17 | $
18 | $
19 | )
20 | target_compile_features(${TARGET_NAME} PUBLIC c_std_99 cxx_std_17)
21 |
22 | target_link_libraries(${TARGET_NAME} PUBLIC
23 | rclcpp::rclcpp
24 | ${std_msgs_TARGETS}
25 | )
26 |
27 | install(TARGETS ${TARGET_NAME}
28 | DESTINATION lib/${PROJECT_NAME}
29 | )
30 |
31 | install(DIRECTORY launch
32 | DESTINATION share/${PROJECT_NAME}
33 | OPTIONAL
34 | )
35 |
36 | install(DIRECTORY config
37 | DESTINATION share/${PROJECT_NAME}
38 | OPTIONAL
39 | )
40 |
41 | ament_package()
42 |
--------------------------------------------------------------------------------
/samples/ros2_python_pkg/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 | from glob import glob
3 | from setuptools import setup
4 |
5 | package_name = 'ros2_python_pkg'
6 |
7 | setup(
8 | name=package_name,
9 | version='0.0.0',
10 | packages=[package_name],
11 | data_files=[('share/ament_index/resource_index/packages',
12 | ['resource/' + package_name]),
13 | (os.path.join('share', package_name), ['package.xml']),
14 | (os.path.join('share', package_name,
15 | 'launch'), glob('launch/*launch.[pxy][yma]*')),
16 | (os.path.join('share', package_name,
17 | 'config'), glob('config/*'))],
18 | install_requires=['setuptools'],
19 | zip_safe=True,
20 | maintainer='root',
21 | maintainer_email='root@todo.todo',
22 | description='TODO: Package description',
23 | license='TODO: License declaration',
24 | tests_require=['pytest'],
25 | entry_points={
26 | 'console_scripts':
27 | ['ros2_python_node = ros2_python_pkg.ros2_python_node:main'],
28 | },
29 | )
30 |
--------------------------------------------------------------------------------
/samples/ros2_python_all_pkg/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 | from glob import glob
3 | from setuptools import setup
4 |
5 | package_name = 'ros2_python_all_pkg'
6 |
7 | setup(
8 | name=package_name,
9 | version='0.0.0',
10 | packages=[package_name],
11 | data_files=[('share/ament_index/resource_index/packages',
12 | ['resource/' + package_name]),
13 | (os.path.join('share', package_name), ['package.xml']),
14 | (os.path.join('share', package_name,
15 | 'launch'), glob('launch/*launch.[pxy][yma]*')),
16 | (os.path.join('share', package_name,
17 | 'config'), glob('config/*'))],
18 | install_requires=['setuptools'],
19 | zip_safe=True,
20 | maintainer='root',
21 | maintainer_email='root@todo.todo',
22 | description='TODO: Package description',
23 | license='TODO: License declaration',
24 | tests_require=['pytest'],
25 | entry_points={
26 | 'console_scripts':
27 | ['ros2_python_node = ros2_python_all_pkg.ros2_python_node:main'],
28 | },
29 | )
30 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_multi_threaded_pkg/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.8)
2 | project(ros2_cpp_multi_threaded_pkg)
3 |
4 | if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
5 | add_compile_options(-Wall -Wextra -Wpedantic)
6 | endif()
7 |
8 | find_package(ament_cmake REQUIRED)
9 | find_package(rclcpp REQUIRED)
10 | find_package(std_msgs REQUIRED)
11 |
12 | set(TARGET_NAME ros2_cpp_node)
13 |
14 | add_executable(${TARGET_NAME} src/ros2_cpp_node.cpp)
15 |
16 | target_include_directories(${TARGET_NAME} PUBLIC
17 | $
18 | $
19 | )
20 | target_compile_features(${TARGET_NAME} PUBLIC c_std_99 cxx_std_17)
21 |
22 | target_link_libraries(${TARGET_NAME} PUBLIC
23 | rclcpp::rclcpp
24 | ${std_msgs_TARGETS}
25 | )
26 |
27 | install(TARGETS ${TARGET_NAME}
28 | DESTINATION lib/${PROJECT_NAME}
29 | )
30 |
31 | install(DIRECTORY launch
32 | DESTINATION share/${PROJECT_NAME}
33 | OPTIONAL
34 | )
35 |
36 | install(DIRECTORY config
37 | DESTINATION share/${PROJECT_NAME}
38 | OPTIONAL
39 | )
40 |
41 | ament_package()
42 |
--------------------------------------------------------------------------------
/templates/ros2_python_pkg/{{ package_name }}/setup.py.jinja:
--------------------------------------------------------------------------------
1 | import os
2 | from glob import glob
3 | from setuptools import setup
4 |
5 | package_name = '{{ package_name }}'
6 |
7 | setup(
8 | name=package_name,
9 | version='0.0.0',
10 | packages=[package_name],
11 | data_files=[('share/ament_index/resource_index/packages',
12 | ['resource/' + package_name]),
13 | (os.path.join('share', package_name), ['package.xml']),
14 | (os.path.join('share', package_name,
15 | 'launch'), glob('launch/*launch.[pxy][yma]*')),
16 | (os.path.join('share', package_name,
17 | 'config'), glob('config/*'))],
18 | install_requires=['setuptools'],
19 | zip_safe=True,
20 | maintainer='root',
21 | maintainer_email='root@todo.todo',
22 | description='TODO: Package description',
23 | license='TODO: License declaration',
24 | tests_require=['pytest'],
25 | entry_points={
26 | 'console_scripts':
27 | ['{{ node_name }} = {{ package_name }}.{{ node_name }}:main'],
28 | },
29 | )
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Institute for Automotive Engineering (ika), RWTH Aachen University
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_lifecycle_pkg/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.8)
2 | project(ros2_cpp_lifecycle_pkg)
3 |
4 | if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
5 | add_compile_options(-Wall -Wextra -Wpedantic)
6 | endif()
7 |
8 | find_package(ament_cmake REQUIRED)
9 | find_package(rclcpp REQUIRED)
10 | find_package(rclcpp_lifecycle REQUIRED)
11 | find_package(std_msgs REQUIRED)
12 |
13 | set(TARGET_NAME ros2_cpp_node)
14 |
15 | add_executable(${TARGET_NAME} src/ros2_cpp_node.cpp)
16 |
17 | target_include_directories(${TARGET_NAME} PUBLIC
18 | $
19 | $
20 | )
21 | target_compile_features(${TARGET_NAME} PUBLIC c_std_99 cxx_std_17)
22 |
23 | target_link_libraries(${TARGET_NAME} PUBLIC
24 | rclcpp::rclcpp
25 | ${rclcpp_lifecycle_TARGETS}
26 | ${std_msgs_TARGETS}
27 | )
28 |
29 | install(TARGETS ${TARGET_NAME}
30 | DESTINATION lib/${PROJECT_NAME}
31 | )
32 |
33 | install(DIRECTORY launch
34 | DESTINATION share/${PROJECT_NAME}
35 | OPTIONAL
36 | )
37 |
38 | install(DIRECTORY config
39 | DESTINATION share/${PROJECT_NAME}
40 | OPTIONAL
41 | )
42 |
43 | ament_package()
44 |
--------------------------------------------------------------------------------
/templates/ros2_cpp_pkg/{{ package_name }}/package.xml.jinja:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ package_name }}
6 | 0.0.0
7 | {{ description }}
8 |
9 | {{ maintainer }}
10 | {{ author }}
11 |
12 | {{ license }}
13 |
14 | ament_cmake
15 |
16 | rclcpp
17 | {% if has_action_server %}
18 | rclcpp_action
19 | {% endif %}
20 | {% if is_component %}
21 | rclcpp_components
22 | {% endif %}
23 | {% if is_lifecycle %}
24 | rclcpp_lifecycle
25 | {% endif %}
26 | std_msgs
27 | {% if has_service_server %}
28 | std_srvs
29 | {% endif %}
30 | {% if has_action_server %}
31 | {{ package_name }}_interfaces
32 | {% endif %}
33 |
34 |
35 | ament_cmake
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish to PyPI
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | tags:
8 | - '*'
9 | pull_request:
10 | branches:
11 | - main
12 |
13 | jobs:
14 | publish:
15 | name: Publish ${{ matrix.package }}
16 | runs-on: ubuntu-latest
17 | strategy:
18 | matrix:
19 | package: [ros2-pkg-create]
20 | steps:
21 | - name: Checkout repository
22 | uses: actions/checkout@v3
23 | - name: Set up Python
24 | uses: actions/setup-python@v5.2.0
25 | with:
26 | python-version: "3.x"
27 | - name: Install pypa/build
28 | run: python3 -m pip install --user build
29 | - name: Build wheel and tarball
30 | run: python3 -m build --sdist --wheel --outdir dist/ ${{ matrix.package }}
31 | - name: Publish to TestPyPI
32 | uses: pypa/gh-action-pypi-publish@v1.12.4
33 | with:
34 | password: ${{ secrets.TEST_PYPI_API_TOKEN }}
35 | repository-url: https://test.pypi.org/legacy/
36 | skip-existing: true
37 | verbose: true
38 | - name: Publish to PyPI
39 | if: startsWith(github.ref, 'refs/tags')
40 | uses: pypa/gh-action-pypi-publish@v1.12.4
41 | with:
42 | password: ${{ secrets.PYPI_API_TOKEN }}
43 | skip-existing: true
44 | verbose: true
45 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_component_pkg/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.8)
2 | project(ros2_cpp_component_pkg)
3 |
4 | if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
5 | add_compile_options(-Wall -Wextra -Wpedantic)
6 | endif()
7 |
8 | find_package(ament_cmake REQUIRED)
9 | find_package(rclcpp REQUIRED)
10 | find_package(rclcpp_components REQUIRED)
11 | find_package(std_msgs REQUIRED)
12 |
13 | set(TARGET_NAME ros2_cpp_node_component)
14 |
15 | add_library(${TARGET_NAME} SHARED src/ros2_cpp_node.cpp)
16 |
17 | rclcpp_components_register_node(${TARGET_NAME}
18 | PLUGIN "ros2_cpp_component_pkg::Ros2CppNode"
19 | EXECUTABLE ros2_cpp_node
20 | EXECUTOR SingleThreadedExecutor
21 | )
22 |
23 | target_include_directories(${TARGET_NAME} PUBLIC
24 | $
25 | $
26 | )
27 | target_compile_features(${TARGET_NAME} PUBLIC c_std_99 cxx_std_17)
28 |
29 | target_link_libraries(${TARGET_NAME} PUBLIC
30 | rclcpp::rclcpp
31 | ${rclcpp_components_TARGETS}
32 | ${std_msgs_TARGETS}
33 | )
34 |
35 | install(TARGETS ${TARGET_NAME}
36 | ARCHIVE DESTINATION lib
37 | LIBRARY DESTINATION lib
38 | RUNTIME DESTINATION bin
39 | )
40 |
41 | install(DIRECTORY launch
42 | DESTINATION share/${PROJECT_NAME}
43 | OPTIONAL
44 | )
45 |
46 | install(DIRECTORY config
47 | DESTINATION share/${PROJECT_NAME}
48 | OPTIONAL
49 | )
50 |
51 | ament_package()
52 |
--------------------------------------------------------------------------------
/ros2-pkg-create/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools>=61.0.0", "wheel"]
3 | build-backend = "setuptools.build_meta"
4 |
5 | [project]
6 | name = "ros2-pkg-create"
7 | version = "1.2.0"
8 | description = "Powerful ROS 2 Package Generator"
9 | license = {file = "LICENSE"}
10 | readme = "README.md"
11 | authors = [
12 | {name = "Lennart Reiher", email = "lennart.reiher@rwth-aachen.de"},
13 | {name = "Jean-Pierre Busch", email = "jean-pierre.busch@rwth-aachen.de"},
14 | ]
15 | maintainers = [
16 | {name = "Lennart Reiher", email = "lennart.reiher@rwth-aachen.de"},
17 | {name = "Jean-Pierre Busch", email = "jean-pierre.busch@rwth-aachen.de"},
18 | ]
19 | classifiers = [
20 | "License :: OSI Approved :: MIT License",
21 | "Programming Language :: Python",
22 | "Programming Language :: Python :: 3",
23 | "Operating System :: POSIX :: Linux",
24 | ]
25 | keywords = ["ros", "ros2"]
26 | dependencies = ["argcomplete~=3.2.3", "copier~=9.2.0", "jinja2-strcase~=0.0.2"]
27 | requires-python = ">=3.7"
28 |
29 | [project.optional-dependencies]
30 | dev = ["build", "twine"]
31 |
32 | [project.urls]
33 | "Repository" = "https://github.com/ika-rwth-aachen/ros2-pkg-create"
34 | "Bug Tracker" = "https://github.com/ika-rwth-aachen/ros2-pkg-create/issues"
35 |
36 | [project.scripts]
37 | ros2-pkg-create = "ros2_pkg_create.__main__:main"
38 |
39 | [tool.setuptools.package-data]
40 | ros2_pkg_create = ["copier.yml", "templates/**"]
41 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_all_pkg/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.8)
2 | project(ros2_cpp_all_pkg)
3 |
4 | if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
5 | add_compile_options(-Wall -Wextra -Wpedantic)
6 | endif()
7 |
8 | find_package(ament_cmake REQUIRED)
9 | find_package(rclcpp REQUIRED)
10 | find_package(rclcpp_action REQUIRED)
11 | find_package(ros2_cpp_all_pkg_interfaces REQUIRED)
12 | find_package(rclcpp_components REQUIRED)
13 | find_package(rclcpp_lifecycle REQUIRED)
14 | find_package(std_msgs REQUIRED)
15 | find_package(std_srvs REQUIRED)
16 |
17 | set(TARGET_NAME ros2_cpp_node_component)
18 |
19 | add_library(${TARGET_NAME} SHARED src/ros2_cpp_node.cpp)
20 |
21 | rclcpp_components_register_node(${TARGET_NAME}
22 | PLUGIN "ros2_cpp_all_pkg::Ros2CppNode"
23 | EXECUTABLE ros2_cpp_node
24 | EXECUTOR StaticSingleThreadedExecutor
25 | )
26 |
27 | target_include_directories(${TARGET_NAME} PUBLIC
28 | $
29 | $
30 | )
31 | target_compile_features(${TARGET_NAME} PUBLIC c_std_99 cxx_std_17)
32 |
33 | target_link_libraries(${TARGET_NAME} PUBLIC
34 | rclcpp::rclcpp
35 | ${rclcpp_action_TARGETS}
36 | ${ros2_cpp_all_pkg_interfaces_TARGETS}
37 | ${rclcpp_components_TARGETS}
38 | ${rclcpp_lifecycle_TARGETS}
39 | ${std_msgs_TARGETS}
40 | ${std_srvs_TARGETS}
41 | )
42 |
43 | install(TARGETS ${TARGET_NAME}
44 | ARCHIVE DESTINATION lib
45 | LIBRARY DESTINATION lib
46 | RUNTIME DESTINATION bin
47 | )
48 |
49 | install(DIRECTORY launch
50 | DESTINATION share/${PROJECT_NAME}
51 | OPTIONAL
52 | )
53 |
54 | install(DIRECTORY config
55 | DESTINATION share/${PROJECT_NAME}
56 | OPTIONAL
57 | )
58 |
59 | ament_package()
60 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_pkg/launch/ros2_cpp_node_launch.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 |
5 | from ament_index_python import get_package_share_directory
6 | from launch import LaunchDescription
7 | from launch.actions import DeclareLaunchArgument
8 | from launch.substitutions import LaunchConfiguration
9 | from launch_ros.actions import Node, SetParameter
10 |
11 |
12 | def generate_launch_description():
13 |
14 | remappable_topics = [
15 | DeclareLaunchArgument("input_topic", default_value="~/input"),
16 | DeclareLaunchArgument("output_topic", default_value="~/output"),
17 | ]
18 |
19 | args = [
20 | DeclareLaunchArgument("name", default_value="ros2_cpp_node", description="node name"),
21 | DeclareLaunchArgument("namespace", default_value="", description="node namespace"),
22 | DeclareLaunchArgument("params", default_value=os.path.join(get_package_share_directory("ros2_cpp_pkg"), "config", "params.yml"), description="path to parameter file"),
23 | DeclareLaunchArgument("log_level", default_value="info", description="ROS logging level (debug, info, warn, error, fatal)"),
24 | DeclareLaunchArgument("use_sim_time", default_value="false", description="use simulation clock"),
25 | *remappable_topics,
26 | ]
27 |
28 | nodes = [
29 | Node(
30 | package="ros2_cpp_pkg",
31 | executable="ros2_cpp_node",
32 | namespace=LaunchConfiguration("namespace"),
33 | name=LaunchConfiguration("name"),
34 | parameters=[LaunchConfiguration("params")],
35 | arguments=["--ros-args", "--log-level", LaunchConfiguration("log_level")],
36 | remappings=[(la.default_value[0].text, LaunchConfiguration(la.name)) for la in remappable_topics],
37 | output="screen",
38 | emulate_tty=True,
39 | )
40 | ]
41 |
42 | return LaunchDescription([
43 | *args,
44 | SetParameter("use_sim_time", LaunchConfiguration("use_sim_time")),
45 | *nodes,
46 | ])
47 |
--------------------------------------------------------------------------------
/samples/ros2_python_pkg/launch/ros2_python_node_launch.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 |
5 | from ament_index_python import get_package_share_directory
6 | from launch import LaunchDescription
7 | from launch.actions import DeclareLaunchArgument
8 | from launch.substitutions import LaunchConfiguration
9 | from launch_ros.actions import Node, SetParameter
10 |
11 |
12 | def generate_launch_description():
13 |
14 | remappable_topics = [
15 | DeclareLaunchArgument("input_topic", default_value="~/input"),
16 | DeclareLaunchArgument("output_topic", default_value="~/output"),
17 | ]
18 |
19 | args = [
20 | DeclareLaunchArgument("name", default_value="ros2_python_node", description="node name"),
21 | DeclareLaunchArgument("namespace", default_value="", description="node namespace"),
22 | DeclareLaunchArgument("params", default_value=os.path.join(get_package_share_directory("ros2_python_pkg"), "config", "params.yml"), description="path to parameter file"),
23 | DeclareLaunchArgument("log_level", default_value="info", description="ROS logging level (debug, info, warn, error, fatal)"),
24 | DeclareLaunchArgument("use_sim_time", default_value="false", description="use simulation clock"),
25 | *remappable_topics,
26 | ]
27 |
28 | nodes = [
29 | Node(
30 | package="ros2_python_pkg",
31 | executable="ros2_python_node",
32 | namespace=LaunchConfiguration("namespace"),
33 | name=LaunchConfiguration("name"),
34 | parameters=[LaunchConfiguration("params")],
35 | arguments=["--ros-args", "--log-level", LaunchConfiguration("log_level")],
36 | remappings=[(la.default_value[0].text, LaunchConfiguration(la.name)) for la in remappable_topics],
37 | output="screen",
38 | emulate_tty=True,
39 | )
40 | ]
41 |
42 | return LaunchDescription([
43 | *args,
44 | SetParameter("use_sim_time", LaunchConfiguration("use_sim_time")),
45 | *nodes,
46 | ])
47 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_component_pkg/launch/ros2_cpp_node_launch.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 |
5 | from ament_index_python import get_package_share_directory
6 | from launch import LaunchDescription
7 | from launch.actions import DeclareLaunchArgument
8 | from launch.substitutions import LaunchConfiguration
9 | from launch_ros.actions import Node, SetParameter
10 |
11 |
12 | def generate_launch_description():
13 |
14 | remappable_topics = [
15 | DeclareLaunchArgument("input_topic", default_value="~/input"),
16 | DeclareLaunchArgument("output_topic", default_value="~/output"),
17 | ]
18 |
19 | args = [
20 | DeclareLaunchArgument("name", default_value="ros2_cpp_node", description="node name"),
21 | DeclareLaunchArgument("namespace", default_value="", description="node namespace"),
22 | DeclareLaunchArgument("params", default_value=os.path.join(get_package_share_directory("ros2_cpp_component_pkg"), "config", "params.yml"), description="path to parameter file"),
23 | DeclareLaunchArgument("log_level", default_value="info", description="ROS logging level (debug, info, warn, error, fatal)"),
24 | DeclareLaunchArgument("use_sim_time", default_value="false", description="use simulation clock"),
25 | *remappable_topics,
26 | ]
27 |
28 | nodes = [
29 | Node(
30 | package="ros2_cpp_component_pkg",
31 | executable="ros2_cpp_node",
32 | namespace=LaunchConfiguration("namespace"),
33 | name=LaunchConfiguration("name"),
34 | parameters=[LaunchConfiguration("params")],
35 | arguments=["--ros-args", "--log-level", LaunchConfiguration("log_level")],
36 | remappings=[(la.default_value[0].text, LaunchConfiguration(la.name)) for la in remappable_topics],
37 | output="screen",
38 | emulate_tty=True,
39 | )
40 | ]
41 |
42 | return LaunchDescription([
43 | *args,
44 | SetParameter("use_sim_time", LaunchConfiguration("use_sim_time")),
45 | *nodes,
46 | ])
47 |
--------------------------------------------------------------------------------
/samples/ros2_python_all_pkg/launch/ros2_python_node_launch.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 |
5 | from ament_index_python import get_package_share_directory
6 | from launch import LaunchDescription
7 | from launch.actions import DeclareLaunchArgument
8 | from launch.substitutions import LaunchConfiguration
9 | from launch_ros.actions import Node, SetParameter
10 |
11 |
12 | def generate_launch_description():
13 |
14 | remappable_topics = [
15 | DeclareLaunchArgument("input_topic", default_value="~/input"),
16 | DeclareLaunchArgument("output_topic", default_value="~/output"),
17 | ]
18 |
19 | args = [
20 | DeclareLaunchArgument("name", default_value="ros2_python_node", description="node name"),
21 | DeclareLaunchArgument("namespace", default_value="", description="node namespace"),
22 | DeclareLaunchArgument("params", default_value=os.path.join(get_package_share_directory("ros2_python_all_pkg"), "config", "params.yml"), description="path to parameter file"),
23 | DeclareLaunchArgument("log_level", default_value="info", description="ROS logging level (debug, info, warn, error, fatal)"),
24 | DeclareLaunchArgument("use_sim_time", default_value="false", description="use simulation clock"),
25 | *remappable_topics,
26 | ]
27 |
28 | nodes = [
29 | Node(
30 | package="ros2_python_all_pkg",
31 | executable="ros2_python_node",
32 | namespace=LaunchConfiguration("namespace"),
33 | name=LaunchConfiguration("name"),
34 | parameters=[LaunchConfiguration("params")],
35 | arguments=["--ros-args", "--log-level", LaunchConfiguration("log_level")],
36 | remappings=[(la.default_value[0].text, LaunchConfiguration(la.name)) for la in remappable_topics],
37 | output="screen",
38 | emulate_tty=True,
39 | )
40 | ]
41 |
42 | return LaunchDescription([
43 | *args,
44 | SetParameter("use_sim_time", LaunchConfiguration("use_sim_time")),
45 | *nodes,
46 | ])
47 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_multi_threaded_pkg/launch/ros2_cpp_node_launch.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 |
5 | from ament_index_python import get_package_share_directory
6 | from launch import LaunchDescription
7 | from launch.actions import DeclareLaunchArgument
8 | from launch.substitutions import LaunchConfiguration
9 | from launch_ros.actions import Node, SetParameter
10 |
11 |
12 | def generate_launch_description():
13 |
14 | remappable_topics = [
15 | DeclareLaunchArgument("input_topic", default_value="~/input"),
16 | DeclareLaunchArgument("output_topic", default_value="~/output"),
17 | ]
18 |
19 | args = [
20 | DeclareLaunchArgument("name", default_value="ros2_cpp_node", description="node name"),
21 | DeclareLaunchArgument("namespace", default_value="", description="node namespace"),
22 | DeclareLaunchArgument("params", default_value=os.path.join(get_package_share_directory("ros2_cpp_multi_threaded_pkg"), "config", "params.yml"), description="path to parameter file"),
23 | DeclareLaunchArgument("log_level", default_value="info", description="ROS logging level (debug, info, warn, error, fatal)"),
24 | DeclareLaunchArgument("num_threads", default_value="1", description="number of threads for MultiThreadedExecutor"),
25 | DeclareLaunchArgument("use_sim_time", default_value="false", description="use simulation clock"),
26 | *remappable_topics,
27 | ]
28 |
29 | nodes = [
30 | Node(
31 | package="ros2_cpp_multi_threaded_pkg",
32 | executable="ros2_cpp_node",
33 | namespace=LaunchConfiguration("namespace"),
34 | name=LaunchConfiguration("name"),
35 | parameters=[{"num_threads": LaunchConfiguration("num_threads")}, LaunchConfiguration("params")],
36 | arguments=["--ros-args", "--log-level", LaunchConfiguration("log_level")],
37 | remappings=[(la.default_value[0].text, LaunchConfiguration(la.name)) for la in remappable_topics],
38 | output="screen",
39 | emulate_tty=True,
40 | )
41 | ]
42 |
43 | return LaunchDescription([
44 | *args,
45 | SetParameter("use_sim_time", LaunchConfiguration("use_sim_time")),
46 | *nodes,
47 | ])
48 |
--------------------------------------------------------------------------------
/templates/ros2_python_pkg/{{ package_name }}/{% if has_launch_file %}launch{% endif %}/{% if launch_file_type == 'py' %}{{ node_name }}_launch.py{% endif %}.jinja:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 |
5 | from ament_index_python import get_package_share_directory
6 | from launch import LaunchDescription
7 | from launch.actions import DeclareLaunchArgument
8 | from launch.substitutions import LaunchConfiguration
9 | from launch_ros.actions import Node, SetParameter
10 |
11 |
12 | def generate_launch_description():
13 |
14 | remappable_topics = [
15 | {% if has_subscriber %}
16 | DeclareLaunchArgument("input_topic", default_value="~/input"),
17 | {% endif %}
18 | {% if has_publisher %}
19 | DeclareLaunchArgument("output_topic", default_value="~/output"),
20 | {% endif %}
21 | ]
22 |
23 | args = [
24 | DeclareLaunchArgument("name", default_value="{{ node_name }}", description="node name"),
25 | DeclareLaunchArgument("namespace", default_value="", description="node namespace"),
26 | DeclareLaunchArgument("params", default_value=os.path.join(get_package_share_directory("{{ package_name }}"), "config", "params.yml"), description="path to parameter file"),
27 | DeclareLaunchArgument("log_level", default_value="info", description="ROS logging level (debug, info, warn, error, fatal)"),
28 | DeclareLaunchArgument("use_sim_time", default_value="false", description="use simulation clock"),
29 | *remappable_topics,
30 | ]
31 |
32 | nodes = [
33 | Node(
34 | package="{{ package_name }}",
35 | executable="{{ node_name }}",
36 | namespace=LaunchConfiguration("namespace"),
37 | name=LaunchConfiguration("name"),
38 | {% if has_params %}
39 | parameters=[LaunchConfiguration("params")],
40 | {% else %}
41 | parameters=[],
42 | {% endif %}
43 | arguments=["--ros-args", "--log-level", LaunchConfiguration("log_level")],
44 | remappings=[(la.default_value[0].text, LaunchConfiguration(la.name)) for la in remappable_topics],
45 | output="screen",
46 | emulate_tty=True,
47 | )
48 | ]
49 |
50 | return LaunchDescription([
51 | *args,
52 | SetParameter("use_sim_time", LaunchConfiguration("use_sim_time")),
53 | *nodes,
54 | ])
55 |
--------------------------------------------------------------------------------
/templates/ros2_cpp_pkg/{{ package_name }}/CMakeLists.txt.jinja:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.8)
2 | project({{ package_name }})
3 |
4 | if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
5 | add_compile_options(-Wall -Wextra -Wpedantic)
6 | endif()
7 |
8 | find_package(ament_cmake REQUIRED)
9 | find_package(rclcpp REQUIRED)
10 | {% if has_action_server %}
11 | find_package(rclcpp_action REQUIRED)
12 | find_package({{ package_name }}_interfaces REQUIRED)
13 | {% endif %}
14 | {% if is_component %}
15 | find_package(rclcpp_components REQUIRED)
16 | {% endif %}
17 | {% if is_lifecycle %}
18 | find_package(rclcpp_lifecycle REQUIRED)
19 | {% endif %}
20 | find_package(std_msgs REQUIRED)
21 | {% if has_service_server %}
22 | find_package(std_srvs REQUIRED)
23 | {% endif %}
24 |
25 | {% if is_component %}
26 | set(TARGET_NAME {{ node_name }}_component)
27 | {% else %}
28 | set(TARGET_NAME {{ node_name }})
29 | {% endif %}
30 |
31 | {% if is_component %}
32 | add_library(${TARGET_NAME} SHARED src/{{ node_name }}.cpp)
33 |
34 | rclcpp_components_register_node(${TARGET_NAME}
35 | PLUGIN "{{ package_name }}::{{ node_class_name }}"
36 | EXECUTABLE {{ node_name }}
37 | EXECUTOR {{ executor_type }}
38 | )
39 | {% else %}
40 | add_executable(${TARGET_NAME} src/{{ node_name }}.cpp)
41 | {% endif %}
42 |
43 | target_include_directories(${TARGET_NAME} PUBLIC
44 | $
45 | $
46 | )
47 | target_compile_features(${TARGET_NAME} PUBLIC c_std_99 cxx_std_17)
48 |
49 | target_link_libraries(${TARGET_NAME} PUBLIC
50 | rclcpp::rclcpp
51 | {% if has_action_server %}
52 | ${rclcpp_action_TARGETS}
53 | {{ '${' ~ package_name ~ '_interfaces_TARGETS}' }}
54 | {% endif %}
55 | {% if is_component %}
56 | ${rclcpp_components_TARGETS}
57 | {% endif %}
58 | {% if is_lifecycle %}
59 | ${rclcpp_lifecycle_TARGETS}
60 | {% endif %}
61 | ${std_msgs_TARGETS}
62 | {% if has_service_server %}
63 | ${std_srvs_TARGETS}
64 | {% endif %}
65 | )
66 |
67 | {% if is_component %}
68 | install(TARGETS ${TARGET_NAME}
69 | ARCHIVE DESTINATION lib
70 | LIBRARY DESTINATION lib
71 | RUNTIME DESTINATION bin
72 | )
73 | {% else %}
74 | install(TARGETS ${TARGET_NAME}
75 | DESTINATION lib/${PROJECT_NAME}
76 | )
77 | {% endif %}
78 |
79 | install(DIRECTORY launch
80 | DESTINATION share/${PROJECT_NAME}
81 | OPTIONAL
82 | )
83 |
84 | install(DIRECTORY config
85 | DESTINATION share/${PROJECT_NAME}
86 | OPTIONAL
87 | )
88 |
89 | ament_package()
90 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_all_pkg/launch/ros2_cpp_node_launch.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 |
5 | from ament_index_python import get_package_share_directory
6 | from launch import LaunchDescription
7 | from launch.actions import DeclareLaunchArgument, GroupAction
8 | from launch.conditions import LaunchConfigurationNotEquals
9 | from launch.substitutions import LaunchConfiguration
10 | from launch_ros.actions import LifecycleNode, SetParameter
11 |
12 |
13 | def generate_launch_description():
14 |
15 | remappable_topics = [
16 | DeclareLaunchArgument("input_topic", default_value="~/input"),
17 | DeclareLaunchArgument("output_topic", default_value="~/output"),
18 | ]
19 |
20 | args = [
21 | DeclareLaunchArgument("name", default_value="ros2_cpp_node", description="node name"),
22 | DeclareLaunchArgument("namespace", default_value="", description="node namespace"),
23 | DeclareLaunchArgument("params", default_value=os.path.join(get_package_share_directory("ros2_cpp_all_pkg"), "config", "params.yml"), description="path to parameter file"),
24 | DeclareLaunchArgument("log_level", default_value="info", description="ROS logging level (debug, info, warn, error, fatal)"),
25 | DeclareLaunchArgument("startup_state", default_value="None", description="initial lifecycle state"),
26 | DeclareLaunchArgument("use_sim_time", default_value="false", description="use simulation clock"),
27 | *remappable_topics,
28 | ]
29 |
30 | nodes = [
31 | GroupAction(
32 | actions=[
33 | SetParameter(
34 | name="startup_state",
35 | value=LaunchConfiguration("startup_state"),
36 | condition=LaunchConfigurationNotEquals("startup_state", "None")
37 | ),
38 | LifecycleNode(
39 | package="ros2_cpp_all_pkg",
40 | executable="ros2_cpp_node",
41 | namespace=LaunchConfiguration("namespace"),
42 | name=LaunchConfiguration("name"),
43 | parameters=[LaunchConfiguration("params")],
44 | arguments=["--ros-args", "--log-level", LaunchConfiguration("log_level")],
45 | remappings=[(la.default_value[0].text, LaunchConfiguration(la.name)) for la in remappable_topics],
46 | output="screen",
47 | emulate_tty=True,
48 | )
49 | ]
50 | )
51 | ]
52 |
53 | return LaunchDescription([
54 | *args,
55 | SetParameter("use_sim_time", LaunchConfiguration("use_sim_time")),
56 | *nodes,
57 | ])
58 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_lifecycle_pkg/launch/ros2_cpp_node_launch.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 |
5 | from ament_index_python import get_package_share_directory
6 | from launch import LaunchDescription
7 | from launch.actions import DeclareLaunchArgument, GroupAction
8 | from launch.conditions import LaunchConfigurationNotEquals
9 | from launch.substitutions import LaunchConfiguration
10 | from launch_ros.actions import LifecycleNode, SetParameter
11 |
12 |
13 | def generate_launch_description():
14 |
15 | remappable_topics = [
16 | DeclareLaunchArgument("input_topic", default_value="~/input"),
17 | DeclareLaunchArgument("output_topic", default_value="~/output"),
18 | ]
19 |
20 | args = [
21 | DeclareLaunchArgument("name", default_value="ros2_cpp_node", description="node name"),
22 | DeclareLaunchArgument("namespace", default_value="", description="node namespace"),
23 | DeclareLaunchArgument("params", default_value=os.path.join(get_package_share_directory("ros2_cpp_lifecycle_pkg"), "config", "params.yml"), description="path to parameter file"),
24 | DeclareLaunchArgument("log_level", default_value="info", description="ROS logging level (debug, info, warn, error, fatal)"),
25 | DeclareLaunchArgument("startup_state", default_value="None", description="initial lifecycle state"),
26 | DeclareLaunchArgument("use_sim_time", default_value="false", description="use simulation clock"),
27 | *remappable_topics,
28 | ]
29 |
30 | nodes = [
31 | GroupAction(
32 | actions=[
33 | SetParameter(
34 | name="startup_state",
35 | value=LaunchConfiguration("startup_state"),
36 | condition=LaunchConfigurationNotEquals("startup_state", "None")
37 | ),
38 | LifecycleNode(
39 | package="ros2_cpp_lifecycle_pkg",
40 | executable="ros2_cpp_node",
41 | namespace=LaunchConfiguration("namespace"),
42 | name=LaunchConfiguration("name"),
43 | parameters=[LaunchConfiguration("params")],
44 | arguments=["--ros-args", "--log-level", LaunchConfiguration("log_level")],
45 | remappings=[(la.default_value[0].text, LaunchConfiguration(la.name)) for la in remappable_topics],
46 | output="screen",
47 | emulate_tty=True,
48 | )
49 | ]
50 | )
51 | ]
52 |
53 | return LaunchDescription([
54 | *args,
55 | SetParameter("use_sim_time", LaunchConfiguration("use_sim_time")),
56 | *nodes,
57 | ])
58 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_pkg/include/ros2_cpp_pkg/ros2_cpp_node.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | #include
8 | #include
9 |
10 |
11 | namespace ros2_cpp_pkg {
12 |
13 | template struct is_vector : std::false_type {};
14 | template struct is_vector< std::vector > : std::true_type {};
15 | template inline constexpr bool is_vector_v = is_vector::value;
16 |
17 |
18 | /**
19 | * @brief Ros2CppNode class
20 | */
21 | class Ros2CppNode : public rclcpp::Node {
22 |
23 | public:
24 |
25 | Ros2CppNode();
26 |
27 | private:
28 |
29 | /**
30 | * @brief Declares and loads a ROS parameter
31 | *
32 | * @param name name
33 | * @param param parameter variable to load into
34 | * @param description description
35 | * @param add_to_auto_reconfigurable_params enable reconfiguration of parameter
36 | * @param is_required whether failure to load parameter will stop node
37 | * @param read_only set parameter to read-only
38 | * @param from_value parameter range minimum
39 | * @param to_value parameter range maximum
40 | * @param step_value parameter range step
41 | * @param additional_constraints additional constraints description
42 | */
43 | template
44 | void declareAndLoadParameter(const std::string &name,
45 | T ¶m,
46 | const std::string &description,
47 | const bool add_to_auto_reconfigurable_params = true,
48 | const bool is_required = false,
49 | const bool read_only = false,
50 | const std::optional &from_value = std::nullopt,
51 | const std::optional &to_value = std::nullopt,
52 | const std::optional &step_value = std::nullopt,
53 | const std::string &additional_constraints = "");
54 |
55 | /**
56 | * @brief Handles reconfiguration when a parameter value is changed
57 | *
58 | * @param parameters parameters
59 | * @return parameter change result
60 | */
61 | rcl_interfaces::msg::SetParametersResult parametersCallback(const std::vector& parameters);
62 |
63 | /**
64 | * @brief Sets up subscribers, publishers, etc. to configure the node
65 | */
66 | void setup();
67 |
68 | /**
69 | * @brief Processes messages received by a subscriber
70 | *
71 | * @param msg message
72 | */
73 | void topicCallback(const std_msgs::msg::Int32::ConstSharedPtr& msg);
74 |
75 | private:
76 |
77 | /**
78 | * @brief Auto-reconfigurable parameters for dynamic reconfiguration
79 | */
80 | std::vector>> auto_reconfigurable_params_;
81 |
82 | /**
83 | * @brief Callback handle for dynamic parameter reconfiguration
84 | */
85 | OnSetParametersCallbackHandle::SharedPtr parameters_callback_;
86 |
87 | /**
88 | * @brief Subscriber
89 | */
90 | rclcpp::Subscription::SharedPtr subscriber_;
91 |
92 | /**
93 | * @brief Publisher
94 | */
95 | rclcpp::Publisher::SharedPtr publisher_;
96 |
97 | /**
98 | * @brief Dummy parameter (parameter)
99 | */
100 | double param_ = 1.0;
101 | };
102 |
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_multi_threaded_pkg/include/ros2_cpp_multi_threaded_pkg/ros2_cpp_node.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | #include
8 | #include
9 |
10 |
11 | namespace ros2_cpp_multi_threaded_pkg {
12 |
13 | template struct is_vector : std::false_type {};
14 | template struct is_vector< std::vector > : std::true_type {};
15 | template inline constexpr bool is_vector_v = is_vector::value;
16 |
17 |
18 | /**
19 | * @brief Ros2CppNode class
20 | */
21 | class Ros2CppNode : public rclcpp::Node {
22 |
23 | public:
24 |
25 | Ros2CppNode();
26 |
27 | /**
28 | * @brief Number of threads for MultiThreadedExecutor
29 | */
30 | int num_threads_ = 1;
31 |
32 | private:
33 |
34 | /**
35 | * @brief Declares and loads a ROS parameter
36 | *
37 | * @param name name
38 | * @param param parameter variable to load into
39 | * @param description description
40 | * @param add_to_auto_reconfigurable_params enable reconfiguration of parameter
41 | * @param is_required whether failure to load parameter will stop node
42 | * @param read_only set parameter to read-only
43 | * @param from_value parameter range minimum
44 | * @param to_value parameter range maximum
45 | * @param step_value parameter range step
46 | * @param additional_constraints additional constraints description
47 | */
48 | template
49 | void declareAndLoadParameter(const std::string &name,
50 | T ¶m,
51 | const std::string &description,
52 | const bool add_to_auto_reconfigurable_params = true,
53 | const bool is_required = false,
54 | const bool read_only = false,
55 | const std::optional &from_value = std::nullopt,
56 | const std::optional &to_value = std::nullopt,
57 | const std::optional &step_value = std::nullopt,
58 | const std::string &additional_constraints = "");
59 |
60 | /**
61 | * @brief Handles reconfiguration when a parameter value is changed
62 | *
63 | * @param parameters parameters
64 | * @return parameter change result
65 | */
66 | rcl_interfaces::msg::SetParametersResult parametersCallback(const std::vector& parameters);
67 |
68 | /**
69 | * @brief Sets up subscribers, publishers, etc. to configure the node
70 | */
71 | void setup();
72 |
73 | /**
74 | * @brief Processes messages received by a subscriber
75 | *
76 | * @param msg message
77 | */
78 | void topicCallback(const std_msgs::msg::Int32::ConstSharedPtr& msg);
79 |
80 | private:
81 |
82 | /**
83 | * @brief Auto-reconfigurable parameters for dynamic reconfiguration
84 | */
85 | std::vector>> auto_reconfigurable_params_;
86 |
87 | /**
88 | * @brief Callback handle for dynamic parameter reconfiguration
89 | */
90 | OnSetParametersCallbackHandle::SharedPtr parameters_callback_;
91 |
92 | /**
93 | * @brief Subscriber
94 | */
95 | rclcpp::Subscription::SharedPtr subscriber_;
96 |
97 | /**
98 | * @brief Publisher
99 | */
100 | rclcpp::Publisher::SharedPtr publisher_;
101 |
102 | /**
103 | * @brief Dummy parameter (parameter)
104 | */
105 | double param_ = 1.0;
106 | };
107 |
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_component_pkg/include/ros2_cpp_component_pkg/ros2_cpp_node.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | #include
8 | #include
9 |
10 |
11 | namespace ros2_cpp_component_pkg {
12 |
13 | template struct is_vector : std::false_type {};
14 | template struct is_vector< std::vector > : std::true_type {};
15 | template inline constexpr bool is_vector_v = is_vector::value;
16 |
17 |
18 | /**
19 | * @brief Ros2CppNode class
20 | */
21 | class Ros2CppNode : public rclcpp::Node {
22 |
23 | public:
24 |
25 | /**
26 | * @brief Constructor
27 | *
28 | * @param options node options
29 | */
30 | explicit Ros2CppNode(const rclcpp::NodeOptions& options);
31 |
32 | private:
33 |
34 | /**
35 | * @brief Declares and loads a ROS parameter
36 | *
37 | * @param name name
38 | * @param param parameter variable to load into
39 | * @param description description
40 | * @param add_to_auto_reconfigurable_params enable reconfiguration of parameter
41 | * @param is_required whether failure to load parameter will stop node
42 | * @param read_only set parameter to read-only
43 | * @param from_value parameter range minimum
44 | * @param to_value parameter range maximum
45 | * @param step_value parameter range step
46 | * @param additional_constraints additional constraints description
47 | */
48 | template
49 | void declareAndLoadParameter(const std::string &name,
50 | T ¶m,
51 | const std::string &description,
52 | const bool add_to_auto_reconfigurable_params = true,
53 | const bool is_required = false,
54 | const bool read_only = false,
55 | const std::optional &from_value = std::nullopt,
56 | const std::optional &to_value = std::nullopt,
57 | const std::optional &step_value = std::nullopt,
58 | const std::string &additional_constraints = "");
59 |
60 | /**
61 | * @brief Handles reconfiguration when a parameter value is changed
62 | *
63 | * @param parameters parameters
64 | * @return parameter change result
65 | */
66 | rcl_interfaces::msg::SetParametersResult parametersCallback(const std::vector& parameters);
67 |
68 | /**
69 | * @brief Sets up subscribers, publishers, etc. to configure the node
70 | */
71 | void setup();
72 |
73 | /**
74 | * @brief Processes messages received by a subscriber
75 | *
76 | * @param msg message
77 | */
78 | void topicCallback(const std_msgs::msg::Int32::ConstSharedPtr& msg);
79 |
80 | private:
81 |
82 | /**
83 | * @brief Auto-reconfigurable parameters for dynamic reconfiguration
84 | */
85 | std::vector>> auto_reconfigurable_params_;
86 |
87 | /**
88 | * @brief Callback handle for dynamic parameter reconfiguration
89 | */
90 | OnSetParametersCallbackHandle::SharedPtr parameters_callback_;
91 |
92 | /**
93 | * @brief Subscriber
94 | */
95 | rclcpp::Subscription::SharedPtr subscriber_;
96 |
97 | /**
98 | * @brief Publisher
99 | */
100 | rclcpp::Publisher::SharedPtr publisher_;
101 |
102 | /**
103 | * @brief Dummy parameter (parameter)
104 | */
105 | double param_ = 1.0;
106 | };
107 |
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/python
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python
3 |
4 | ### Python ###
5 | # Byte-compiled / optimized / DLL files
6 | __pycache__/
7 | *.py[cod]
8 | *$py.class
9 |
10 | # C extensions
11 | *.so
12 |
13 | # Distribution / packaging
14 | .Python
15 | build/
16 | develop-eggs/
17 | dist/
18 | downloads/
19 | eggs/
20 | .eggs/
21 | lib/
22 | lib64/
23 | parts/
24 | sdist/
25 | var/
26 | wheels/
27 | share/python-wheels/
28 | *.egg-info/
29 | .installed.cfg
30 | *.egg
31 | MANIFEST
32 |
33 | # PyInstaller
34 | # Usually these files are written by a python script from a template
35 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
36 | *.manifest
37 | *.spec
38 |
39 | # Installer logs
40 | pip-log.txt
41 | pip-delete-this-directory.txt
42 |
43 | # Unit test / coverage reports
44 | htmlcov/
45 | .tox/
46 | .nox/
47 | .coverage
48 | .coverage.*
49 | .cache
50 | nosetests.xml
51 | coverage.xml
52 | *.cover
53 | *.py,cover
54 | .hypothesis/
55 | .pytest_cache/
56 | cover/
57 |
58 | # Translations
59 | *.mo
60 | *.pot
61 |
62 | # Django stuff:
63 | *.log
64 | local_settings.py
65 | db.sqlite3
66 | db.sqlite3-journal
67 |
68 | # Flask stuff:
69 | instance/
70 | .webassets-cache
71 |
72 | # Scrapy stuff:
73 | .scrapy
74 |
75 | # Sphinx documentation
76 | docs/_build/
77 |
78 | # PyBuilder
79 | .pybuilder/
80 | target/
81 |
82 | # Jupyter Notebook
83 | .ipynb_checkpoints
84 |
85 | # IPython
86 | profile_default/
87 | ipython_config.py
88 |
89 | # pyenv
90 | # For a library or package, you might want to ignore these files since the code is
91 | # intended to run in multiple environments; otherwise, check them in:
92 | # .python-version
93 |
94 | # pipenv
95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
98 | # install all needed dependencies.
99 | #Pipfile.lock
100 |
101 | # poetry
102 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
103 | # This is especially recommended for binary packages to ensure reproducibility, and is more
104 | # commonly ignored for libraries.
105 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
106 | #poetry.lock
107 |
108 | # pdm
109 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
110 | #pdm.lock
111 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
112 | # in version control.
113 | # https://pdm.fming.dev/#use-with-ide
114 | .pdm.toml
115 |
116 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
117 | __pypackages__/
118 |
119 | # Celery stuff
120 | celerybeat-schedule
121 | celerybeat.pid
122 |
123 | # SageMath parsed files
124 | *.sage.py
125 |
126 | # Environments
127 | .env
128 | .venv
129 | env/
130 | venv/
131 | ENV/
132 | env.bak/
133 | venv.bak/
134 |
135 | # Spyder project settings
136 | .spyderproject
137 | .spyproject
138 |
139 | # Rope project settings
140 | .ropeproject
141 |
142 | # mkdocs documentation
143 | /site
144 |
145 | # mypy
146 | .mypy_cache/
147 | .dmypy.json
148 | dmypy.json
149 |
150 | # Pyre type checker
151 | .pyre/
152 |
153 | # pytype static type analyzer
154 | .pytype/
155 |
156 | # Cython debug symbols
157 | cython_debug/
158 |
159 | # PyCharm
160 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
161 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
162 | # and can be added to the global gitignore or merged into this file. For a more nuclear
163 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
164 | #.idea/
165 |
166 | ### Python Patch ###
167 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
168 | poetry.toml
169 |
170 | # ruff
171 | .ruff_cache/
172 |
173 | # LSP config files
174 | pyrightconfig.json
175 |
176 | # End of https://www.toptal.com/developers/gitignore/api/python
177 |
--------------------------------------------------------------------------------
/templates/ros2_cpp_pkg/{{ package_name }}/{% if has_launch_file %}launch{% endif %}/{% if launch_file_type == 'py' %}{{ node_name }}_launch.py{% endif %}.jinja:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 |
5 | from ament_index_python import get_package_share_directory
6 | from launch import LaunchDescription
7 | {% if is_lifecycle %}
8 | from launch.actions import DeclareLaunchArgument, GroupAction
9 | from launch.conditions import LaunchConfigurationNotEquals
10 | from launch.substitutions import LaunchConfiguration
11 | from launch_ros.actions import LifecycleNode, SetParameter
12 | {% else %}
13 | from launch.actions import DeclareLaunchArgument
14 | from launch.substitutions import LaunchConfiguration
15 | from launch_ros.actions import Node, SetParameter
16 | {% endif %}
17 |
18 |
19 | def generate_launch_description():
20 |
21 | remappable_topics = [
22 | {% if has_subscriber %}
23 | DeclareLaunchArgument("input_topic", default_value="~/input"),
24 | {% endif %}
25 | {% if has_publisher %}
26 | DeclareLaunchArgument("output_topic", default_value="~/output"),
27 | {% endif %}
28 | ]
29 |
30 | args = [
31 | DeclareLaunchArgument("name", default_value="{{ node_name }}", description="node name"),
32 | DeclareLaunchArgument("namespace", default_value="", description="node namespace"),
33 | DeclareLaunchArgument("params", default_value=os.path.join(get_package_share_directory("{{ package_name }}"), "config", "params.yml"), description="path to parameter file"),
34 | DeclareLaunchArgument("log_level", default_value="info", description="ROS logging level (debug, info, warn, error, fatal)"),
35 | {% if not is_component and executor_type == "MultiThreadedExecutor" %}
36 | DeclareLaunchArgument("num_threads", default_value="1", description="number of threads for MultiThreadedExecutor"),
37 | {% endif %}
38 | {% if is_lifecycle %}
39 | DeclareLaunchArgument("startup_state", default_value="None", description="initial lifecycle state"),
40 | {% endif %}
41 | DeclareLaunchArgument("use_sim_time", default_value="false", description="use simulation clock"),
42 | *remappable_topics,
43 | ]
44 |
45 | {% if is_lifecycle %}
46 | nodes = [
47 | GroupAction(
48 | actions=[
49 | SetParameter(
50 | name="startup_state",
51 | value=LaunchConfiguration("startup_state"),
52 | condition=LaunchConfigurationNotEquals("startup_state", "None")
53 | ),
54 | LifecycleNode(
55 | package="{{ package_name }}",
56 | executable="{{ node_name }}",
57 | namespace=LaunchConfiguration("namespace"),
58 | name=LaunchConfiguration("name"),
59 | {% if not is_component and executor_type == "MultiThreadedExecutor" %}
60 | {% if has_params %}
61 | parameters=[{"num_threads": LaunchConfiguration("num_threads")}, LaunchConfiguration("params")],
62 | {% else %}
63 | parameters=[{"num_threads": LaunchConfiguration("num_threads")}],
64 | {% endif %}
65 | {% else %}
66 | {% if has_params %}
67 | parameters=[LaunchConfiguration("params")],
68 | {% else %}
69 | parameters=[],
70 | {% endif %}
71 | {% endif %}
72 | arguments=["--ros-args", "--log-level", LaunchConfiguration("log_level")],
73 | remappings=[(la.default_value[0].text, LaunchConfiguration(la.name)) for la in remappable_topics],
74 | output="screen",
75 | emulate_tty=True,
76 | )
77 | ]
78 | )
79 | ]
80 | {% else %}
81 | nodes = [
82 | Node(
83 | package="{{ package_name }}",
84 | executable="{{ node_name }}",
85 | namespace=LaunchConfiguration("namespace"),
86 | name=LaunchConfiguration("name"),
87 | {% if not is_component and executor_type == "MultiThreadedExecutor" %}
88 | {% if has_params %}
89 | parameters=[{"num_threads": LaunchConfiguration("num_threads")}, LaunchConfiguration("params")],
90 | {% else %}
91 | parameters=[{"num_threads": LaunchConfiguration("num_threads")}],
92 | {% endif %}
93 | {% else %}
94 | {% if has_params %}
95 | parameters=[LaunchConfiguration("params")],
96 | {% else %}
97 | parameters=[],
98 | {% endif %}
99 | {% endif %}
100 | arguments=["--ros-args", "--log-level", LaunchConfiguration("log_level")],
101 | remappings=[(la.default_value[0].text, LaunchConfiguration(la.name)) for la in remappable_topics],
102 | output="screen",
103 | emulate_tty=True,
104 | )
105 | ]
106 | {% endif %}
107 |
108 | return LaunchDescription([
109 | *args,
110 | SetParameter("use_sim_time", LaunchConfiguration("use_sim_time")),
111 | *nodes,
112 | ])
113 |
--------------------------------------------------------------------------------
/copier.yml:
--------------------------------------------------------------------------------
1 | _subdirectory: "templates/{{ template }}"
2 |
3 | _envops:
4 | lstrip_blocks: true
5 | trim_blocks: true
6 |
7 | _jinja_extensions:
8 | - jinja2_strcase.StrcaseExtension
9 |
10 |
11 | template:
12 | help: Template
13 | type: str
14 | default: ros2_cpp_pkg
15 | choices: [ros2_cpp_pkg, ros2_interfaces_pkg, ros2_python_pkg]
16 |
17 | package_name:
18 | help: Package name
19 | type: str
20 | placeholder: "{{ template }}"
21 | validator: "{% if not package_name %}Package name is required{% endif %}"
22 |
23 | description:
24 | help: Description
25 | type: str
26 | default: TODO
27 |
28 | maintainer:
29 | help: Maintainer
30 | type: str
31 | default: TODO
32 |
33 | maintainer_email:
34 | help: Maintainer email
35 | type: str
36 | default: "{{ maintainer | lower | replace(' ', '.') }}@TODO.com"
37 |
38 | author:
39 | help: Author
40 | type: str
41 | default: "{{ maintainer }}"
42 |
43 | author_email:
44 | help: Author email
45 | type: str
46 | default: "{{ author | lower | replace(' ', '.') }}@TODO.com"
47 |
48 | license:
49 | help: License
50 | type: str
51 | default: TODO
52 | choices: [TODO, Apache-2.0, BSL-1.0, BSD-2.0, BSD-2-Clause, BSD-3-Clause, GPL-3.0-only, LGPL-2.1-only, LGPL-3.0-only, MIT, MIT-0]
53 |
54 | node_name:
55 | help: Node name
56 | when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
57 | type: str
58 | default: "{{ package_name }}"
59 |
60 | node_class_name:
61 | help: Class name of node
62 | when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
63 | type: str
64 | default: "{{ node_name | to_camel }}"
65 |
66 | is_component:
67 | help: Make it a component?
68 | when: "{{ template == 'ros2_cpp_pkg' }}"
69 | type: bool
70 | default: false
71 |
72 | is_lifecycle:
73 | help: Make it a lifecycle node?
74 | when: "{{ template == 'ros2_cpp_pkg' }}"
75 | type: bool
76 | default: false
77 |
78 | executor_type:
79 | help: Type of Executor
80 | when: "{{ template == 'ros2_cpp_pkg' }}"
81 | type: str
82 | default: SingleThreadedExecutor
83 | choices: [SingleThreadedExecutor, MultiThreadedExecutor, StaticSingleThreadedExecutor]
84 |
85 | has_launch_file:
86 | help: Add a launch file?
87 | when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
88 | type: bool
89 | default: true
90 |
91 | launch_file_type:
92 | help: Type of launch file
93 | when: "{{ (template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg') and has_launch_file }}"
94 | type: str
95 | default: py
96 | choices: [py, xml, yml]
97 |
98 | has_params:
99 | help: Add parameter loading?
100 | when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
101 | type: bool
102 | default: true
103 |
104 | has_subscriber:
105 | help: Add a subscriber?
106 | when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
107 | type: bool
108 | default: true
109 |
110 | has_publisher:
111 | help: Add a publisher?
112 | when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
113 | type: bool
114 | default: true
115 |
116 | has_service_server:
117 | help: Add a service server?
118 | when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
119 | type: bool
120 | default: false
121 |
122 | has_action_server:
123 | help: Add an action server?
124 | when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
125 | type: bool
126 | default: false
127 |
128 | has_timer:
129 | help: Add a timer callback?
130 | when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
131 | type: bool
132 | default: false
133 |
134 | auto_shutdown:
135 | help: Automatically shutdown the node after launch (useful in CI/CD)?
136 | when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
137 | when: false
138 | type: bool
139 | default: false
140 |
141 | interface_types:
142 | help: Interfaces types
143 | when: "{{ template == 'ros2_interfaces_pkg' }}"
144 | type: str
145 | default: [msg, srv, action]
146 | choices:
147 | Message: msg
148 | Service: srv
149 | Action: action
150 | multiselect: true
151 |
152 | msg_name:
153 | help: Message name
154 | when: "{{ template == 'ros2_interfaces_pkg' and 'msg' in interface_types }}"
155 | type: str
156 | default: Message
157 |
158 | srv_name:
159 | help: Service name
160 | when: "{{ template == 'ros2_interfaces_pkg' and 'srv' in interface_types }}"
161 | type: str
162 | default: Service
163 |
164 | action_name:
165 | help: Action name
166 | when: "{{ template == 'ros2_interfaces_pkg' and 'action' in interface_types }}"
167 | type: str
168 | default: Action
169 |
170 | has_docker_ros:
171 | help: Add the docker-ros CI integration?
172 | type: bool
173 | default: false
174 |
175 | docker_ros_type:
176 | help: Type of docker-ros CI integration
177 | when: "{{ has_docker_ros }}"
178 | type: str
179 | default: gitlab
180 | choices: [github, gitlab]
181 |
--------------------------------------------------------------------------------
/samples/generate_samples.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | script_dir=$(dirname $0)
4 | template_dir="$script_dir/.."
5 |
6 | copier copy --trust --defaults --overwrite --vcs-ref=HEAD \
7 | -d template=ros2_cpp_pkg \
8 | -d package_name=ros2_cpp_pkg \
9 | -d node_name=ros2_cpp_node \
10 | -d is_component=false \
11 | -d is_lifecycle=false \
12 | -d executor_type=SingleThreadedExecutor \
13 | -d has_launch_file=true \
14 | -d has_params=true \
15 | -d has_subscriber=true \
16 | -d has_publisher=true \
17 | -d has_service_server=false \
18 | -d has_action_server=false \
19 | -d has_timer=false \
20 | -d has_docker_ros=true \
21 | $template_dir $script_dir
22 |
23 | copier copy --trust --defaults --overwrite --vcs-ref=HEAD \
24 | -d template=ros2_cpp_pkg \
25 | -d package_name=ros2_cpp_component_pkg \
26 | -d node_name=ros2_cpp_node \
27 | -d is_component=true \
28 | -d is_lifecycle=false \
29 | -d executor_type=SingleThreadedExecutor \
30 | -d has_launch_file=true \
31 | -d has_params=true \
32 | -d has_subscriber=true \
33 | -d has_publisher=true \
34 | -d has_service_server=false \
35 | -d has_action_server=false \
36 | -d has_timer=false \
37 | -d has_docker_ros=true \
38 | $template_dir $script_dir
39 |
40 | copier copy --trust --defaults --overwrite --vcs-ref=HEAD \
41 | -d template=ros2_cpp_pkg \
42 | -d package_name=ros2_cpp_lifecycle_pkg \
43 | -d node_name=ros2_cpp_node \
44 | -d is_component=false \
45 | -d is_lifecycle=true \
46 | -d executor_type=SingleThreadedExecutor \
47 | -d has_launch_file=true \
48 | -d has_params=true \
49 | -d has_subscriber=true \
50 | -d has_publisher=true \
51 | -d has_service_server=false \
52 | -d has_action_server=false \
53 | -d has_timer=false \
54 | -d has_docker_ros=true \
55 | $template_dir $script_dir
56 |
57 | copier copy --trust --defaults --overwrite --vcs-ref=HEAD \
58 | -d template=ros2_cpp_pkg \
59 | -d package_name=ros2_cpp_multi_threaded_pkg \
60 | -d node_name=ros2_cpp_node \
61 | -d is_component=false \
62 | -d is_lifecycle=false \
63 | -d executor_type=MultiThreadedExecutor \
64 | -d has_launch_file=true \
65 | -d has_params=true \
66 | -d has_subscriber=true \
67 | -d has_publisher=true \
68 | -d has_service_server=false \
69 | -d has_action_server=false \
70 | -d has_timer=false \
71 | -d has_docker_ros=true \
72 | $template_dir $script_dir
73 |
74 | copier copy --trust --defaults --overwrite --vcs-ref=HEAD \
75 | -d template=ros2_cpp_pkg \
76 | -d package_name=ros2_cpp_all_pkg \
77 | -d node_name=ros2_cpp_node \
78 | -d is_component=true \
79 | -d is_lifecycle=true \
80 | -d executor_type=StaticSingleThreadedExecutor \
81 | -d has_launch_file=true \
82 | -d has_params=true \
83 | -d has_subscriber=true \
84 | -d has_publisher=true \
85 | -d has_service_server=true \
86 | -d has_action_server=true \
87 | -d has_timer=true \
88 | -d has_docker_ros=true \
89 | $template_dir $script_dir
90 |
91 | copier copy --trust --defaults --overwrite --vcs-ref=HEAD \
92 | -d template=ros2_python_pkg \
93 | -d package_name=ros2_python_pkg \
94 | -d node_name=ros2_python_node \
95 | -d has_launch_file=true \
96 | -d has_params=true \
97 | -d has_subscriber=true \
98 | -d has_publisher=true \
99 | -d has_service_server=false \
100 | -d has_action_server=false \
101 | -d has_timer=false \
102 | -d has_docker_ros=true \
103 | $template_dir $script_dir
104 |
105 | copier copy --trust --defaults --overwrite --vcs-ref=HEAD \
106 | -d template=ros2_python_pkg \
107 | -d package_name=ros2_python_all_pkg \
108 | -d node_name=ros2_python_node \
109 | -d has_launch_file=true \
110 | -d has_params=true \
111 | -d has_subscriber=true \
112 | -d has_publisher=true \
113 | -d has_service_server=true \
114 | -d has_action_server=true \
115 | -d has_timer=true \
116 | -d has_docker_ros=true \
117 | $template_dir $script_dir
118 |
119 | copier copy --trust --defaults --overwrite --vcs-ref=HEAD \
120 | -d template=ros2_interfaces_pkg \
121 | -d package_name=ros2_interfaces_pkg \
122 | $template_dir $script_dir
123 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_lifecycle_pkg/include/ros2_cpp_lifecycle_pkg/ros2_cpp_node.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 |
14 |
15 | namespace ros2_cpp_lifecycle_pkg {
16 |
17 | template struct is_vector : std::false_type {};
18 | template struct is_vector< std::vector > : std::true_type {};
19 | template inline constexpr bool is_vector_v = is_vector::value;
20 |
21 |
22 | /**
23 | * @brief Ros2CppNode class
24 | */
25 | class Ros2CppNode : public rclcpp_lifecycle::LifecycleNode {
26 |
27 | public:
28 |
29 | Ros2CppNode();
30 |
31 | protected:
32 |
33 | /**
34 | * @brief Processes 'configuring' transitions to 'inactive' state
35 | *
36 | * @param state previous state
37 | * @return transition result
38 | */
39 | rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_configure(const rclcpp_lifecycle::State& state) override;
40 |
41 | /**
42 | * @brief Processes 'activating' transitions to 'active' state
43 | *
44 | * @param state previous state
45 | * @return transition result
46 | */
47 | rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_activate(const rclcpp_lifecycle::State& state) override;
48 |
49 | /**
50 | * @brief Processes 'deactivating' transitions to 'inactive' state
51 | *
52 | * @param state previous state
53 | * @return transition result
54 | */
55 | rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_deactivate(const rclcpp_lifecycle::State& state) override;
56 |
57 | /**
58 | * @brief Processes 'cleaningup' transitions to 'unconfigured' state
59 | *
60 | * @param state previous state
61 | * @return transition result
62 | */
63 | rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_cleanup(const rclcpp_lifecycle::State& state) override;
64 |
65 | /**
66 | * @brief Processes 'shuttingdown' transitions to 'finalized' state
67 | *
68 | * @param state previous state
69 | * @return transition result
70 | */
71 | rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_shutdown(const rclcpp_lifecycle::State& state) override;
72 |
73 | private:
74 |
75 | /**
76 | * @brief Declares and loads a ROS parameter
77 | *
78 | * @param name name
79 | * @param param parameter variable to load into
80 | * @param description description
81 | * @param add_to_auto_reconfigurable_params enable reconfiguration of parameter
82 | * @param is_required whether failure to load parameter will stop node
83 | * @param read_only set parameter to read-only
84 | * @param from_value parameter range minimum
85 | * @param to_value parameter range maximum
86 | * @param step_value parameter range step
87 | * @param additional_constraints additional constraints description
88 | */
89 | template
90 | void declareAndLoadParameter(const std::string &name,
91 | T ¶m,
92 | const std::string &description,
93 | const bool add_to_auto_reconfigurable_params = true,
94 | const bool is_required = false,
95 | const bool read_only = false,
96 | const std::optional &from_value = std::nullopt,
97 | const std::optional &to_value = std::nullopt,
98 | const std::optional &step_value = std::nullopt,
99 | const std::string &additional_constraints = "");
100 |
101 | /**
102 | * @brief Handles reconfiguration when a parameter value is changed
103 | *
104 | * @param parameters parameters
105 | * @return parameter change result
106 | */
107 | rcl_interfaces::msg::SetParametersResult parametersCallback(const std::vector& parameters);
108 |
109 | /**
110 | * @brief Sets up subscribers, publishers, etc. to configure the node
111 | */
112 | void setup();
113 |
114 | /**
115 | * @brief Processes messages received by a subscriber
116 | *
117 | * @param msg message
118 | */
119 | void topicCallback(const std_msgs::msg::Int32::ConstSharedPtr& msg);
120 |
121 | private:
122 |
123 | /**
124 | * @brief Auto-reconfigurable parameters for dynamic reconfiguration
125 | */
126 | std::vector>> auto_reconfigurable_params_;
127 |
128 | /**
129 | * @brief Callback handle for dynamic parameter reconfiguration
130 | */
131 | OnSetParametersCallbackHandle::SharedPtr parameters_callback_;
132 |
133 | /**
134 | * @brief Subscriber
135 | */
136 | rclcpp::Subscription::SharedPtr subscriber_;
137 |
138 | /**
139 | * @brief Publisher
140 | */
141 | rclcpp_lifecycle::LifecyclePublisher::SharedPtr publisher_;
142 |
143 | /**
144 | * @brief Dummy parameter (parameter)
145 | */
146 | double param_ = 1.0;
147 | };
148 |
149 |
150 | }
151 |
--------------------------------------------------------------------------------
/ros2-pkg-create/src/ros2_pkg_create/__main__.py:
--------------------------------------------------------------------------------
1 | import argcomplete
2 | import argparse
3 | import os
4 |
5 | import copier
6 |
7 | import ros2_pkg_create
8 |
9 |
10 | def parseArguments() -> argparse.Namespace:
11 |
12 | parser = argparse.ArgumentParser(description="Creates a ROS 2 package from templates")
13 |
14 | parser.add_argument("destination", type=str, help="Destination directory")
15 | parser.add_argument("--defaults", action="store_true", help="Use defaults for all options")
16 | parser.add_argument("--use-local-templates", action="store_true", help="Use locally installed templates instead of remotely pulling most recent ones")
17 |
18 | parser.add_argument("--template", type=str, default=None, choices=os.listdir(os.path.join(os.path.dirname(__file__), "templates")), required=True, help="Template")
19 | parser.add_argument("--package-name", type=str, default=None, help="Package name")
20 | parser.add_argument("--description", type=str, default=None, help="Description")
21 | parser.add_argument("--maintainer", type=str, default=None, help="Maintainer")
22 | parser.add_argument("--maintainer-email", type=str, default=None, help="Maintainer email")
23 | parser.add_argument("--author", type=str, default=None, help="Author")
24 | parser.add_argument("--author-email", type=str, default=None, help="Author email")
25 | parser.add_argument("--license", type=str, default=None, choices=["Apache-2.0", "BSL-1.0", "BSD-2.0", "BSD-2-Clause", "BSD-3-Clause", "GPL-3.0-only", "LGPL-2.1-only", "LGPL-3.0-only", "MIT", "MIT-0"], help="License")
26 | parser.add_argument("--node-name", type=str, default=None, help="Node name")
27 | parser.add_argument("--node-class-name", type=str, default=None, help="Class name of node")
28 | parser.add_argument("--is-component", action="store_true", default=None, help="Make it a component?")
29 | parser.add_argument("--no-is-component", dest="is-component", default=None, action="store_false")
30 | parser.add_argument("--is-lifecycle", action="store_true", default=None, help="Make it a lifecycle node?")
31 | parser.add_argument("--no-is-lifecycle", dest="is-lifecycle", default=None, action="store_false")
32 | parser.add_argument("--executor-type", type=str, choices=["SingleThreadedExecutor", "MultiThreadedExecutor", "StaticSingleThreadedExecutor"], help="Type of Executor")
33 | parser.add_argument("--has-launch-file", action="store_true", default=None, help="Add a launch file?")
34 | parser.add_argument("--no-has-launch-file", dest="has-launch-file", default=None, action="store_false")
35 | parser.add_argument("--launch-file-type", type=str, choices=["xml", "py", "yml"], help="Type of launch file")
36 | parser.add_argument("--has-params", action="store_true", default=None, help="Add parameter loading")
37 | parser.add_argument("--no-has-params", dest="has-params", default=None, action="store_false")
38 | parser.add_argument("--has-subscriber", action="store_true", default=None, help="Add a subscriber?")
39 | parser.add_argument("--no-has-subscriber", dest="has-subscriber", default=None, action="store_false")
40 | parser.add_argument("--has-publisher", action="store_true", default=None, help="Add a publisher?")
41 | parser.add_argument("--no-has-publisher", dest="has-publisher", default=None, action="store_false")
42 | parser.add_argument("--has-service-server", action="store_true", default=None, help="Add a service server?")
43 | parser.add_argument("--no-has-service-server", dest="has-service-server", default=None, action="store_false")
44 | parser.add_argument("--has-action-server", action="store_true", default=None, help="Add an action server?")
45 | parser.add_argument("--no-has-action-server", dest="has-action-server", default=None, action="store_false")
46 | parser.add_argument("--has-timer", action="store_true", default=None, help="Add a timer callback?")
47 | parser.add_argument("--no-has-timer", dest="has-timer", default=None, action="store_false")
48 | parser.add_argument("--auto-shutdown", action="store_true", default=None, help="Automatically shutdown the node after launch (useful in CI/CD)?")
49 | parser.add_argument("--no-auto-shutdown", dest="auto-shutdown", default=None, action="store_false")
50 | parser.add_argument("--interface-types", type=str, default=None, choices=["Message", "Service", "Action"], help="Interfaces types")
51 | parser.add_argument("--msg-name", type=str, default=None, help="Message name")
52 | parser.add_argument("--srv-name", type=str, default=None, help="Service name")
53 | parser.add_argument("--action-name", type=str, default=None, help="Action name")
54 | parser.add_argument("--has-docker-ros", action="store_true", default=None, help="Add the docker-ros CI integration?")
55 | parser.add_argument("--docker-ros-type", type=str, choices=["github", "gitlab"], help="Type of docker-ros CI integration")
56 |
57 | parser.add_argument("--version", action="version", version=f"%(prog)s v{ros2_pkg_create.__version__}")
58 |
59 | argcomplete.autocomplete(parser)
60 | return parser.parse_args()
61 |
62 |
63 | def main():
64 |
65 | # pass specified arguments as data to copier
66 | args = parseArguments()
67 | answers = {k: v for k, v in vars(args).items() if v is not None}
68 |
69 | # run copier
70 | try:
71 | if args.use_local_templates:
72 | template_location = os.path.join(os.path.dirname(__file__))
73 | else:
74 | template_location = "https://github.com/ika-rwth-aachen/ros2-pkg-create.git"
75 | copier.run_copy(template_location,
76 | os.path.join(os.getcwd(), args.destination),
77 | data=answers,
78 | defaults=args.defaults,
79 | unsafe=True)
80 | except copier.CopierAnswersInterrupt:
81 | print("Aborted")
82 | return
83 |
84 | if __name__ == "__main__":
85 | main()
86 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_component_pkg/src/ros2_cpp_node.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include
4 |
5 | #include
6 | RCLCPP_COMPONENTS_REGISTER_NODE(ros2_cpp_component_pkg::Ros2CppNode)
7 |
8 |
9 | namespace ros2_cpp_component_pkg {
10 |
11 |
12 | Ros2CppNode::Ros2CppNode(const rclcpp::NodeOptions& options) : Node("ros2_cpp_node", options) {
13 |
14 | this->declareAndLoadParameter("param", param_, "TODO", true, false, false, 0.0, 10.0, 1.0);
15 | this->setup();
16 | }
17 |
18 |
19 | template
20 | void Ros2CppNode::declareAndLoadParameter(const std::string& name,
21 | T& param,
22 | const std::string& description,
23 | const bool add_to_auto_reconfigurable_params,
24 | const bool is_required,
25 | const bool read_only,
26 | const std::optional& from_value,
27 | const std::optional& to_value,
28 | const std::optional& step_value,
29 | const std::string& additional_constraints) {
30 |
31 | rcl_interfaces::msg::ParameterDescriptor param_desc;
32 | param_desc.description = description;
33 | param_desc.additional_constraints = additional_constraints;
34 | param_desc.read_only = read_only;
35 |
36 | auto type = rclcpp::ParameterValue(param).get_type();
37 |
38 | if (from_value.has_value() && to_value.has_value()) {
39 | if constexpr(std::is_integral_v) {
40 | rcl_interfaces::msg::IntegerRange range;
41 | range.set__from_value(static_cast(from_value.value())).set__to_value(static_cast(to_value.value()));
42 | if (step_value.has_value()) range.set__step(static_cast(step_value.value()));
43 | param_desc.integer_range = {range};
44 | } else if constexpr(std::is_floating_point_v) {
45 | rcl_interfaces::msg::FloatingPointRange range;
46 | range.set__from_value(static_cast(from_value.value())).set__to_value(static_cast(to_value.value()));
47 | if (step_value.has_value()) range.set__step(static_cast(step_value.value()));
48 | param_desc.floating_point_range = {range};
49 | } else {
50 | RCLCPP_WARN(this->get_logger(), "Parameter type of parameter '%s' does not support specifying a range", name.c_str());
51 | }
52 | }
53 |
54 | this->declare_parameter(name, type, param_desc);
55 |
56 | try {
57 | param = this->get_parameter(name).get_value();
58 | std::stringstream ss;
59 | ss << "Loaded parameter '" << name << "': ";
60 | if constexpr(is_vector_v) {
61 | ss << "[";
62 | for (const auto& element : param) ss << element << (&element != ¶m.back() ? ", " : "");
63 | ss << "]";
64 | } else {
65 | ss << param;
66 | }
67 | RCLCPP_INFO_STREAM(this->get_logger(), ss.str());
68 | } catch (rclcpp::exceptions::ParameterUninitializedException&) {
69 | if (is_required) {
70 | RCLCPP_FATAL_STREAM(this->get_logger(), "Missing required parameter '" << name << "', exiting");
71 | exit(EXIT_FAILURE);
72 | } else {
73 | std::stringstream ss;
74 | ss << "Missing parameter '" << name << "', using default value: ";
75 | if constexpr(is_vector_v) {
76 | ss << "[";
77 | for (const auto& element : param) ss << element << (&element != ¶m.back() ? ", " : "");
78 | ss << "]";
79 | } else {
80 | ss << param;
81 | }
82 | RCLCPP_WARN_STREAM(this->get_logger(), ss.str());
83 | this->set_parameters({rclcpp::Parameter(name, rclcpp::ParameterValue(param))});
84 | }
85 | }
86 |
87 | if (add_to_auto_reconfigurable_params) {
88 | std::function setter = [¶m](const rclcpp::Parameter& p) {
89 | param = p.get_value();
90 | };
91 | auto_reconfigurable_params_.push_back(std::make_tuple(name, setter));
92 | }
93 | }
94 |
95 |
96 | rcl_interfaces::msg::SetParametersResult Ros2CppNode::parametersCallback(const std::vector& parameters) {
97 |
98 | for (const auto& param : parameters) {
99 | for (auto& auto_reconfigurable_param : auto_reconfigurable_params_) {
100 | if (param.get_name() == std::get<0>(auto_reconfigurable_param)) {
101 | std::get<1>(auto_reconfigurable_param)(param);
102 | RCLCPP_INFO(this->get_logger(), "Reconfigured parameter '%s' to: %s", param.get_name().c_str(), param.value_to_string().c_str());
103 | break;
104 | }
105 | }
106 | }
107 |
108 | rcl_interfaces::msg::SetParametersResult result;
109 | result.successful = true;
110 |
111 | return result;
112 | }
113 |
114 |
115 | void Ros2CppNode::setup() {
116 |
117 | // callback for dynamic parameter configuration
118 | parameters_callback_ = this->add_on_set_parameters_callback(std::bind(&Ros2CppNode::parametersCallback, this, std::placeholders::_1));
119 |
120 | // subscriber for handling incoming messages
121 | subscriber_ = this->create_subscription("~/input", 10, std::bind(&Ros2CppNode::topicCallback, this, std::placeholders::_1));
122 | RCLCPP_INFO(this->get_logger(), "Subscribed to '%s'", subscriber_->get_topic_name());
123 |
124 | // publisher for publishing outgoing messages
125 | publisher_ = this->create_publisher("~/output", 10);
126 | RCLCPP_INFO(this->get_logger(), "Publishing to '%s'", publisher_->get_topic_name());
127 | }
128 |
129 |
130 | void Ros2CppNode::topicCallback(const std_msgs::msg::Int32::ConstSharedPtr& msg) {
131 |
132 | RCLCPP_INFO(this->get_logger(), "Message received: '%d'", msg->data);
133 |
134 | // publish message
135 | std_msgs::msg::Int32::UniquePtr out_msg = std::make_unique();
136 | out_msg->data = msg->data;
137 | RCLCPP_INFO(this->get_logger(), "Message published: '%d'", out_msg->data);
138 | publisher_->publish(std::move(out_msg));
139 | }
140 |
141 |
142 | }
143 |
--------------------------------------------------------------------------------
/.github/workflows/check-samples.yml:
--------------------------------------------------------------------------------
1 | name: Check samples
2 |
3 | # avoiding duplicate jobs on push with open pull_request: https://github.com/orgs/community/discussions/26940#discussioncomment-6656489
4 | on: [push, pull_request]
5 |
6 | jobs:
7 |
8 | check:
9 | if: (github.event_name != 'pull_request' && ! github.event.pull_request.head.repo.fork) || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork)
10 | runs-on: ubuntu-latest
11 | strategy:
12 | matrix:
13 | include:
14 | - package_name: ros2_cpp_pkg
15 | template: ros2_cpp_pkg
16 | node_name: ros2_cpp_node
17 | is_component: false
18 | is_lifecycle: false
19 | executor_type: SingleThreadedExecutor
20 | has_launch_file: true
21 | has_params: true
22 | has_subscriber: true
23 | has_publisher: true
24 | has_service_server: false
25 | has_action_server: false
26 | has_timer: false
27 | has_docker_ros: true
28 | - package_name: ros2_cpp_component_pkg
29 | template: ros2_cpp_pkg
30 | node_name: ros2_cpp_node
31 | is_component: true
32 | is_lifecycle: false
33 | executor_type: SingleThreadedExecutor
34 | has_launch_file: true
35 | has_params: true
36 | has_subscriber: true
37 | has_publisher: true
38 | has_service_server: false
39 | has_action_server: false
40 | has_timer: false
41 | has_docker_ros: true
42 | - package_name: ros2_cpp_lifecycle_pkg
43 | template: ros2_cpp_pkg
44 | node_name: ros2_cpp_node
45 | is_component: false
46 | is_lifecycle: true
47 | executor_type: SingleThreadedExecutor
48 | has_launch_file: true
49 | has_params: true
50 | has_subscriber: true
51 | has_publisher: true
52 | has_service_server: false
53 | has_action_server: false
54 | has_timer: false
55 | has_docker_ros: true
56 | - package_name: ros2_cpp_multi_threaded_pkg
57 | template: ros2_cpp_pkg
58 | node_name: ros2_cpp_node
59 | is_component: false
60 | is_lifecycle: false
61 | executor_type: MultiThreadedExecutor
62 | has_launch_file: true
63 | has_params: true
64 | has_subscriber: true
65 | has_publisher: true
66 | has_service_server: false
67 | has_action_server: false
68 | has_timer: false
69 | has_docker_ros: true
70 | - package_name: ros2_cpp_all_pkg
71 | template: ros2_cpp_pkg
72 | node_name: ros2_cpp_node
73 | is_component: true
74 | is_lifecycle: true
75 | executor_type: StaticSingleThreadedExecutor
76 | has_launch_file: true
77 | has_params: true
78 | has_subscriber: true
79 | has_publisher: true
80 | has_service_server: true
81 | has_action_server: true
82 | has_timer: true
83 | has_docker_ros: true
84 | - package_name: ros2_python_pkg
85 | template: ros2_python_pkg
86 | node_name: ros2_python_node
87 | is_lifecycle: false
88 | executor_type: SingleThreadedExecutor
89 | has_launch_file: true
90 | has_params: true
91 | has_subscriber: true
92 | has_publisher: true
93 | has_service_server: false
94 | has_action_server: false
95 | has_timer: false
96 | has_docker_ros: true
97 | - package_name: ros2_python_all_pkg
98 | template: ros2_python_pkg
99 | node_name: ros2_python_node
100 | is_lifecycle: false
101 | executor_type: SingleThreadedExecutor
102 | has_launch_file: true
103 | has_params: true
104 | has_subscriber: true
105 | has_publisher: true
106 | has_service_server: true
107 | has_action_server: true
108 | has_timer: true
109 | has_docker_ros: true
110 | - package_name: ros2_interfaces_pkg
111 | template: ros2_interfaces_pkg
112 | executor_type: SingleThreadedExecutor
113 | steps:
114 | - name: Checkout code
115 | uses: actions/checkout@v4
116 | - name: Set up Python
117 | uses: actions/setup-python@v5
118 | with:
119 | python-version: "3.10"
120 | - name: Install dependencies
121 | run: |
122 | pip install "copier~=9.2.0" "jinja2-strcase~=0.0.2"
123 | - name: Configure git to run copier
124 | run: |
125 | git config --global user.name "dummy"
126 | git config --global user.email "dummy@dummy.com"
127 | - name: Generate packages
128 | run: |
129 | copier copy --trust --defaults --overwrite --vcs-ref=HEAD \
130 | -d template=${{ matrix.template }} \
131 | -d package_name=${{ matrix.package_name }} \
132 | -d node_name=${{ matrix.node_name }} \
133 | -d is_component=${{ matrix.is_component }} \
134 | -d is_lifecycle=${{ matrix.is_lifecycle }} \
135 | -d executor_type=${{ matrix.executor_type }} \
136 | -d has_launch_file=${{ matrix.has_launch_file }} \
137 | -d has_params=${{ matrix.has_params }} \
138 | -d has_subscriber=${{ matrix.has_subscriber }} \
139 | -d has_publisher=${{ matrix.has_publisher }} \
140 | -d has_service_server=${{ matrix.has_service_server }} \
141 | -d has_action_server=${{ matrix.has_action_server }} \
142 | -d has_timer=${{ matrix.has_timer }} \
143 | -d has_docker_ros=${{ matrix.has_docker_ros }} \
144 | . packages
145 | - name: Check for repository changes
146 | run: |
147 | rm -rf samples/${{ matrix.package_name }}*
148 | mv packages/${{ matrix.package_name }}* samples/
149 | if [[ ! -z "$(git status --porcelain)" ]]; then
150 | echo "Sample generation resulted in changes to the repository"
151 | git status
152 | git diff
153 | exit 1
154 | fi
155 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_pkg/src/ros2_cpp_node.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include
4 |
5 |
6 | namespace ros2_cpp_pkg {
7 |
8 |
9 | Ros2CppNode::Ros2CppNode() : Node("ros2_cpp_node") {
10 |
11 | this->declareAndLoadParameter("param", param_, "TODO", true, false, false, 0.0, 10.0, 1.0);
12 | this->setup();
13 | }
14 |
15 |
16 | template
17 | void Ros2CppNode::declareAndLoadParameter(const std::string& name,
18 | T& param,
19 | const std::string& description,
20 | const bool add_to_auto_reconfigurable_params,
21 | const bool is_required,
22 | const bool read_only,
23 | const std::optional& from_value,
24 | const std::optional& to_value,
25 | const std::optional& step_value,
26 | const std::string& additional_constraints) {
27 |
28 | rcl_interfaces::msg::ParameterDescriptor param_desc;
29 | param_desc.description = description;
30 | param_desc.additional_constraints = additional_constraints;
31 | param_desc.read_only = read_only;
32 |
33 | auto type = rclcpp::ParameterValue(param).get_type();
34 |
35 | if (from_value.has_value() && to_value.has_value()) {
36 | if constexpr(std::is_integral_v) {
37 | rcl_interfaces::msg::IntegerRange range;
38 | range.set__from_value(static_cast(from_value.value())).set__to_value(static_cast(to_value.value()));
39 | if (step_value.has_value()) range.set__step(static_cast(step_value.value()));
40 | param_desc.integer_range = {range};
41 | } else if constexpr(std::is_floating_point_v) {
42 | rcl_interfaces::msg::FloatingPointRange range;
43 | range.set__from_value(static_cast(from_value.value())).set__to_value(static_cast(to_value.value()));
44 | if (step_value.has_value()) range.set__step(static_cast(step_value.value()));
45 | param_desc.floating_point_range = {range};
46 | } else {
47 | RCLCPP_WARN(this->get_logger(), "Parameter type of parameter '%s' does not support specifying a range", name.c_str());
48 | }
49 | }
50 |
51 | this->declare_parameter(name, type, param_desc);
52 |
53 | try {
54 | param = this->get_parameter(name).get_value();
55 | std::stringstream ss;
56 | ss << "Loaded parameter '" << name << "': ";
57 | if constexpr(is_vector_v) {
58 | ss << "[";
59 | for (const auto& element : param) ss << element << (&element != ¶m.back() ? ", " : "");
60 | ss << "]";
61 | } else {
62 | ss << param;
63 | }
64 | RCLCPP_INFO_STREAM(this->get_logger(), ss.str());
65 | } catch (rclcpp::exceptions::ParameterUninitializedException&) {
66 | if (is_required) {
67 | RCLCPP_FATAL_STREAM(this->get_logger(), "Missing required parameter '" << name << "', exiting");
68 | exit(EXIT_FAILURE);
69 | } else {
70 | std::stringstream ss;
71 | ss << "Missing parameter '" << name << "', using default value: ";
72 | if constexpr(is_vector_v) {
73 | ss << "[";
74 | for (const auto& element : param) ss << element << (&element != ¶m.back() ? ", " : "");
75 | ss << "]";
76 | } else {
77 | ss << param;
78 | }
79 | RCLCPP_WARN_STREAM(this->get_logger(), ss.str());
80 | this->set_parameters({rclcpp::Parameter(name, rclcpp::ParameterValue(param))});
81 | }
82 | }
83 |
84 | if (add_to_auto_reconfigurable_params) {
85 | std::function setter = [¶m](const rclcpp::Parameter& p) {
86 | param = p.get_value();
87 | };
88 | auto_reconfigurable_params_.push_back(std::make_tuple(name, setter));
89 | }
90 | }
91 |
92 |
93 | rcl_interfaces::msg::SetParametersResult Ros2CppNode::parametersCallback(const std::vector& parameters) {
94 |
95 | for (const auto& param : parameters) {
96 | for (auto& auto_reconfigurable_param : auto_reconfigurable_params_) {
97 | if (param.get_name() == std::get<0>(auto_reconfigurable_param)) {
98 | std::get<1>(auto_reconfigurable_param)(param);
99 | RCLCPP_INFO(this->get_logger(), "Reconfigured parameter '%s' to: %s", param.get_name().c_str(), param.value_to_string().c_str());
100 | break;
101 | }
102 | }
103 | }
104 |
105 | rcl_interfaces::msg::SetParametersResult result;
106 | result.successful = true;
107 |
108 | return result;
109 | }
110 |
111 |
112 | void Ros2CppNode::setup() {
113 |
114 | // callback for dynamic parameter configuration
115 | parameters_callback_ = this->add_on_set_parameters_callback(std::bind(&Ros2CppNode::parametersCallback, this, std::placeholders::_1));
116 |
117 | // subscriber for handling incoming messages
118 | subscriber_ = this->create_subscription("~/input", 10, std::bind(&Ros2CppNode::topicCallback, this, std::placeholders::_1));
119 | RCLCPP_INFO(this->get_logger(), "Subscribed to '%s'", subscriber_->get_topic_name());
120 |
121 | // publisher for publishing outgoing messages
122 | publisher_ = this->create_publisher("~/output", 10);
123 | RCLCPP_INFO(this->get_logger(), "Publishing to '%s'", publisher_->get_topic_name());
124 | }
125 |
126 |
127 | void Ros2CppNode::topicCallback(const std_msgs::msg::Int32::ConstSharedPtr& msg) {
128 |
129 | RCLCPP_INFO(this->get_logger(), "Message received: '%d'", msg->data);
130 |
131 | // publish message
132 | std_msgs::msg::Int32 out_msg;
133 | out_msg.data = msg->data;
134 | publisher_->publish(out_msg);
135 | RCLCPP_INFO(this->get_logger(), "Message published: '%d'", out_msg.data);
136 | }
137 |
138 |
139 | }
140 |
141 |
142 | int main(int argc, char *argv[]) {
143 |
144 | rclcpp::init(argc, argv);
145 | auto node = std::make_shared();
146 | rclcpp::executors::SingleThreadedExecutor executor;
147 | RCLCPP_INFO(node->get_logger(), "Spinning node '%s' with %s", node->get_fully_qualified_name(), "SingleThreadedExecutor");
148 | executor.add_node(node);
149 | executor.spin();
150 | rclcpp::shutdown();
151 |
152 | return 0;
153 | }
154 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_multi_threaded_pkg/src/ros2_cpp_node.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include
5 |
6 |
7 | namespace ros2_cpp_multi_threaded_pkg {
8 |
9 |
10 | Ros2CppNode::Ros2CppNode() : Node("ros2_cpp_node") {
11 |
12 | this->declareAndLoadParameter("num_threads", num_threads_, "number of threads for MultiThreadedExecutor", false, false, false, 1, std::thread::hardware_concurrency(), 1);
13 | this->declareAndLoadParameter("param", param_, "TODO", true, false, false, 0.0, 10.0, 1.0);
14 | this->setup();
15 | }
16 |
17 |
18 | template
19 | void Ros2CppNode::declareAndLoadParameter(const std::string& name,
20 | T& param,
21 | const std::string& description,
22 | const bool add_to_auto_reconfigurable_params,
23 | const bool is_required,
24 | const bool read_only,
25 | const std::optional& from_value,
26 | const std::optional& to_value,
27 | const std::optional& step_value,
28 | const std::string& additional_constraints) {
29 |
30 | rcl_interfaces::msg::ParameterDescriptor param_desc;
31 | param_desc.description = description;
32 | param_desc.additional_constraints = additional_constraints;
33 | param_desc.read_only = read_only;
34 |
35 | auto type = rclcpp::ParameterValue(param).get_type();
36 |
37 | if (from_value.has_value() && to_value.has_value()) {
38 | if constexpr(std::is_integral_v) {
39 | rcl_interfaces::msg::IntegerRange range;
40 | range.set__from_value(static_cast(from_value.value())).set__to_value(static_cast(to_value.value()));
41 | if (step_value.has_value()) range.set__step(static_cast(step_value.value()));
42 | param_desc.integer_range = {range};
43 | } else if constexpr(std::is_floating_point_v) {
44 | rcl_interfaces::msg::FloatingPointRange range;
45 | range.set__from_value(static_cast(from_value.value())).set__to_value(static_cast(to_value.value()));
46 | if (step_value.has_value()) range.set__step(static_cast(step_value.value()));
47 | param_desc.floating_point_range = {range};
48 | } else {
49 | RCLCPP_WARN(this->get_logger(), "Parameter type of parameter '%s' does not support specifying a range", name.c_str());
50 | }
51 | }
52 |
53 | this->declare_parameter(name, type, param_desc);
54 |
55 | try {
56 | param = this->get_parameter(name).get_value();
57 | std::stringstream ss;
58 | ss << "Loaded parameter '" << name << "': ";
59 | if constexpr(is_vector_v) {
60 | ss << "[";
61 | for (const auto& element : param) ss << element << (&element != ¶m.back() ? ", " : "");
62 | ss << "]";
63 | } else {
64 | ss << param;
65 | }
66 | RCLCPP_INFO_STREAM(this->get_logger(), ss.str());
67 | } catch (rclcpp::exceptions::ParameterUninitializedException&) {
68 | if (is_required) {
69 | RCLCPP_FATAL_STREAM(this->get_logger(), "Missing required parameter '" << name << "', exiting");
70 | exit(EXIT_FAILURE);
71 | } else {
72 | std::stringstream ss;
73 | ss << "Missing parameter '" << name << "', using default value: ";
74 | if constexpr(is_vector_v) {
75 | ss << "[";
76 | for (const auto& element : param) ss << element << (&element != ¶m.back() ? ", " : "");
77 | ss << "]";
78 | } else {
79 | ss << param;
80 | }
81 | RCLCPP_WARN_STREAM(this->get_logger(), ss.str());
82 | this->set_parameters({rclcpp::Parameter(name, rclcpp::ParameterValue(param))});
83 | }
84 | }
85 |
86 | if (add_to_auto_reconfigurable_params) {
87 | std::function setter = [¶m](const rclcpp::Parameter& p) {
88 | param = p.get_value();
89 | };
90 | auto_reconfigurable_params_.push_back(std::make_tuple(name, setter));
91 | }
92 | }
93 |
94 |
95 | rcl_interfaces::msg::SetParametersResult Ros2CppNode::parametersCallback(const std::vector& parameters) {
96 |
97 | for (const auto& param : parameters) {
98 | for (auto& auto_reconfigurable_param : auto_reconfigurable_params_) {
99 | if (param.get_name() == std::get<0>(auto_reconfigurable_param)) {
100 | std::get<1>(auto_reconfigurable_param)(param);
101 | RCLCPP_INFO(this->get_logger(), "Reconfigured parameter '%s' to: %s", param.get_name().c_str(), param.value_to_string().c_str());
102 | break;
103 | }
104 | }
105 | }
106 |
107 | rcl_interfaces::msg::SetParametersResult result;
108 | result.successful = true;
109 |
110 | return result;
111 | }
112 |
113 |
114 | void Ros2CppNode::setup() {
115 |
116 | // callback for dynamic parameter configuration
117 | parameters_callback_ = this->add_on_set_parameters_callback(std::bind(&Ros2CppNode::parametersCallback, this, std::placeholders::_1));
118 |
119 | // create callback group for potentially multi-threaded execution
120 | rclcpp::SubscriptionOptions subscriber_options;
121 | subscriber_options.callback_group = this->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive);
122 |
123 | // subscriber for handling incoming messages
124 | subscriber_ = this->create_subscription("~/input", 10, std::bind(&Ros2CppNode::topicCallback, this, std::placeholders::_1), subscriber_options);
125 | RCLCPP_INFO(this->get_logger(), "Subscribed to '%s'", subscriber_->get_topic_name());
126 |
127 | // publisher for publishing outgoing messages
128 | publisher_ = this->create_publisher("~/output", 10);
129 | RCLCPP_INFO(this->get_logger(), "Publishing to '%s'", publisher_->get_topic_name());
130 | }
131 |
132 |
133 | void Ros2CppNode::topicCallback(const std_msgs::msg::Int32::ConstSharedPtr& msg) {
134 |
135 | RCLCPP_INFO(this->get_logger(), "Message received: '%d'", msg->data);
136 |
137 | // publish message
138 | std_msgs::msg::Int32 out_msg;
139 | out_msg.data = msg->data;
140 | publisher_->publish(out_msg);
141 | RCLCPP_INFO(this->get_logger(), "Message published: '%d'", out_msg.data);
142 | }
143 |
144 |
145 | }
146 |
147 |
148 | int main(int argc, char *argv[]) {
149 |
150 | rclcpp::init(argc, argv);
151 | auto node = std::make_shared();
152 | rclcpp::executors::MultiThreadedExecutor executor(rclcpp::ExecutorOptions(), node->num_threads_);
153 | RCLCPP_INFO(node->get_logger(), "Spinning node '%s' with %s (%d threads)", node->get_fully_qualified_name(), "MultiThreadedExecutor", node->num_threads_);
154 | executor.add_node(node);
155 | executor.spin();
156 | rclcpp::shutdown();
157 |
158 | return 0;
159 | }
160 |
--------------------------------------------------------------------------------
/samples/ros2_python_pkg/ros2_python_pkg/ros2_python_node.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Optional, Union
2 |
3 | import rclpy
4 | from rclpy.node import Node
5 | import rclpy.exceptions
6 | from rcl_interfaces.msg import (FloatingPointRange, IntegerRange, ParameterDescriptor, SetParametersResult)
7 | from std_msgs.msg import Int32
8 |
9 |
10 | class Ros2PythonNode(Node):
11 |
12 | def __init__(self):
13 | """Constructor"""
14 | super().__init__("ros2_python_node")
15 |
16 | self.subscriber = None
17 |
18 | self.publisher = None
19 |
20 | self.auto_reconfigurable_params: list[str] = []
21 | self.param = self.declare_and_load_parameter(name="param",
22 | param_type=rclpy.Parameter.Type.DOUBLE,
23 | description="TODO",
24 | default=1.0,
25 | from_value=0.0,
26 | to_value=10.0,
27 | step_value=0.1)
28 |
29 | self.setup()
30 |
31 | def declare_and_load_parameter(self,
32 | name: str,
33 | param_type: rclpy.Parameter.Type,
34 | description: str,
35 | default: Optional[Any] = None,
36 | add_to_auto_reconfigurable_params: bool = True,
37 | is_required: bool = False,
38 | read_only: bool = False,
39 | from_value: Optional[Union[int, float]] = None,
40 | to_value: Optional[Union[int, float]] = None,
41 | step_value: Optional[Union[int, float]] = None,
42 | additional_constraints: str = "") -> Any:
43 | """Declares and loads a ROS parameter
44 |
45 | Args:
46 | name (str): name
47 | param_type (rclpy.Parameter.Type): parameter type
48 | description (str): description
49 | default (Optional[Any], optional): default value
50 | add_to_auto_reconfigurable_params (bool, optional): enable reconfiguration of parameter
51 | is_required (bool, optional): whether failure to load parameter will stop node
52 | read_only (bool, optional): set parameter to read-only
53 | from_value (Optional[Union[int, float]], optional): parameter range minimum
54 | to_value (Optional[Union[int, float]], optional): parameter range maximum
55 | step_value (Optional[Union[int, float]], optional): parameter range step
56 | additional_constraints (str, optional): additional constraints description
57 |
58 | Returns:
59 | Any: parameter value
60 | """
61 |
62 | # declare parameter
63 | param_desc = ParameterDescriptor()
64 | param_desc.description = description
65 | param_desc.additional_constraints = additional_constraints
66 | param_desc.read_only = read_only
67 | if from_value is not None and to_value is not None:
68 | if param_type == rclpy.Parameter.Type.INTEGER:
69 | range = IntegerRange(from_value=from_value, to_value=to_value)
70 | if step_value is not None:
71 | range.step = step_value
72 | param_desc.integer_range = [range]
73 | elif param_type == rclpy.Parameter.Type.DOUBLE:
74 | range = FloatingPointRange(from_value=from_value, to_value=to_value)
75 | if step_value is not None:
76 | range.step = step_value
77 | param_desc.floating_point_range = [range]
78 | else:
79 | self.get_logger().warn(f"Parameter type of parameter '{name}' does not support specifying a range")
80 | self.declare_parameter(name, param_type, param_desc)
81 |
82 | # load parameter
83 | try:
84 | param = self.get_parameter(name).value
85 | self.get_logger().info(f"Loaded parameter '{name}': {param}")
86 | except rclpy.exceptions.ParameterUninitializedException:
87 | if is_required:
88 | self.get_logger().fatal(f"Missing required parameter '{name}', exiting")
89 | raise SystemExit(1)
90 | else:
91 | self.get_logger().warn(f"Missing parameter '{name}', using default value: {default}")
92 | param = default
93 | self.set_parameters([rclpy.Parameter(name=name, value=param)])
94 |
95 | # add parameter to auto-reconfigurable parameters
96 | if add_to_auto_reconfigurable_params:
97 | self.auto_reconfigurable_params.append(name)
98 |
99 | return param
100 |
101 | def parameters_callback(self,
102 | parameters: list[rclpy.Parameter]) -> SetParametersResult:
103 | """Handles reconfiguration when a parameter value is changed
104 |
105 | Args:
106 | parameters (list[rclpy.Parameter]): parameters
107 |
108 | Returns:
109 | SetParametersResult: parameter change result
110 | """
111 |
112 | for param in parameters:
113 | if param.name in self.auto_reconfigurable_params:
114 | setattr(self, param.name, param.value)
115 | self.get_logger().info(f"Reconfigured parameter '{param.name}' to: {param.value}")
116 |
117 | result = SetParametersResult()
118 | result.successful = True
119 |
120 | return result
121 |
122 | def setup(self):
123 | """Sets up subscribers, publishers, etc. to configure the node"""
124 |
125 | # callback for dynamic parameter configuration
126 | self.add_on_set_parameters_callback(self.parameters_callback)
127 |
128 | # subscriber for handling incoming messages
129 | self.subscriber = self.create_subscription(Int32,
130 | "~/input",
131 | self.topic_callback,
132 | qos_profile=10)
133 | self.get_logger().info(f"Subscribed to '{self.subscriber.topic_name}'")
134 |
135 | # publisher for publishing outgoing messages
136 | self.publisher = self.create_publisher(Int32,
137 | "~/output",
138 | qos_profile=10)
139 | self.get_logger().info(f"Publishing to '{self.publisher.topic_name}'")
140 |
141 | def topic_callback(self, msg: Int32):
142 | """Processes messages received by a subscriber
143 |
144 | Args:
145 | msg (Int32): message
146 | """
147 |
148 | self.get_logger().info(f"Message received: '{msg.data}'")
149 |
150 |
151 | def main():
152 |
153 | rclpy.init()
154 | node = Ros2PythonNode()
155 | try:
156 | rclpy.spin(node)
157 | except KeyboardInterrupt:
158 | pass
159 | finally:
160 | node.destroy_node()
161 | rclpy.try_shutdown()
162 |
163 |
164 | if __name__ == '__main__':
165 | main()
166 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_all_pkg/include/ros2_cpp_all_pkg/ros2_cpp_node.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 | #include
17 |
18 |
19 | namespace ros2_cpp_all_pkg {
20 |
21 | template struct is_vector : std::false_type {};
22 | template struct is_vector< std::vector > : std::true_type {};
23 | template inline constexpr bool is_vector_v = is_vector::value;
24 |
25 |
26 | /**
27 | * @brief Ros2CppNode class
28 | */
29 | class Ros2CppNode : public rclcpp_lifecycle::LifecycleNode {
30 |
31 | public:
32 |
33 | /**
34 | * @brief Constructor
35 | *
36 | * @param options node options
37 | */
38 | explicit Ros2CppNode(const rclcpp::NodeOptions& options);
39 |
40 | protected:
41 |
42 | /**
43 | * @brief Processes 'configuring' transitions to 'inactive' state
44 | *
45 | * @param state previous state
46 | * @return transition result
47 | */
48 | rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_configure(const rclcpp_lifecycle::State& state) override;
49 |
50 | /**
51 | * @brief Processes 'activating' transitions to 'active' state
52 | *
53 | * @param state previous state
54 | * @return transition result
55 | */
56 | rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_activate(const rclcpp_lifecycle::State& state) override;
57 |
58 | /**
59 | * @brief Processes 'deactivating' transitions to 'inactive' state
60 | *
61 | * @param state previous state
62 | * @return transition result
63 | */
64 | rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_deactivate(const rclcpp_lifecycle::State& state) override;
65 |
66 | /**
67 | * @brief Processes 'cleaningup' transitions to 'unconfigured' state
68 | *
69 | * @param state previous state
70 | * @return transition result
71 | */
72 | rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_cleanup(const rclcpp_lifecycle::State& state) override;
73 |
74 | /**
75 | * @brief Processes 'shuttingdown' transitions to 'finalized' state
76 | *
77 | * @param state previous state
78 | * @return transition result
79 | */
80 | rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_shutdown(const rclcpp_lifecycle::State& state) override;
81 |
82 | private:
83 |
84 | /**
85 | * @brief Declares and loads a ROS parameter
86 | *
87 | * @param name name
88 | * @param param parameter variable to load into
89 | * @param description description
90 | * @param add_to_auto_reconfigurable_params enable reconfiguration of parameter
91 | * @param is_required whether failure to load parameter will stop node
92 | * @param read_only set parameter to read-only
93 | * @param from_value parameter range minimum
94 | * @param to_value parameter range maximum
95 | * @param step_value parameter range step
96 | * @param additional_constraints additional constraints description
97 | */
98 | template
99 | void declareAndLoadParameter(const std::string &name,
100 | T ¶m,
101 | const std::string &description,
102 | const bool add_to_auto_reconfigurable_params = true,
103 | const bool is_required = false,
104 | const bool read_only = false,
105 | const std::optional &from_value = std::nullopt,
106 | const std::optional &to_value = std::nullopt,
107 | const std::optional &step_value = std::nullopt,
108 | const std::string &additional_constraints = "");
109 |
110 | /**
111 | * @brief Handles reconfiguration when a parameter value is changed
112 | *
113 | * @param parameters parameters
114 | * @return parameter change result
115 | */
116 | rcl_interfaces::msg::SetParametersResult parametersCallback(const std::vector& parameters);
117 |
118 | /**
119 | * @brief Sets up subscribers, publishers, etc. to configure the node
120 | */
121 | void setup();
122 |
123 | /**
124 | * @brief Processes messages received by a subscriber
125 | *
126 | * @param msg message
127 | */
128 | void topicCallback(const std_msgs::msg::Int32::ConstSharedPtr& msg);
129 |
130 | /**
131 | * @brief Processes service requests
132 | *
133 | * @param request service request
134 | * @param response service response
135 | */
136 | void serviceCallback(const std::shared_ptrrequest, std::shared_ptr response);
137 |
138 | /**
139 | * @brief Processes action goal requests
140 | *
141 | * @param uuid unique goal identifier
142 | * @param goal action goal
143 | * @return goal response
144 | */
145 | rclcpp_action::GoalResponse actionHandleGoal(const rclcpp_action::GoalUUID& uuid, std::shared_ptr goal);
146 |
147 | /**
148 | * @brief Processes action cancel requests
149 | *
150 | * @param goal_handle action goal handle
151 | * @return cancel response
152 | */
153 | rclcpp_action::CancelResponse actionHandleCancel(const std::shared_ptr> goal_handle);
154 |
155 | /**
156 | * @brief Processes accepted action goal requests
157 | *
158 | * @param goal_handle action goal handle
159 | */
160 | void actionHandleAccepted(const std::shared_ptr> goal_handle);
161 |
162 | /**
163 | * @brief Executes an action
164 | *
165 | * @param goal_handle action goal handle
166 | */
167 | void actionExecute(const std::shared_ptr> goal_handle);
168 |
169 | /**
170 | * @brief Processes timer triggers
171 | */
172 | void timerCallback();
173 |
174 | private:
175 |
176 | /**
177 | * @brief Auto-reconfigurable parameters for dynamic reconfiguration
178 | */
179 | std::vector>> auto_reconfigurable_params_;
180 |
181 | /**
182 | * @brief Callback handle for dynamic parameter reconfiguration
183 | */
184 | OnSetParametersCallbackHandle::SharedPtr parameters_callback_;
185 |
186 | /**
187 | * @brief Subscriber
188 | */
189 | rclcpp::Subscription::SharedPtr subscriber_;
190 |
191 | /**
192 | * @brief Publisher
193 | */
194 | rclcpp_lifecycle::LifecyclePublisher::SharedPtr publisher_;
195 |
196 | /**
197 | * @brief Service server
198 | */
199 | rclcpp::Service::SharedPtr service_server_;
200 |
201 | /**
202 | * @brief Action server
203 | */
204 | rclcpp_action::Server::SharedPtr action_server_;
205 |
206 | /**
207 | * @brief Timer
208 | */
209 | rclcpp::TimerBase::SharedPtr timer_;
210 |
211 | /**
212 | * @brief Dummy parameter (parameter)
213 | */
214 | double param_ = 1.0;
215 | };
216 |
217 |
218 | }
219 |
--------------------------------------------------------------------------------
/.github/workflows/generate-and-test.yml:
--------------------------------------------------------------------------------
1 | name: Generate and test packages
2 |
3 | # avoiding duplicate jobs on push with open pull_request: https://github.com/orgs/community/discussions/26940#discussioncomment-6656489
4 | on: [push, pull_request]
5 |
6 | jobs:
7 |
8 | generate:
9 | if: (github.event_name != 'pull_request' && ! github.event.pull_request.head.repo.fork) || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork)
10 | runs-on: ubuntu-latest
11 | strategy:
12 | matrix:
13 | include:
14 | - package_name: default_cpp_pkg
15 | template: ros2_cpp_pkg
16 | auto_shutdown: true
17 | - package_name: component_cpp_pkg
18 | template: ros2_cpp_pkg
19 | is_component: true
20 | auto_shutdown: true
21 | - package_name: lifecycle_cpp_pkg
22 | template: ros2_cpp_pkg
23 | is_lifecycle: true
24 | auto_shutdown: true
25 | - package_name: multi_threaded_cpp_pkg
26 | template: ros2_cpp_pkg
27 | executor_type: MultiThreadedExecutor
28 | auto_shutdown: true
29 | - package_name: service_cpp_pkg
30 | template: ros2_cpp_pkg
31 | has_service_server: true
32 | auto_shutdown: true
33 | - package_name: action_cpp_pkg
34 | template: ros2_cpp_pkg
35 | has_action_server: true
36 | auto_shutdown: true
37 | - package_name: timer_cpp_pkg
38 | template: ros2_cpp_pkg
39 | has_timer: true
40 | auto_shutdown: true
41 | - package_name: no_params_cpp_pkg
42 | template: ros2_cpp_pkg
43 | has_params: false
44 | auto_shutdown: true
45 | - package_name: no_launch_file_cpp_pkg
46 | template: ros2_cpp_pkg
47 | has_launch_file: false
48 | auto_shutdown: true
49 | - package_name: all_cpp_pkg
50 | template: ros2_cpp_pkg
51 | is_component: true
52 | is_lifecycle: true
53 | has_service_server: true
54 | has_action_server: true
55 | has_timer: true
56 | auto_shutdown: true
57 | - package_name: default_python_pkg
58 | template: ros2_python_pkg
59 | auto_shutdown: true
60 | - package_name: service_python_pkg
61 | template: ros2_python_pkg
62 | has_service_server: true
63 | auto_shutdown: true
64 | - package_name: action_python_pkg
65 | template: ros2_python_pkg
66 | has_action_server: true
67 | auto_shutdown: true
68 | - package_name: timer_python_pkg
69 | template: ros2_python_pkg
70 | has_timer: true
71 | auto_shutdown: true
72 | - package_name: no_params_python_pkg
73 | template: ros2_python_pkg
74 | has_params: false
75 | auto_shutdown: true
76 | - package_name: no_launch_file_python_pkg
77 | template: ros2_python_pkg
78 | has_launch_file: false
79 | auto_shutdown: true
80 | - package_name: all_python_pkg
81 | template: ros2_python_pkg
82 | is_lifecycle: true
83 | has_service_server: true
84 | has_action_server: true
85 | has_timer: true
86 | auto_shutdown: true
87 | - package_name: interfaces_pkg
88 | template: ros2_interfaces_pkg
89 | steps:
90 | - name: Checkout code
91 | uses: actions/checkout@v4
92 | - name: Set up Python
93 | uses: actions/setup-python@v5
94 | with:
95 | python-version: "3.10"
96 | - name: Install dependencies
97 | run: |
98 | pip install "copier~=9.2.0" "jinja2-strcase~=0.0.2"
99 | - name: Configure git to run copier
100 | run: |
101 | git config --global user.name "dummy"
102 | git config --global user.email "dummy@dummy.com"
103 | - name: Generate packages
104 | run: |
105 | copier copy --trust --defaults \
106 | -d template=${{ matrix.template }} \
107 | -d auto_shutdown=${{ matrix.auto_shutdown }} \
108 | -d package_name=${{ matrix.package_name }} \
109 | $( [ "${{ matrix.is_component }}" = "true" ] && echo "-d is_component=true" ) \
110 | $( [ "${{ matrix.is_lifecycle }}" = "true" ] && echo "-d is_lifecycle=true" ) \
111 | $( [ -n "${{ matrix.executor_type }}" ] && echo "-d executor_type=${{ matrix.executor_type }}" ) \
112 | $( [ "${{ matrix.has_service_server }}" = "true" ] && echo "-d has_service_server=true" ) \
113 | $( [ "${{ matrix.has_action_server }}" = "true" ] && echo "-d has_action_server=true" ) \
114 | $( [ "${{ matrix.has_timer }}" = "true" ] && echo "-d has_timer=true" ) \
115 | $( [ "${{ matrix.has_params }}" = "false" ] && echo "-d has_params=false" ) \
116 | $( [ "${{ matrix.has_launch_file }}" = "false" ] && echo "-d has_launch_file=false" ) \
117 | . packages
118 | - name: Upload artifacts
119 | uses: actions/upload-artifact@v4
120 | with:
121 | name: ${{ matrix.package_name }}
122 | path: "packages/${{ matrix.package_name }}*"
123 |
124 | build:
125 | if: (github.event_name != 'pull_request' && ! github.event.pull_request.head.repo.fork) || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork)
126 | runs-on: ubuntu-latest
127 | container:
128 | image: rwthika/ros2:jazzy
129 | needs: generate
130 | strategy:
131 | matrix:
132 | include:
133 | - package: default_cpp_pkg
134 | command: ros2 launch default_cpp_pkg default_cpp_pkg_launch.py
135 | - package: component_cpp_pkg
136 | command: ros2 launch component_cpp_pkg component_cpp_pkg_launch.py
137 | - package: lifecycle_cpp_pkg
138 | command: ros2 launch lifecycle_cpp_pkg lifecycle_cpp_pkg_launch.py
139 | - package: multi_threaded_cpp_pkg
140 | command: ros2 launch multi_threaded_cpp_pkg multi_threaded_cpp_pkg_launch.py
141 | - package: service_cpp_pkg
142 | command: ros2 launch service_cpp_pkg service_cpp_pkg_launch.py
143 | - package: action_cpp_pkg
144 | command: ros2 launch action_cpp_pkg action_cpp_pkg_launch.py
145 | - package: timer_cpp_pkg
146 | command: ros2 launch timer_cpp_pkg timer_cpp_pkg_launch.py
147 | - package: no_params_cpp_pkg
148 | command: ros2 launch no_params_cpp_pkg no_params_cpp_pkg_launch.py
149 | - package: no_launch_file_cpp_pkg
150 | command: ros2 run no_launch_file_cpp_pkg no_launch_file_cpp_pkg --ros-args -p param:=1.0
151 | - package: all_cpp_pkg
152 | command: ros2 launch all_cpp_pkg all_cpp_pkg_launch.py
153 | - package: default_python_pkg
154 | command: ros2 launch default_python_pkg default_python_pkg_launch.py
155 | - package: service_python_pkg
156 | command: ros2 launch service_python_pkg service_python_pkg_launch.py
157 | - package: action_python_pkg
158 | command: ros2 launch action_python_pkg action_python_pkg_launch.py
159 | - package: timer_python_pkg
160 | command: ros2 launch timer_python_pkg timer_python_pkg_launch.py
161 | - package: no_params_python_pkg
162 | command: ros2 launch no_params_python_pkg no_params_python_pkg_launch.py
163 | - package: no_launch_file_python_pkg
164 | command: ros2 run no_launch_file_python_pkg no_launch_file_python_pkg --ros-args -p param:=1.0
165 | - package: all_python_pkg
166 | command: ros2 launch all_python_pkg all_python_pkg_launch.py
167 | - package: interfaces_pkg
168 | command: |
169 | ros2 interface show interfaces_pkg/msg/Message && \
170 | ros2 interface show interfaces_pkg/srv/Service && \
171 | ros2 interface show interfaces_pkg/action/Action
172 | steps:
173 | - name: Checkout code
174 | uses: actions/checkout@v4
175 | - name: Download artifacts
176 | uses: actions/download-artifact@v4
177 | - name: Build
178 | shell: bash
179 | run: |
180 | source /opt/ros/$ROS_DISTRO/setup.bash
181 | colcon build --packages-up-to ${{ matrix.package }}
182 | - name: Run
183 | shell: bash
184 | run: |
185 | source install/setup.bash
186 | ${{ matrix.command }}
187 |
--------------------------------------------------------------------------------
/samples/ros2_cpp_lifecycle_pkg/src/ros2_cpp_node.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include
5 |
6 |
7 | namespace ros2_cpp_lifecycle_pkg {
8 |
9 |
10 | Ros2CppNode::Ros2CppNode() : rclcpp_lifecycle::LifecycleNode("ros2_cpp_node") {
11 |
12 | int startup_state = lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE;
13 | this->declareAndLoadParameter("startup_state", startup_state, "Initial lifecycle state", false, false, false);
14 | if (startup_state > lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED)
15 | this->trigger_transition(lifecycle_msgs::msg::Transition::TRANSITION_CONFIGURE);
16 | if (startup_state > lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE)
17 | this->trigger_transition(lifecycle_msgs::msg::Transition::TRANSITION_ACTIVATE);
18 | }
19 |
20 |
21 | template
22 | void Ros2CppNode::declareAndLoadParameter(const std::string& name,
23 | T& param,
24 | const std::string& description,
25 | const bool add_to_auto_reconfigurable_params,
26 | const bool is_required,
27 | const bool read_only,
28 | const std::optional& from_value,
29 | const std::optional& to_value,
30 | const std::optional& step_value,
31 | const std::string& additional_constraints) {
32 |
33 | rcl_interfaces::msg::ParameterDescriptor param_desc;
34 | param_desc.description = description;
35 | param_desc.additional_constraints = additional_constraints;
36 | param_desc.read_only = read_only;
37 |
38 | auto type = rclcpp::ParameterValue(param).get_type();
39 |
40 | if (from_value.has_value() && to_value.has_value()) {
41 | if constexpr(std::is_integral_v) {
42 | rcl_interfaces::msg::IntegerRange range;
43 | range.set__from_value(static_cast(from_value.value())).set__to_value(static_cast(to_value.value()));
44 | if (step_value.has_value()) range.set__step(static_cast(step_value.value()));
45 | param_desc.integer_range = {range};
46 | } else if constexpr(std::is_floating_point_v) {
47 | rcl_interfaces::msg::FloatingPointRange range;
48 | range.set__from_value(static_cast(from_value.value())).set__to_value(static_cast(to_value.value()));
49 | if (step_value.has_value()) range.set__step(static_cast(step_value.value()));
50 | param_desc.floating_point_range = {range};
51 | } else {
52 | RCLCPP_WARN(this->get_logger(), "Parameter type of parameter '%s' does not support specifying a range", name.c_str());
53 | }
54 | }
55 |
56 | this->declare_parameter(name, type, param_desc);
57 |
58 | try {
59 | param = this->get_parameter(name).get_value();
60 | std::stringstream ss;
61 | ss << "Loaded parameter '" << name << "': ";
62 | if constexpr(is_vector_v) {
63 | ss << "[";
64 | for (const auto& element : param) ss << element << (&element != ¶m.back() ? ", " : "");
65 | ss << "]";
66 | } else {
67 | ss << param;
68 | }
69 | RCLCPP_INFO_STREAM(this->get_logger(), ss.str());
70 | } catch (rclcpp::exceptions::ParameterUninitializedException&) {
71 | if (is_required) {
72 | RCLCPP_FATAL_STREAM(this->get_logger(), "Missing required parameter '" << name << "', exiting");
73 | exit(EXIT_FAILURE);
74 | } else {
75 | std::stringstream ss;
76 | ss << "Missing parameter '" << name << "', using default value: ";
77 | if constexpr(is_vector_v) {
78 | ss << "[";
79 | for (const auto& element : param) ss << element << (&element != ¶m.back() ? ", " : "");
80 | ss << "]";
81 | } else {
82 | ss << param;
83 | }
84 | RCLCPP_WARN_STREAM(this->get_logger(), ss.str());
85 | this->set_parameters({rclcpp::Parameter(name, rclcpp::ParameterValue(param))});
86 | }
87 | }
88 |
89 | if (add_to_auto_reconfigurable_params) {
90 | std::function setter = [¶m](const rclcpp::Parameter& p) {
91 | param = p.get_value();
92 | };
93 | auto_reconfigurable_params_.push_back(std::make_tuple(name, setter));
94 | }
95 | }
96 |
97 |
98 | rcl_interfaces::msg::SetParametersResult Ros2CppNode::parametersCallback(const std::vector& parameters) {
99 |
100 | for (const auto& param : parameters) {
101 | for (auto& auto_reconfigurable_param : auto_reconfigurable_params_) {
102 | if (param.get_name() == std::get<0>(auto_reconfigurable_param)) {
103 | std::get<1>(auto_reconfigurable_param)(param);
104 | RCLCPP_INFO(this->get_logger(), "Reconfigured parameter '%s' to: %s", param.get_name().c_str(), param.value_to_string().c_str());
105 | break;
106 | }
107 | }
108 | }
109 |
110 | rcl_interfaces::msg::SetParametersResult result;
111 | result.successful = true;
112 |
113 | return result;
114 | }
115 |
116 |
117 | void Ros2CppNode::setup() {
118 |
119 | // callback for dynamic parameter configuration
120 | parameters_callback_ = this->add_on_set_parameters_callback(std::bind(&Ros2CppNode::parametersCallback, this, std::placeholders::_1));
121 |
122 | // subscriber for handling incoming messages
123 | subscriber_ = this->create_subscription("~/input", 10, std::bind(&Ros2CppNode::topicCallback, this, std::placeholders::_1));
124 | RCLCPP_INFO(this->get_logger(), "Subscribed to '%s'", subscriber_->get_topic_name());
125 |
126 | // publisher for publishing outgoing messages
127 | publisher_ = this->create_publisher("~/output", 10);
128 | RCLCPP_INFO(this->get_logger(), "Publishing to '%s'", publisher_->get_topic_name());
129 | }
130 |
131 |
132 | void Ros2CppNode::topicCallback(const std_msgs::msg::Int32::ConstSharedPtr& msg) {
133 |
134 | RCLCPP_INFO(this->get_logger(), "Message received: '%d'", msg->data);
135 |
136 | // publish message
137 | std_msgs::msg::Int32 out_msg;
138 | out_msg.data = msg->data;
139 | publisher_->publish(out_msg);
140 | RCLCPP_INFO(this->get_logger(), "Message published: '%d'", out_msg.data);
141 | }
142 |
143 |
144 | rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn Ros2CppNode::on_configure(const rclcpp_lifecycle::State& state) {
145 |
146 | RCLCPP_INFO(get_logger(), "Configuring to enter 'inactive' state from '%s' state", state.label().c_str());
147 |
148 | this->declareAndLoadParameter("param", param_, "TODO", true, false, false, 0.0, 10.0, 1.0);
149 | setup();
150 |
151 | return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
152 | }
153 |
154 |
155 | rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn Ros2CppNode::on_activate(const rclcpp_lifecycle::State& state) {
156 |
157 | RCLCPP_INFO(get_logger(), "Activating to enter 'active' state from '%s' state", state.label().c_str());
158 |
159 | publisher_->on_activate();
160 |
161 | return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
162 | }
163 |
164 |
165 | rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn Ros2CppNode::on_deactivate(const rclcpp_lifecycle::State& state) {
166 |
167 | RCLCPP_INFO(get_logger(), "Deactivating to enter 'inactive' state from '%s' state", state.label().c_str());
168 |
169 | publisher_->on_deactivate();
170 |
171 | return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
172 | }
173 |
174 |
175 | rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn Ros2CppNode::on_cleanup(const rclcpp_lifecycle::State& state) {
176 |
177 | RCLCPP_INFO(get_logger(), "Cleaning up to enter 'unconfigured' state from '%s' state", state.label().c_str());
178 |
179 | subscriber_.reset();
180 | publisher_.reset();
181 | parameters_callback_.reset();
182 |
183 | return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
184 | }
185 |
186 |
187 | rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn Ros2CppNode::on_shutdown(const rclcpp_lifecycle::State& state) {
188 |
189 | RCLCPP_INFO(get_logger(), "Shutting down to enter 'finalized' state from '%s' state", state.label().c_str());
190 |
191 | if (state.id() >= lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE)
192 | on_deactivate(state);
193 | if (state.id() >= lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE)
194 | on_cleanup(state);
195 |
196 | return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
197 | }
198 |
199 |
200 | }
201 |
202 |
203 | int main(int argc, char *argv[]) {
204 |
205 | rclcpp::init(argc, argv);
206 | auto node = std::make_shared();
207 | rclcpp::executors::SingleThreadedExecutor executor;
208 | RCLCPP_INFO(node->get_logger(), "Spinning node '%s' with %s", node->get_fully_qualified_name(), "SingleThreadedExecutor");
209 | executor.add_node(node->get_node_base_interface());
210 | executor.spin();
211 | rclcpp::shutdown();
212 |
213 | return 0;
214 | }
215 |
--------------------------------------------------------------------------------
/templates/ros2_cpp_pkg/{{ package_name }}/include/{{ package_name }}/{{ node_name }}.hpp.jinja:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | {% if is_lifecycle %}
8 | #include
9 | #include
10 | {% endif %}
11 | #include
12 | {% if has_action_server %}
13 | #include
14 | {% endif %}
15 | {% if is_lifecycle %}
16 | #include
17 | #include
18 | {% endif %}
19 | #include
20 | {% if has_service_server %}
21 | #include
22 | {% endif %}
23 | {% if has_action_server %}
24 |
25 | #include <{{ package_name }}_interfaces/action/fibonacci.hpp>
26 | {% endif %}
27 |
28 |
29 | namespace {{ package_name }} {
30 |
31 | {% if has_params or is_lifecycle %}
32 | template struct is_vector : std::false_type {};
33 | template struct is_vector< std::vector > : std::true_type {};
34 | template inline constexpr bool is_vector_v = is_vector::value;
35 | {% endif %}
36 |
37 |
38 | /**
39 | * @brief {{ node_class_name }} class
40 | */
41 | {% if is_lifecycle %}
42 | class {{ node_class_name }} : public rclcpp_lifecycle::LifecycleNode {
43 | {% else %}
44 | class {{ node_class_name }} : public rclcpp::Node {
45 | {% endif %}
46 |
47 | public:
48 |
49 | {% if is_component %}
50 | /**
51 | * @brief Constructor
52 | *
53 | * @param options node options
54 | */
55 | explicit {{ node_class_name }}(const rclcpp::NodeOptions& options);
56 | {% else %}
57 | {{ node_class_name }}();
58 | {% if executor_type == "MultiThreadedExecutor" %}
59 |
60 | /**
61 | * @brief Number of threads for MultiThreadedExecutor
62 | */
63 | int num_threads_ = 1;
64 | {% endif %}
65 | {% endif %}
66 | {% if is_lifecycle %}
67 |
68 | protected:
69 |
70 | /**
71 | * @brief Processes 'configuring' transitions to 'inactive' state
72 | *
73 | * @param state previous state
74 | * @return transition result
75 | */
76 | rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_configure(const rclcpp_lifecycle::State& state) override;
77 |
78 | /**
79 | * @brief Processes 'activating' transitions to 'active' state
80 | *
81 | * @param state previous state
82 | * @return transition result
83 | */
84 | rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_activate(const rclcpp_lifecycle::State& state) override;
85 |
86 | /**
87 | * @brief Processes 'deactivating' transitions to 'inactive' state
88 | *
89 | * @param state previous state
90 | * @return transition result
91 | */
92 | rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_deactivate(const rclcpp_lifecycle::State& state) override;
93 |
94 | /**
95 | * @brief Processes 'cleaningup' transitions to 'unconfigured' state
96 | *
97 | * @param state previous state
98 | * @return transition result
99 | */
100 | rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_cleanup(const rclcpp_lifecycle::State& state) override;
101 |
102 | /**
103 | * @brief Processes 'shuttingdown' transitions to 'finalized' state
104 | *
105 | * @param state previous state
106 | * @return transition result
107 | */
108 | rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn on_shutdown(const rclcpp_lifecycle::State& state) override;
109 | {% endif %}
110 |
111 | private:
112 | {% if has_params or is_lifecycle %}
113 |
114 | /**
115 | * @brief Declares and loads a ROS parameter
116 | *
117 | * @param name name
118 | * @param param parameter variable to load into
119 | * @param description description
120 | * @param add_to_auto_reconfigurable_params enable reconfiguration of parameter
121 | * @param is_required whether failure to load parameter will stop node
122 | * @param read_only set parameter to read-only
123 | * @param from_value parameter range minimum
124 | * @param to_value parameter range maximum
125 | * @param step_value parameter range step
126 | * @param additional_constraints additional constraints description
127 | */
128 | template
129 | void declareAndLoadParameter(const std::string &name,
130 | T ¶m,
131 | const std::string &description,
132 | const bool add_to_auto_reconfigurable_params = true,
133 | const bool is_required = false,
134 | const bool read_only = false,
135 | const std::optional &from_value = std::nullopt,
136 | const std::optional &to_value = std::nullopt,
137 | const std::optional &step_value = std::nullopt,
138 | const std::string &additional_constraints = "");
139 | {% endif %}
140 | {% if has_params %}
141 |
142 | /**
143 | * @brief Handles reconfiguration when a parameter value is changed
144 | *
145 | * @param parameters parameters
146 | * @return parameter change result
147 | */
148 | rcl_interfaces::msg::SetParametersResult parametersCallback(const std::vector& parameters);
149 | {% endif %}
150 |
151 | /**
152 | * @brief Sets up subscribers, publishers, etc. to configure the node
153 | */
154 | void setup();
155 | {% if has_subscriber %}
156 |
157 | /**
158 | * @brief Processes messages received by a subscriber
159 | *
160 | * @param msg message
161 | */
162 | void topicCallback(const std_msgs::msg::Int32::ConstSharedPtr& msg);
163 | {% endif %}
164 | {% if has_service_server %}
165 |
166 | /**
167 | * @brief Processes service requests
168 | *
169 | * @param request service request
170 | * @param response service response
171 | */
172 | void serviceCallback(const std::shared_ptrrequest, std::shared_ptr response);
173 | {% endif %}
174 | {% if has_action_server %}
175 |
176 | /**
177 | * @brief Processes action goal requests
178 | *
179 | * @param uuid unique goal identifier
180 | * @param goal action goal
181 | * @return goal response
182 | */
183 | rclcpp_action::GoalResponse actionHandleGoal(const rclcpp_action::GoalUUID& uuid, std::shared_ptr goal);
184 |
185 | /**
186 | * @brief Processes action cancel requests
187 | *
188 | * @param goal_handle action goal handle
189 | * @return cancel response
190 | */
191 | rclcpp_action::CancelResponse actionHandleCancel(const std::shared_ptr> goal_handle);
192 |
193 | /**
194 | * @brief Processes accepted action goal requests
195 | *
196 | * @param goal_handle action goal handle
197 | */
198 | void actionHandleAccepted(const std::shared_ptr> goal_handle);
199 |
200 | /**
201 | * @brief Executes an action
202 | *
203 | * @param goal_handle action goal handle
204 | */
205 | void actionExecute(const std::shared_ptr> goal_handle);
206 | {% endif %}
207 | {% if has_timer %}
208 |
209 | /**
210 | * @brief Processes timer triggers
211 | */
212 | void timerCallback();
213 | {% endif %}
214 | {% if auto_shutdown %}
215 |
216 | /**
217 | * @brief Processes timer triggers to auto-shutdown the node
218 | */
219 | void autoShutdownTimerCallback();
220 | {% endif %}
221 |
222 | private:
223 | {% if has_params or is_lifecycle %}
224 |
225 | /**
226 | * @brief Auto-reconfigurable parameters for dynamic reconfiguration
227 | */
228 | std::vector>> auto_reconfigurable_params_;
229 | {% endif %}
230 | {% if has_params %}
231 |
232 | /**
233 | * @brief Callback handle for dynamic parameter reconfiguration
234 | */
235 | OnSetParametersCallbackHandle::SharedPtr parameters_callback_;
236 | {% endif %}
237 | {% if has_subscriber %}
238 |
239 | /**
240 | * @brief Subscriber
241 | */
242 | rclcpp::Subscription::SharedPtr subscriber_;
243 | {% endif %}
244 | {% if has_publisher %}
245 |
246 | /**
247 | * @brief Publisher
248 | */
249 | {% if is_lifecycle %}
250 | rclcpp_lifecycle::LifecyclePublisher::SharedPtr publisher_;
251 | {% else %}
252 | rclcpp::Publisher::SharedPtr publisher_;
253 | {% endif %}
254 | {% endif %}
255 | {% if has_service_server %}
256 |
257 | /**
258 | * @brief Service server
259 | */
260 | rclcpp::Service::SharedPtr service_server_;
261 | {% endif %}
262 | {% if has_action_server %}
263 |
264 | /**
265 | * @brief Action server
266 | */
267 | rclcpp_action::Server<{{ package_name }}_interfaces::action::Fibonacci>::SharedPtr action_server_;
268 | {% endif %}
269 | {% if has_timer %}
270 |
271 | /**
272 | * @brief Timer
273 | */
274 | rclcpp::TimerBase::SharedPtr timer_;
275 | {% endif %}
276 | {% if has_params %}
277 |
278 | /**
279 | * @brief Dummy parameter (parameter)
280 | */
281 | double param_ = 1.0;
282 | {% endif %}
283 | {% if auto_shutdown %}
284 |
285 | /**
286 | * @brief Timer for auto-shutdown
287 | */
288 | rclcpp::TimerBase::SharedPtr auto_shutdown_timer_;
289 | {% endif %}
290 | };
291 |
292 |
293 | }
294 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # *ros2-pkg-create* – Powerful ROS 2 Package Generator
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | *ros2-pkg-create* is an interactive CLI tool for quickly generating ROS 2 packages from basic pub/sub nodes to complex lifecycle components. It is meant to replace the official [`ros2 pkg create`](https://docs.ros.org/en/jazzy/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.html#create-a-package) command.
11 |
12 | - [Quick Demo](#quick-demo)
13 | - [Installation](#installation)
14 | - [Templates \& Features](#templates--features)
15 | - [Usage](#usage)
16 | - [Acknowledgements](#acknowledgements)
17 |
18 | > [!IMPORTANT]
19 | > This repository is open-sourced and maintained by the [**Institute for Automotive Engineering (ika) at RWTH Aachen University**](https://www.ika.rwth-aachen.de/).
20 | > **ROS is the backbone** of many research topics within our [*Vehicle Intelligence & Automated Driving*](https://www.ika.rwth-aachen.de/en/competences/fields-of-research/vehicle-intelligence-automated-driving.html) domain.
21 | > If you would like to learn more about how we can support your advanced driver assistance and automated driving efforts, feel free to reach out to us!
22 | > :email: ***opensource@ika.rwth-aachen.de***
23 |
24 |
25 | ## Quick Demo
26 |
27 | ```bash
28 | pip install ros2-pkg-create
29 | ros2-pkg-create --template ros2_cpp_pkg .
30 | ```
31 |
32 |
33 |
34 |
35 | ## Installation
36 |
37 | ```bash
38 | pip install ros2-pkg-create
39 |
40 | # (optional) bash auto-completion
41 | activate-global-python-argcomplete
42 | eval "$(register-python-argcomplete ros2-pkg-create)"
43 | ```
44 |
45 | > [!WARNING]
46 | > Outside of a virtual environment, *pip* may default to a user-site installation of executables to `~/.local/bin`, which may not be present in your shell's `PATH`. If running `ros2-pkg-create` errors with `ros2-pkg-create: command not found`, add the directory to your path. [*(More information)*](https://packaging.python.org/en/latest/tutorials/installing-packages/#installing-to-the-user-site)
47 | > ```bash
48 | > echo "export PATH=\$HOME/.local/bin:\$PATH" >> ~/.bashrc
49 | > source ~/.bashrc
50 | > ```
51 |
52 |
53 | ## Templates & Features
54 |
55 | *ros2-pkg-create* provides multiple templates, each covering a different questionnaire for generating all the components you need. See below for the list of supported features and questionnarie options. Note that all options can also be passed directly to the command, bypassing the interactive questionnaire (see [Usage](#usage)).
56 |
57 | - [C++ Package](#c-package---template-ros2_cpp_pkg)
58 | - [Python Package](#python-package---template-ros2_python_pkg)
59 | - [Interfaces Package](#interfaces-package---template-ros2_interfaces_pkg)
60 |
61 | ### C++ Package (`--template ros2_cpp_pkg`)
62 |
63 | **Supported Features:** publisher, subscriber, parameter loading, launch file, service server, action server, timer callback, component, lifecycle node, executor, docker-ros
64 |
65 |
66 | Questionnaire
67 |
68 | - Package name
69 | - Description
70 | - Maintainer | Maintainer email
71 | - Author | Author email
72 | - License
73 | - Node name
74 | - Class name of node
75 | - Make it a component?
76 | - Make it a lifecycle node?
77 | - Type of Executor
78 | - Add a launch file? | Type of launch file
79 | - Add parameter loading?
80 | - Add a subscriber?
81 | - Add a publisher?
82 | - Add a service server?
83 | - Add an action server?
84 | - Add a timer callback?
85 | - Add the docker-ros CI integration?
86 |
87 |
88 | ### Python Package (`--template ros2_python_pkg`)
89 |
90 | **Supported Features:** publisher, subscriber, parameter loading, launch file, service server, action server, timer callback, docker-ros
91 |
92 |
93 | Questionnaire
94 |
95 | - Package name
96 | - Description
97 | - Maintainer | Maintainer email
98 | - Author | Author email
99 | - License
100 | - Node name
101 | - Class name of node
102 | - Add a launch file? | Type of launch file
103 | - Add parameter loading?
104 | - Add a subscriber?
105 | - Add a publisher?
106 | - Add a service server?
107 | - Add an action server?
108 | - Add a timer callback?
109 | - Add the docker-ros CI integration?
110 |
111 |
112 | ### Interfaces Package (`--template ros2_interfaces_pkg`)
113 |
114 | **Supported Features:** message, service, action
115 |
116 |
117 | Questionnaire
118 |
119 | - Package name
120 | - Description
121 | - Maintainer | Maintainer email
122 | - Author | Author email
123 | - License
124 | - Interfaces types
125 | - Message name
126 | - Service name
127 | - Action name
128 | - Add the docker-ros CI integration?
129 |
130 |
131 |
132 | ## Usage
133 |
134 | ```
135 | usage: ros2-pkg-create [-h] [--defaults] [--use-local-templates] --template {ros2_interfaces_pkg,ros2_cpp_pkg,ros2_python_pkg} [--package-name PACKAGE_NAME] [--description DESCRIPTION] [--maintainer MAINTAINER] [--maintainer-email MAINTAINER_EMAIL] [--author AUTHOR]
136 | [--author-email AUTHOR_EMAIL] [--license {Apache-2.0,BSL-1.0,BSD-2.0,BSD-2-Clause,BSD-3-Clause,GPL-3.0-only,LGPL-2.1-only,LGPL-3.0-only,MIT,MIT-0}] [--node-name NODE_NAME] [--node-class-name NODE_CLASS_NAME] [--is-component] [--no-is-component] [--is-lifecycle]
137 | [--no-is-lifecycle] [--executor-type {SingleThreadedExecutor,MultiThreadedExecutor,StaticSingleThreadedExecutor}] [--has-launch-file] [--no-has-launch-file] [--launch-file-type {xml,py,yml}] [--has-params] [--no-has-params] [--has-subscriber] [--no-has-subscriber]
138 | [--has-publisher] [--no-has-publisher] [--has-service-server] [--no-has-service-server] [--has-action-server] [--no-has-action-server] [--has-timer] [--no-has-timer] [--auto-shutdown] [--no-auto-shutdown] [--interface-types {Message,Service,Action}]
139 | [--msg-name MSG_NAME] [--srv-name SRV_NAME] [--action-name ACTION_NAME] [--has-docker-ros] [--docker-ros-type {github,gitlab}] [--version]
140 | destination
141 |
142 | Creates a ROS 2 package from templates
143 |
144 | positional arguments:
145 | destination Destination directory
146 |
147 | options:
148 | -h, --help show this help message and exit
149 | --defaults Use defaults for all options
150 | --use-local-templates
151 | Use locally installed templates instead of remotely pulling most recent ones
152 | --template {ros2_interfaces_pkg,ros2_cpp_pkg,ros2_python_pkg}
153 | Template
154 | --package-name PACKAGE_NAME
155 | Package name
156 | --description DESCRIPTION
157 | Description
158 | --maintainer MAINTAINER
159 | Maintainer
160 | --maintainer-email MAINTAINER_EMAIL
161 | Maintainer email
162 | --author AUTHOR Author
163 | --author-email AUTHOR_EMAIL
164 | Author email
165 | --license {Apache-2.0,BSL-1.0,BSD-2.0,BSD-2-Clause,BSD-3-Clause,GPL-3.0-only,LGPL-2.1-only,LGPL-3.0-only,MIT,MIT-0}
166 | License
167 | --node-name NODE_NAME
168 | Node name
169 | --node-class-name NODE_CLASS_NAME
170 | Class name of node
171 | --is-component Make it a component?
172 | --no-is-component
173 | --is-lifecycle Make it a lifecycle node?
174 | --no-is-lifecycle
175 | --executor-type {SingleThreadedExecutor,MultiThreadedExecutor,StaticSingleThreadedExecutor}
176 | Type of Executor
177 | --has-launch-file Add a launch file?
178 | --no-has-launch-file
179 | --launch-file-type {xml,py,yml}
180 | Type of launch file
181 | --has-params Add parameter loading
182 | --no-has-params
183 | --has-subscriber Add a subscriber?
184 | --no-has-subscriber
185 | --has-publisher Add a publisher?
186 | --no-has-publisher
187 | --has-service-server Add a service server?
188 | --no-has-service-server
189 | --has-action-server Add an action server?
190 | --no-has-action-server
191 | --has-timer Add a timer callback?
192 | --no-has-timer
193 | --auto-shutdown Automatically shutdown the node after launch (useful in CI/CD)?
194 | --no-auto-shutdown
195 | --interface-types {Message,Service,Action}
196 | Interfaces types
197 | --msg-name MSG_NAME Message name
198 | --srv-name SRV_NAME Service name
199 | --action-name ACTION_NAME
200 | Action name
201 | --has-docker-ros Add the docker-ros CI integration?
202 | --docker-ros-type {github,gitlab}
203 | Type of docker-ros CI integration
204 | --version show program's version number and exit
205 | ```
206 |
207 | ## Acknowledgements
208 |
209 | This work is accomplished within the projects [6GEM](https://6gem.de/en/) (FKZ 16KISK036K) and [autotech.agil](https://www.autotechagil.de/) (FKZ 01IS22088A). We acknowledge the financial support for the projects by the *Federal Ministry of Education and Research of Germany (BMBF)*.
210 |
--------------------------------------------------------------------------------
/samples/ros2_python_all_pkg/ros2_python_all_pkg/ros2_python_node.py:
--------------------------------------------------------------------------------
1 | import time
2 | from typing import Any, Optional, Union
3 |
4 | import rclpy
5 | from rclpy.action import ActionServer, CancelResponse, GoalResponse
6 | from rclpy.node import Node
7 | import rclpy.exceptions
8 | from rcl_interfaces.msg import (FloatingPointRange, IntegerRange, ParameterDescriptor, SetParametersResult)
9 | from std_msgs.msg import Int32
10 | from std_srvs.srv import SetBool
11 | from ros2_python_all_pkg_interfaces.action import Fibonacci
12 |
13 |
14 | class Ros2PythonNode(Node):
15 |
16 | def __init__(self):
17 | """Constructor"""
18 | super().__init__("ros2_python_node")
19 |
20 | self.subscriber = None
21 |
22 | self.publisher = None
23 |
24 | self.auto_reconfigurable_params: list[str] = []
25 | self.param = self.declare_and_load_parameter(name="param",
26 | param_type=rclpy.Parameter.Type.DOUBLE,
27 | description="TODO",
28 | default=1.0,
29 | from_value=0.0,
30 | to_value=10.0,
31 | step_value=0.1)
32 |
33 | self.setup()
34 |
35 | def declare_and_load_parameter(self,
36 | name: str,
37 | param_type: rclpy.Parameter.Type,
38 | description: str,
39 | default: Optional[Any] = None,
40 | add_to_auto_reconfigurable_params: bool = True,
41 | is_required: bool = False,
42 | read_only: bool = False,
43 | from_value: Optional[Union[int, float]] = None,
44 | to_value: Optional[Union[int, float]] = None,
45 | step_value: Optional[Union[int, float]] = None,
46 | additional_constraints: str = "") -> Any:
47 | """Declares and loads a ROS parameter
48 |
49 | Args:
50 | name (str): name
51 | param_type (rclpy.Parameter.Type): parameter type
52 | description (str): description
53 | default (Optional[Any], optional): default value
54 | add_to_auto_reconfigurable_params (bool, optional): enable reconfiguration of parameter
55 | is_required (bool, optional): whether failure to load parameter will stop node
56 | read_only (bool, optional): set parameter to read-only
57 | from_value (Optional[Union[int, float]], optional): parameter range minimum
58 | to_value (Optional[Union[int, float]], optional): parameter range maximum
59 | step_value (Optional[Union[int, float]], optional): parameter range step
60 | additional_constraints (str, optional): additional constraints description
61 |
62 | Returns:
63 | Any: parameter value
64 | """
65 |
66 | # declare parameter
67 | param_desc = ParameterDescriptor()
68 | param_desc.description = description
69 | param_desc.additional_constraints = additional_constraints
70 | param_desc.read_only = read_only
71 | if from_value is not None and to_value is not None:
72 | if param_type == rclpy.Parameter.Type.INTEGER:
73 | range = IntegerRange(from_value=from_value, to_value=to_value)
74 | if step_value is not None:
75 | range.step = step_value
76 | param_desc.integer_range = [range]
77 | elif param_type == rclpy.Parameter.Type.DOUBLE:
78 | range = FloatingPointRange(from_value=from_value, to_value=to_value)
79 | if step_value is not None:
80 | range.step = step_value
81 | param_desc.floating_point_range = [range]
82 | else:
83 | self.get_logger().warn(f"Parameter type of parameter '{name}' does not support specifying a range")
84 | self.declare_parameter(name, param_type, param_desc)
85 |
86 | # load parameter
87 | try:
88 | param = self.get_parameter(name).value
89 | self.get_logger().info(f"Loaded parameter '{name}': {param}")
90 | except rclpy.exceptions.ParameterUninitializedException:
91 | if is_required:
92 | self.get_logger().fatal(f"Missing required parameter '{name}', exiting")
93 | raise SystemExit(1)
94 | else:
95 | self.get_logger().warn(f"Missing parameter '{name}', using default value: {default}")
96 | param = default
97 | self.set_parameters([rclpy.Parameter(name=name, value=param)])
98 |
99 | # add parameter to auto-reconfigurable parameters
100 | if add_to_auto_reconfigurable_params:
101 | self.auto_reconfigurable_params.append(name)
102 |
103 | return param
104 |
105 | def parameters_callback(self,
106 | parameters: list[rclpy.Parameter]) -> SetParametersResult:
107 | """Handles reconfiguration when a parameter value is changed
108 |
109 | Args:
110 | parameters (list[rclpy.Parameter]): parameters
111 |
112 | Returns:
113 | SetParametersResult: parameter change result
114 | """
115 |
116 | for param in parameters:
117 | if param.name in self.auto_reconfigurable_params:
118 | setattr(self, param.name, param.value)
119 | self.get_logger().info(f"Reconfigured parameter '{param.name}' to: {param.value}")
120 |
121 | result = SetParametersResult()
122 | result.successful = True
123 |
124 | return result
125 |
126 | def setup(self):
127 | """Sets up subscribers, publishers, etc. to configure the node"""
128 |
129 | # callback for dynamic parameter configuration
130 | self.add_on_set_parameters_callback(self.parameters_callback)
131 |
132 | # subscriber for handling incoming messages
133 | self.subscriber = self.create_subscription(Int32,
134 | "~/input",
135 | self.topic_callback,
136 | qos_profile=10)
137 | self.get_logger().info(f"Subscribed to '{self.subscriber.topic_name}'")
138 |
139 | # publisher for publishing outgoing messages
140 | self.publisher = self.create_publisher(Int32,
141 | "~/output",
142 | qos_profile=10)
143 | self.get_logger().info(f"Publishing to '{self.publisher.topic_name}'")
144 |
145 | # service server for handling service calls
146 | self.service_server = self.create_service(SetBool, "~/service", self.service_callback)
147 |
148 | # action server for handling action goal requests
149 | self.action_server = ActionServer(
150 | self,
151 | Fibonacci,
152 | "~/action",
153 | execute_callback=self.action_execute,
154 | goal_callback=self.action_handle_goal,
155 | cancel_callback=self.action_handle_cancel,
156 | handle_accepted_callback=self.action_handle_accepted
157 | )
158 |
159 | # timer for repeatedly invoking a callback to publish messages
160 | self.timer = self.create_timer(1.0, self.timer_callback)
161 |
162 | def topic_callback(self, msg: Int32):
163 | """Processes messages received by a subscriber
164 |
165 | Args:
166 | msg (Int32): message
167 | """
168 |
169 | self.get_logger().info(f"Message received: '{msg.data}'")
170 |
171 | def service_callback(self, request: SetBool.Request, response: SetBool.Response) -> SetBool.Response:
172 | """Processes service requests
173 |
174 | Args:
175 | request (SetBool.Request): service request
176 | response (SetBool.Response): service response
177 |
178 | Returns:
179 | SetBool.Response: service response
180 | """
181 |
182 | self.get_logger().info("Received service request")
183 | response.success = True
184 |
185 | return response
186 |
187 | def action_handle_goal(self, goal: Fibonacci.Goal) -> GoalResponse:
188 | """Processes action goal requests
189 |
190 | Args:
191 | goal (Fibonacci.Goal): action goal
192 |
193 | Returns:
194 | GoalResponse: goal response
195 | """
196 |
197 | self.get_logger().info("Received action goal request")
198 |
199 | return GoalResponse.ACCEPT
200 |
201 | def action_handle_cancel(self, goal: Fibonacci.Goal) -> CancelResponse:
202 | """Processes action cancel requests
203 |
204 | Args:
205 | goal (Fibonacci.Goal): action goal
206 |
207 | Returns:
208 | CancelResponse: cancel response
209 | """
210 |
211 | self.get_logger().info("Received request to cancel action goal")
212 |
213 | return CancelResponse.ACCEPT
214 |
215 | def action_handle_accepted(self, goal: Fibonacci.Goal):
216 | """Processes accepted action goal requests
217 |
218 | Args:
219 | goal (Fibonacci.Goal): action goal
220 | """
221 |
222 | # execute action in a separate thread to avoid blocking
223 | goal.execute()
224 |
225 | async def action_execute(self, goal: Fibonacci.Goal) -> Fibonacci.Result:
226 | """Executes an action
227 |
228 | Args:
229 | goal (Fibonacci.Goal): action goal
230 |
231 | Returns:
232 | Fibonacci.Result: action goal result
233 | """
234 |
235 | self.get_logger().info("Executing action goal")
236 |
237 | # define a sleeping rate between computing individual Fibonacci numbers
238 | loop_rate = 1
239 |
240 | # create the action feedback and result
241 | feedback = Fibonacci.Feedback()
242 | result = Fibonacci.Result()
243 |
244 | # initialize the Fibonacci sequence
245 | feedback.partial_sequence = [0, 1]
246 |
247 | # compute the Fibonacci sequence up to the requested order n
248 | for i in range(1, goal.request.order):
249 |
250 | # cancel, if requested
251 | if goal.is_cancel_requested:
252 | result.sequence = feedback.partial_sequence
253 | goal.canceled()
254 | self.get_logger().info("Action goal canceled")
255 | return result
256 |
257 | # compute the next Fibonacci number
258 | feedback.partial_sequence.append(feedback.partial_sequence[i] + feedback.partial_sequence[i-1])
259 |
260 | # publish the current sequence as action feedback
261 | goal.publish_feedback(feedback)
262 | self.get_logger().info("Publishing action feedback")
263 |
264 | # sleep before computing the next Fibonacci number
265 | time.sleep(loop_rate)
266 |
267 | # finish by publishing the action result
268 | if rclpy.ok():
269 | result.sequence = feedback.partial_sequence
270 | goal.succeed()
271 | self.get_logger().info("Goal succeeded")
272 |
273 | return result
274 |
275 | def timer_callback(self):
276 | """Processes timer triggers
277 | """
278 |
279 | self.get_logger().info("Timer triggered")
280 |
281 |
282 | def main():
283 |
284 | rclpy.init()
285 | node = Ros2PythonNode()
286 | try:
287 | rclpy.spin(node)
288 | except KeyboardInterrupt:
289 | pass
290 | finally:
291 | node.destroy_node()
292 | rclpy.try_shutdown()
293 |
294 |
295 | if __name__ == '__main__':
296 | main()
297 |
--------------------------------------------------------------------------------
/templates/ros2_python_pkg/{{ package_name }}/{{ package_name }}/{{ node_name }}.py.jinja:
--------------------------------------------------------------------------------
1 | {% if has_action_server %}
2 | import time
3 | {% endif %}
4 | from typing import Any, Optional, Union
5 |
6 | import rclpy
7 | {% if has_action_server %}
8 | from rclpy.action import ActionServer, CancelResponse, GoalResponse
9 | {% endif %}
10 | from rclpy.node import Node
11 | {% if has_params %}
12 | import rclpy.exceptions
13 | from rcl_interfaces.msg import (FloatingPointRange, IntegerRange, ParameterDescriptor, SetParametersResult)
14 | {% endif %}
15 | {% if has_subscriber or has_publisher %}
16 | from std_msgs.msg import Int32
17 | {% endif %}
18 | {% if has_service_server %}
19 | from std_srvs.srv import SetBool
20 | {% endif %}
21 | {% if has_action_server %}
22 | from {{ package_name }}_interfaces.action import Fibonacci
23 | {% endif %}
24 |
25 |
26 | class {{ node_class_name }}(Node):
27 |
28 | def __init__(self):
29 | """Constructor"""
30 | super().__init__("{{ node_name }}")
31 | {% if has_subscriber %}
32 |
33 | self.subscriber = None
34 | {% endif %}
35 | {% if has_publisher %}
36 |
37 | self.publisher = None
38 | {% endif %}
39 | {% if has_params %}
40 |
41 | self.auto_reconfigurable_params: list[str] = []
42 | self.param = self.declare_and_load_parameter(name="param",
43 | param_type=rclpy.Parameter.Type.DOUBLE,
44 | description="TODO",
45 | default=1.0,
46 | from_value=0.0,
47 | to_value=10.0,
48 | step_value=0.1)
49 | {% endif %}
50 |
51 | self.setup()
52 | {% if has_params %}
53 |
54 | def declare_and_load_parameter(self,
55 | name: str,
56 | param_type: rclpy.Parameter.Type,
57 | description: str,
58 | default: Optional[Any] = None,
59 | add_to_auto_reconfigurable_params: bool = True,
60 | is_required: bool = False,
61 | read_only: bool = False,
62 | from_value: Optional[Union[int, float]] = None,
63 | to_value: Optional[Union[int, float]] = None,
64 | step_value: Optional[Union[int, float]] = None,
65 | additional_constraints: str = "") -> Any:
66 | """Declares and loads a ROS parameter
67 |
68 | Args:
69 | name (str): name
70 | param_type (rclpy.Parameter.Type): parameter type
71 | description (str): description
72 | default (Optional[Any], optional): default value
73 | add_to_auto_reconfigurable_params (bool, optional): enable reconfiguration of parameter
74 | is_required (bool, optional): whether failure to load parameter will stop node
75 | read_only (bool, optional): set parameter to read-only
76 | from_value (Optional[Union[int, float]], optional): parameter range minimum
77 | to_value (Optional[Union[int, float]], optional): parameter range maximum
78 | step_value (Optional[Union[int, float]], optional): parameter range step
79 | additional_constraints (str, optional): additional constraints description
80 |
81 | Returns:
82 | Any: parameter value
83 | """
84 |
85 | # declare parameter
86 | param_desc = ParameterDescriptor()
87 | param_desc.description = description
88 | param_desc.additional_constraints = additional_constraints
89 | param_desc.read_only = read_only
90 | if from_value is not None and to_value is not None:
91 | if param_type == rclpy.Parameter.Type.INTEGER:
92 | range = IntegerRange(from_value=from_value, to_value=to_value)
93 | if step_value is not None:
94 | range.step = step_value
95 | param_desc.integer_range = [range]
96 | elif param_type == rclpy.Parameter.Type.DOUBLE:
97 | range = FloatingPointRange(from_value=from_value, to_value=to_value)
98 | if step_value is not None:
99 | range.step = step_value
100 | param_desc.floating_point_range = [range]
101 | else:
102 | self.get_logger().warn(f"Parameter type of parameter '{name}' does not support specifying a range")
103 | self.declare_parameter(name, param_type, param_desc)
104 |
105 | # load parameter
106 | try:
107 | param = self.get_parameter(name).value
108 | self.get_logger().info(f"Loaded parameter '{name}': {param}")
109 | except rclpy.exceptions.ParameterUninitializedException:
110 | if is_required:
111 | self.get_logger().fatal(f"Missing required parameter '{name}', exiting")
112 | raise SystemExit(1)
113 | else:
114 | self.get_logger().warn(f"Missing parameter '{name}', using default value: {default}")
115 | param = default
116 | self.set_parameters([rclpy.Parameter(name=name, value=param)])
117 |
118 | # add parameter to auto-reconfigurable parameters
119 | if add_to_auto_reconfigurable_params:
120 | self.auto_reconfigurable_params.append(name)
121 |
122 | return param
123 |
124 | def parameters_callback(self,
125 | parameters: list[rclpy.Parameter]) -> SetParametersResult:
126 | """Handles reconfiguration when a parameter value is changed
127 |
128 | Args:
129 | parameters (list[rclpy.Parameter]): parameters
130 |
131 | Returns:
132 | SetParametersResult: parameter change result
133 | """
134 |
135 | for param in parameters:
136 | if param.name in self.auto_reconfigurable_params:
137 | setattr(self, param.name, param.value)
138 | self.get_logger().info(f"Reconfigured parameter '{param.name}' to: {param.value}")
139 |
140 | result = SetParametersResult()
141 | result.successful = True
142 |
143 | return result
144 | {% endif %}
145 |
146 | def setup(self):
147 | """Sets up subscribers, publishers, etc. to configure the node"""
148 | {% if has_params %}
149 |
150 | # callback for dynamic parameter configuration
151 | self.add_on_set_parameters_callback(self.parameters_callback)
152 | {% endif %}
153 | {% if has_subscriber %}
154 |
155 | # subscriber for handling incoming messages
156 | self.subscriber = self.create_subscription(Int32,
157 | "~/input",
158 | self.topic_callback,
159 | qos_profile=10)
160 | self.get_logger().info(f"Subscribed to '{self.subscriber.topic_name}'")
161 | {% endif %}
162 | {% if has_publisher %}
163 |
164 | # publisher for publishing outgoing messages
165 | self.publisher = self.create_publisher(Int32,
166 | "~/output",
167 | qos_profile=10)
168 | self.get_logger().info(f"Publishing to '{self.publisher.topic_name}'")
169 | {% endif %}
170 | {% if has_service_server %}
171 |
172 | # service server for handling service calls
173 | self.service_server = self.create_service(SetBool, "~/service", self.service_callback)
174 | {% endif %}
175 | {% if has_action_server %}
176 |
177 | # action server for handling action goal requests
178 | self.action_server = ActionServer(
179 | self,
180 | Fibonacci,
181 | "~/action",
182 | execute_callback=self.action_execute,
183 | goal_callback=self.action_handle_goal,
184 | cancel_callback=self.action_handle_cancel,
185 | handle_accepted_callback=self.action_handle_accepted
186 | )
187 | {% endif %}
188 | {% if has_timer %}
189 |
190 | # timer for repeatedly invoking a callback to publish messages
191 | self.timer = self.create_timer(1.0, self.timer_callback)
192 | {% endif %}
193 | {% if auto_shutdown %}
194 |
195 | self.auto_shutdown_timer = self.create_timer(3.0, self.auto_shutdown_timer_callback)
196 | {% endif %}
197 | {% if has_subscriber %}
198 |
199 | def topic_callback(self, msg: Int32):
200 | """Processes messages received by a subscriber
201 |
202 | Args:
203 | msg (Int32): message
204 | """
205 |
206 | self.get_logger().info(f"Message received: '{msg.data}'")
207 | {% endif %}
208 | {% if has_service_server %}
209 |
210 | def service_callback(self, request: SetBool.Request, response: SetBool.Response) -> SetBool.Response:
211 | """Processes service requests
212 |
213 | Args:
214 | request (SetBool.Request): service request
215 | response (SetBool.Response): service response
216 |
217 | Returns:
218 | SetBool.Response: service response
219 | """
220 |
221 | self.get_logger().info("Received service request")
222 | response.success = True
223 |
224 | return response
225 | {% endif %}
226 | {% if has_action_server %}
227 |
228 | def action_handle_goal(self, goal: Fibonacci.Goal) -> GoalResponse:
229 | """Processes action goal requests
230 |
231 | Args:
232 | goal (Fibonacci.Goal): action goal
233 |
234 | Returns:
235 | GoalResponse: goal response
236 | """
237 |
238 | self.get_logger().info("Received action goal request")
239 |
240 | return GoalResponse.ACCEPT
241 |
242 | def action_handle_cancel(self, goal: Fibonacci.Goal) -> CancelResponse:
243 | """Processes action cancel requests
244 |
245 | Args:
246 | goal (Fibonacci.Goal): action goal
247 |
248 | Returns:
249 | CancelResponse: cancel response
250 | """
251 |
252 | self.get_logger().info("Received request to cancel action goal")
253 |
254 | return CancelResponse.ACCEPT
255 |
256 | def action_handle_accepted(self, goal: Fibonacci.Goal):
257 | """Processes accepted action goal requests
258 |
259 | Args:
260 | goal (Fibonacci.Goal): action goal
261 | """
262 |
263 | # execute action in a separate thread to avoid blocking
264 | goal.execute()
265 |
266 | async def action_execute(self, goal: Fibonacci.Goal) -> Fibonacci.Result:
267 | """Executes an action
268 |
269 | Args:
270 | goal (Fibonacci.Goal): action goal
271 |
272 | Returns:
273 | Fibonacci.Result: action goal result
274 | """
275 |
276 | self.get_logger().info("Executing action goal")
277 |
278 | # define a sleeping rate between computing individual Fibonacci numbers
279 | loop_rate = 1
280 |
281 | # create the action feedback and result
282 | feedback = Fibonacci.Feedback()
283 | result = Fibonacci.Result()
284 |
285 | # initialize the Fibonacci sequence
286 | feedback.partial_sequence = [0, 1]
287 |
288 | # compute the Fibonacci sequence up to the requested order n
289 | for i in range(1, goal.request.order):
290 |
291 | # cancel, if requested
292 | if goal.is_cancel_requested:
293 | result.sequence = feedback.partial_sequence
294 | goal.canceled()
295 | self.get_logger().info("Action goal canceled")
296 | return result
297 |
298 | # compute the next Fibonacci number
299 | feedback.partial_sequence.append(feedback.partial_sequence[i] + feedback.partial_sequence[i-1])
300 |
301 | # publish the current sequence as action feedback
302 | goal.publish_feedback(feedback)
303 | self.get_logger().info("Publishing action feedback")
304 |
305 | # sleep before computing the next Fibonacci number
306 | time.sleep(loop_rate)
307 |
308 | # finish by publishing the action result
309 | if rclpy.ok():
310 | result.sequence = feedback.partial_sequence
311 | goal.succeed()
312 | self.get_logger().info("Goal succeeded")
313 |
314 | return result
315 | {% endif %}
316 | {% if has_timer %}
317 |
318 | def timer_callback(self):
319 | """Processes timer triggers
320 | """
321 |
322 | self.get_logger().info("Timer triggered")
323 | {% endif %}
324 | {% if auto_shutdown %}
325 |
326 | def auto_shutdown_timer_callback(self):
327 | """Processes timer triggers to auto-shutdown the node
328 | """
329 |
330 | self.get_logger().info("Shutting down")
331 | raise SystemExit(0)
332 | {% endif %}
333 |
334 |
335 | def main():
336 |
337 | rclpy.init()
338 | node = {{ node_class_name }}()
339 | try:
340 | rclpy.spin(node)
341 | except KeyboardInterrupt:
342 | pass
343 | finally:
344 | node.destroy_node()
345 | rclpy.try_shutdown()
346 |
347 |
348 | if __name__ == '__main__':
349 | main()
350 |
--------------------------------------------------------------------------------