├── 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 | --------------------------------------------------------------------------------