├── .clang-format ├── .github └── workflows │ ├── ci.yaml │ └── format.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── example ├── CHANGELOG.rst ├── CMakeLists.txt ├── README.md ├── config │ ├── implementation.yaml │ └── missing_required.yaml ├── include │ └── generate_parameter_library_example │ │ ├── example_validators.hpp │ │ └── minimal_publisher.hpp ├── package.xml ├── src │ ├── minimal_publisher.cpp │ └── parameters.yaml └── test │ ├── descriptor_test_gtest.cpp │ ├── example_params.yaml │ ├── example_test_gmock.cpp │ └── example_test_gtest.cpp ├── example_cmake_python ├── CHANGELOG.rst ├── CMakeLists.txt ├── README.md ├── cmake_generate_parameter_module_example │ ├── __init__.py │ ├── custom_validation.py │ ├── minimal_publisher.py │ └── parameters.yaml ├── config │ ├── implementation.yaml │ └── missing_required.yaml └── package.xml ├── example_external ├── CHANGELOG.rst ├── CMakeLists.txt ├── README.md ├── config │ ├── implementation.yaml │ └── missing_required.yaml ├── include │ └── generate_parameter_library_example_external │ │ └── minimal_publisher_external.hpp ├── package.xml └── src │ └── minimal_publisher_external.cpp ├── example_python ├── CHANGELOG.rst ├── README.md ├── config │ ├── implementation.yaml │ └── missing_required.yaml ├── generate_parameter_module_example │ ├── custom_validation.py │ ├── minimal_publisher.py │ └── parameters.yaml ├── package.xml ├── resource │ └── generate_parameter_module_example ├── setup.cfg ├── setup.py └── test │ ├── test_copyright.py │ ├── test_flake8.py │ ├── test_load_modules.py │ └── test_pep257.py ├── generate_parameter_library ├── CHANGELOG.rst ├── CMakeLists.txt ├── cmake │ └── generate_parameter_library.cmake ├── generate_parameter_library-extras.cmake └── package.xml ├── generate_parameter_library_py ├── CHANGELOG.rst ├── generate_parameter_library_py │ ├── __init__.py │ ├── cpp_conversions.py │ ├── generate_cpp_header.py │ ├── generate_markdown.py │ ├── generate_python_module.py │ ├── jinja_templates │ │ ├── cpp │ │ │ ├── declare_parameter │ │ │ ├── declare_runtime_parameter │ │ │ ├── declare_struct │ │ │ ├── declare_variable │ │ │ ├── parameter_library_header │ │ │ ├── parameter_validation │ │ │ ├── remove_runtime_parameter │ │ │ ├── set_parameter │ │ │ ├── set_runtime_parameter │ │ │ ├── set_stack_params │ │ │ ├── update_parameter │ │ │ └── update_runtime_parameter │ │ ├── markdown │ │ │ ├── default_config │ │ │ ├── documentation │ │ │ └── parameter_detail │ │ ├── python │ │ │ ├── declare_parameter │ │ │ ├── declare_runtime_parameter │ │ │ ├── declare_struct │ │ │ ├── declare_variable │ │ │ ├── parameter_library_header │ │ │ ├── parameter_validation │ │ │ ├── remove_runtime_parameter │ │ │ ├── set_parameter │ │ │ ├── set_runtime_parameter │ │ │ ├── set_stack_params │ │ │ ├── update_parameter │ │ │ └── update_runtime_parameter │ │ └── rst │ │ │ ├── default_config │ │ │ ├── documentation │ │ │ └── parameter_detail │ ├── parse_yaml.py │ ├── python_conversions.py │ ├── python_validators.py │ ├── setup_helper.py │ ├── string_filters_cpp.py │ └── test │ │ ├── YAML_parse_error_test.py │ │ ├── invalid_parameter_type.yaml │ │ ├── invalid_syntax.yaml │ │ ├── missing_type.yaml │ │ ├── valid_parameters.yaml │ │ ├── valid_parameters_with_none_type.yaml │ │ ├── validation_only_parameters.yaml │ │ └── wrong_default_type.yaml ├── package.xml ├── resource │ └── generate_parameter_library_py ├── setup.py └── test │ └── test_copyright.py ├── parameter_traits ├── CHANGELOG.rst ├── CMakeLists.txt ├── include │ └── parameter_traits │ │ └── parameter_traits.hpp └── package.xml └── upstream.repos /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | DerivePointerAlignment: false 4 | IncludeBlocks: Regroup 5 | IncludeCategories: 6 | # Headers in "" with extension. 7 | - Regex: '"([A-Za-z0-9.\Q/-_\E])+"' 8 | Priority: 1 9 | # Headers in <> without extension. 10 | - Regex: '<([A-Za-z0-9\Q/-_\E])+>' 11 | Priority: 2 12 | # Headers in <> from specific external libraries. 13 | - Regex: '<(catch2|boost|rclcpp.*)\/' 14 | Priority: 3 15 | # Headers in <> with extension. 16 | - Regex: '<([A-Za-z0-9.\Q/-_\E])+>' 17 | Priority: 4 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | # This config uses industrial_ci (https://github.com/ros-industrial/industrial_ci.git). 2 | # For troubleshooting, see readme (https://github.com/ros-industrial/industrial_ci/blob/master/README.rst) 3 | 4 | name: CI 5 | 6 | on: 7 | workflow_dispatch: 8 | pull_request: 9 | push: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | ci: 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | env: 19 | - ROS_DISTRO: rolling 20 | ROS_REPO: testing 21 | - ROS_DISTRO: rolling 22 | ROS_REPO: main 23 | - ROS_DISTRO: jazzy 24 | ROS_REPO: testing 25 | - ROS_DISTRO: jazzy 26 | ROS_REPO: main 27 | - ROS_DISTRO: humble 28 | ROS_REPO: testing 29 | - ROS_DISTRO: humble 30 | ROS_REPO: main 31 | 32 | env: 33 | BASEDIR: ${{ github.workspace }}/.work 34 | UPSTREAM_WORKSPACE: upstream.repos 35 | 36 | name: ${{ matrix.env.ROS_DISTRO }}-${{ matrix.env.ROS_REPO }} 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v4 40 | - id: ici 41 | name: Run industrial_ci 42 | uses: ros-industrial/industrial_ci@master 43 | env: ${{ matrix.env }} 44 | - name: Upload test artifacts (on failure) 45 | uses: actions/upload-artifact@v4 46 | if: failure() && (steps.ici.outputs.run_target_test || steps.ici.outputs.target_test_results) 47 | with: 48 | name: test-results-${{ matrix.env.NAME }} 49 | path: ${{ env.BASEDIR }}/target_ws/**/test_results/**/*.xml 50 | overwrite: true 51 | - name: Upload log artifacts (on failure) 52 | uses: actions/upload-artifact@v4 53 | if: failure() 54 | with: 55 | name: logs-${{ matrix.env.NAME }} 56 | path: ${{ env.BASEDIR }}/target_ws/log/* 57 | overwrite: true 58 | -------------------------------------------------------------------------------- /.github/workflows/format.yaml: -------------------------------------------------------------------------------- 1 | # This is a format job. Pre-commit has a first-party GitHub action, so we use 2 | # that: https://github.com/pre-commit/action 3 | 4 | name: Format 5 | 6 | on: 7 | workflow_dispatch: 8 | pull_request: 9 | push: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | pre-commit: 15 | name: pre-commit 16 | runs-on: ubuntu-22.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: actions/setup-python@v5 20 | - name: Install clang-format 21 | run: sudo apt-get install clang-format 22 | - uses: pre-commit/action@v3.0.1 23 | id: precommit 24 | - name: Upload pre-commit changes 25 | if: failure() && steps.precommit.outcome == 'failure' 26 | uses: rhaschke/upload-git-patch-action@main 27 | with: 28 | name: pre-commit 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__ 2 | build 3 | install 4 | log 5 | *.egg-info 6 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # To use: 2 | # 3 | # pre-commit run -a 4 | # 5 | # Or: 6 | # 7 | # pre-commit install # (runs every time you commit in git) 8 | # 9 | # To update this file: 10 | # 11 | # pre-commit autoupdate 12 | # 13 | # See https://github.com/pre-commit/pre-commit 14 | 15 | repos: 16 | # Standard hooks 17 | - repo: https://github.com/pre-commit/pre-commit-hooks 18 | rev: v5.0.0 19 | hooks: 20 | - id: check-added-large-files 21 | args: ['--maxkb=1000'] 22 | - id: check-ast 23 | - id: check-byte-order-marker 24 | - id: check-builtin-literals 25 | - id: check-case-conflict 26 | - id: check-docstring-first 27 | - id: check-executables-have-shebangs 28 | - id: check-json 29 | - id: check-executables-have-shebangs 30 | - id: pretty-format-json 31 | - id: check-merge-conflict 32 | - id: check-symlinks 33 | - id: check-toml 34 | - id: check-vcs-permalinks 35 | - id: check-yaml 36 | exclude: generate_parameter_library_py/generate_parameter_library_py/test 37 | - id: debug-statements 38 | - id: destroyed-symlinks 39 | - id: detect-private-key 40 | - id: end-of-file-fixer 41 | - id: mixed-line-ending 42 | - id: fix-byte-order-marker 43 | - id: fix-encoding-pragma 44 | - id: forbid-new-submodules 45 | - id: mixed-line-ending 46 | - id: name-tests-test 47 | - id: requirements-txt-fixer 48 | - id: sort-simple-yaml 49 | - id: trailing-whitespace 50 | - id: double-quote-string-fixer 51 | 52 | - repo: https://github.com/psf/black 53 | rev: 24.10.0 54 | hooks: 55 | - id: black 56 | args: ["--skip-string-normalization"] 57 | 58 | - repo: local 59 | hooks: 60 | - id: clang-format 61 | name: clang-format 62 | description: Format files with ClangFormat. 63 | entry: clang-format 64 | language: system 65 | files: \.(c|cc|cxx|cpp|frag|glsl|h|hpp|hxx|ih|ispc|ipp|java|js|m|proto|vert)$ 66 | args: ['-fallback-style=none', '-i'] 67 | 68 | - repo: https://github.com/codespell-project/codespell 69 | rev: v2.3.0 70 | hooks: 71 | - id: codespell 72 | args: ['--write-changes'] 73 | exclude: CHANGELOG.rst 74 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Any contribution that you make to this repository will 2 | be under the 3-Clause BSD License, as dictated by that 3 | [license](https://opensource.org/licenses/BSD-3-Clause). 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Redistribution and use in source and binary forms, with or without 2 | modification, are permitted provided that the following conditions are met: 3 | 4 | * Redistributions of source code must retain the above copyright 5 | notice, this list of conditions and the following disclaimer. 6 | 7 | * Redistributions in binary form must reproduce the above copyright 8 | notice, this list of conditions and the following disclaimer in the 9 | documentation and/or other materials provided with the distribution. 10 | 11 | * Neither the name of the copyright holder nor the names of its 12 | contributors may be used to endorse or promote products derived from 13 | this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /example/CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2 | Changelog for package generate_parameter_library_example 3 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 4 | 5 | 0.4.0 (2025-01-13) 6 | ------------------ 7 | * Change header install path (`#213 `_) 8 | * Contributors: Auguste Bourgois 9 | 10 | 0.3.9 (2024-10-27) 11 | ------------------ 12 | * Add "additional_constraints" support (`#221 `_) 13 | * Use int64_t instead of int for parameter integer range, fixes `#199 `_ (`#214 `_) 14 | * Add Non-Blocking try_get_params Function for Real-Time Control Systems (`#205 `_) 15 | * Drop yaml brackets for consistency and readability (`#203 `_) 16 | * Contributors: Auguste Bourgois, David Revay, KentaKato, Tim Clephas 17 | 18 | 0.3.8 (2024-03-27) 19 | ------------------ 20 | * Restore functionality for mapped params with no struct name (`#185 _`) 21 | * Fix newline issue (`#176 `_) 22 | * fix new line rendering for Python 23 | * Support nested mapped parameters (`#166 `_) 24 | * Contributors: Paul Gesel, Sebastian Castro 25 | 26 | 0.3.7 (2024-01-12) 27 | ------------------ 28 | * Split example/README.md into C++ and Python version; updated content (`#138 `_) 29 | * Contributors: chriseichmann 30 | 31 | 0.3.6 (2023-07-31) 32 | ------------------ 33 | 34 | 0.3.5 (2023-07-28) 35 | ------------------ 36 | * Remove documentation of deprecated validator functions (`#135 `_) 37 | * Contributors: Tyler Weaver 38 | 39 | 0.3.4 (2023-07-24) 40 | ------------------ 41 | * Add Python support for generate_parameter_library (`#110 `_) 42 | Co-authored-by: Tyler Weaver 43 | * Contributors: Paul Gesel 44 | 45 | 0.3.3 (2023-04-13) 46 | ------------------ 47 | 48 | 0.3.2 (2023-04-12) 49 | ------------------ 50 | * Populate Range Constraints in Parameter Descriptors from Validation Functions (`#103 `_) 51 | * Mark deprecated rsl method and propose alternative in the docs. (`#102 `_) 52 | * Contributors: Chance Cardona, Dr. Denis 53 | 54 | 0.3.1 (2023-02-01) 55 | ------------------ 56 | * Make it easy for users to override (`#92 `_) 57 | * Contributors: Tyler Weaver 58 | 59 | 0.3.0 (2022-11-15) 60 | ------------------ 61 | * Migrate from parameter_traits to RSL (take 2) (`#91 `_) 62 | * Contributors: Tyler Weaver 63 | 64 | 0.2.8 (2022-11-03) 65 | ------------------ 66 | 67 | 0.2.7 (2022-10-28) 68 | ------------------ 69 | * Standardize cmake (`#79 `_) 70 | * Contributors: Tyler Weaver 71 | 72 | 0.2.6 (2022-09-28) 73 | ------------------ 74 | 75 | 0.2.5 (2022-09-20) 76 | ------------------ 77 | * 🈵 Support use of '_' in mapped parameters. (`#68 `_) 78 | * Component node example (`#60 `_) 79 | * Update README for example (`#63 `_) 80 | * 🚀 Add cmake macros for using tests with example yaml files. 🤖 (`#57 `_) 81 | Co-authored-by: Tyler Weaver 82 | * Fix example parameters (`#54 `_) 83 | * Contributors: Denis Štogl, Paul Gesel, Tyler Weaver 84 | 85 | 0.2.4 (2022-08-19) 86 | ------------------ 87 | * INTEGER type (`#53 `_) 88 | * 0.2.3 89 | * Contributors: Tyler Weaver 90 | 91 | 0.2.3 (2022-08-05) 92 | ------------------ 93 | 94 | 0.2.2 (2022-08-03) 95 | ------------------ 96 | 97 | 0.2.1 (2022-08-02) 98 | ------------------ 99 | * Fix scientific notation (`#46 `_) 100 | * Contributors: Paul Gesel 101 | 102 | 0.2.0 (2022-08-01) 103 | ------------------ 104 | * Create stack allocated struct (`#45 `_) 105 | * Fixed length arrays (`#44 `_) 106 | * Fixed size string no default bug (`#43 `_) 107 | * static OK to fix ODR errors (`#41 `_) 108 | * Change package name (`#40 `_) 109 | * parameter validators interface library (`#32 `_) 110 | * Validate fixed length Strings (`#33 `_) 111 | * Fixed size strings (`#29 `_) 112 | * Contributors: Paul Gesel, Tyler Weaver 113 | 114 | 0.1.0 (2022-07-27) 115 | ------------------ 116 | * Example usage of generate_parameter_library. 117 | * Contributors: Paul Gesel, Tyler Weaver 118 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(generate_parameter_library_example) 3 | 4 | if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") 5 | add_compile_options(-Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wsign-conversion -Wold-style-cast) 6 | endif() 7 | 8 | find_package(ament_cmake REQUIRED) 9 | find_package(ament_cmake_python REQUIRED) 10 | find_package(generate_parameter_library REQUIRED) 11 | find_package(rclcpp REQUIRED) 12 | find_package(rclcpp_components REQUIRED) 13 | find_package(rclpy REQUIRED) 14 | 15 | generate_parameter_library(admittance_controller_parameters 16 | src/parameters.yaml 17 | include/generate_parameter_library_example/example_validators.hpp 18 | ) 19 | 20 | add_library(minimal_publisher SHARED 21 | src/minimal_publisher.cpp 22 | ) 23 | target_include_directories(minimal_publisher PUBLIC 24 | $ 25 | $ 26 | ) 27 | target_link_libraries(minimal_publisher 28 | PUBLIC 29 | admittance_controller_parameters 30 | rclcpp::rclcpp 31 | rclcpp_components::component 32 | ) 33 | rclcpp_components_register_node(minimal_publisher 34 | PLUGIN "admittance_controller::MinimalPublisher" 35 | EXECUTABLE test_node 36 | ) 37 | 38 | if(BUILD_TESTING) 39 | find_package(ament_lint_auto REQUIRED) 40 | set(ament_cmake_cpplint_FOUND TRUE) # Conflicts with clang-foramt 41 | set(ament_cmake_flake8_FOUND TRUE) # Conflicts with black 42 | set(ament_cmake_uncrustify_FOUND TRUE) # Conflicts with clang-format 43 | ament_lint_auto_find_test_dependencies() 44 | 45 | find_package(ament_cmake_gtest REQUIRED) 46 | # example_test_gtest 47 | add_rostest_with_parameters_gtest(test_example_gtest test/example_test_gtest.cpp 48 | ${CMAKE_CURRENT_SOURCE_DIR}/test/example_params.yaml) 49 | target_include_directories(test_example_gtest PRIVATE include) 50 | target_link_libraries(test_example_gtest admittance_controller_parameters rclcpp::rclcpp) 51 | # descriptor_test_gtest 52 | add_rostest_with_parameters_gtest(test_descriptor_gtest test/descriptor_test_gtest.cpp 53 | ${CMAKE_CURRENT_SOURCE_DIR}/test/example_params.yaml) 54 | target_include_directories(test_descriptor_gtest PRIVATE include) 55 | target_link_libraries(test_descriptor_gtest admittance_controller_parameters rclcpp::rclcpp) 56 | 57 | find_package(ament_cmake_gmock REQUIRED) 58 | add_rostest_with_parameters_gmock(test_example_gmock test/example_test_gmock.cpp 59 | ${CMAKE_CURRENT_SOURCE_DIR}/test/example_params.yaml) 60 | target_include_directories(test_example_gmock PRIVATE include) 61 | target_link_libraries(test_example_gmock admittance_controller_parameters rclcpp::rclcpp) 62 | endif() 63 | 64 | install( 65 | DIRECTORY include/ 66 | DESTINATION include/generate_parameter_library_example 67 | ) 68 | 69 | install(TARGETS minimal_publisher admittance_controller_parameters 70 | EXPORT export_generate_parameter_library_example 71 | ARCHIVE DESTINATION lib 72 | LIBRARY DESTINATION lib 73 | RUNTIME DESTINATION bin 74 | ) 75 | 76 | install( 77 | TARGETS test_node 78 | DESTINATION lib/generate_parameter_library_example 79 | ) 80 | 81 | ament_export_targets(export_generate_parameter_library_example HAS_LIBRARY_TARGET) 82 | ament_export_dependencies(rclcpp rclcpp_components) 83 | ament_package() 84 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Example: 2 | 3 | ## Build the node 4 | 5 | ``` 6 | mkdir colcon_ws 7 | mkdir colcon_ws/src 8 | cd colcon_ws/src 9 | git clone https://github.com/picknikrobotics/generate_parameter_library.git 10 | cd .. 11 | colcon build 12 | ``` 13 | 14 | ## Run the C++ node 15 | 16 | ``` 17 | source install/setup.bash 18 | ros2 run generate_parameter_library_example test_node --ros-args --params-file src/generate_parameter_library/example/config/implementation.yaml 19 | ``` 20 | 21 | You should see an output like this: 22 | `[INFO] [1656018676.015816509] [admittance_controller]: Control frame is: 'ee_link'` 23 | 24 | ## ROS 2 CLI 25 | 26 | Run the following: 27 | 28 | `ros2 param list` 29 | 30 | You should see: 31 | 32 | ``` 33 | /admittance_controller: 34 | admittance.damping_ratio 35 | admittance.mass 36 | admittance.selected_axes 37 | admittance.stiffness 38 | chainable_command_interfaces 39 | command_interfaces 40 | control.frame.external 41 | control.frame.id 42 | enable_parameter_update_without_reactivation 43 | fixed_array 44 | fixed_string 45 | fixed_string_no_default 46 | fixed_world_frame.frame.external 47 | fixed_world_frame.frame.id 48 | ft_sensor.filter_coefficient 49 | ft_sensor.frame.external 50 | ft_sensor.frame.id 51 | ft_sensor.name 52 | gravity_compensation.CoG.force 53 | gravity_compensation.CoG.pos 54 | gravity_compensation.frame.external 55 | gravity_compensation.frame.id 56 | interpolation_mode 57 | joints 58 | kinematics.alpha 59 | kinematics.base 60 | kinematics.group_name 61 | kinematics.plugin_name 62 | kinematics.plugin_package 63 | kinematics.tip 64 | one_number 65 | pid.elbow_joint.d 66 | pid.elbow_joint.i 67 | pid.elbow_joint.p 68 | pid.rate 69 | pid.shoulder_lift_joint.d 70 | pid.shoulder_lift_joint.i 71 | pid.shoulder_lift_joint.p 72 | pid.shoulder_pan_joint.d 73 | pid.shoulder_pan_joint.i 74 | pid.shoulder_pan_joint.p 75 | pid.wrist_1_joint.d 76 | pid.wrist_1_joint.i 77 | pid.wrist_1_joint.p 78 | pid.wrist_2_joint.d 79 | pid.wrist_2_joint.i 80 | pid.wrist_2_joint.p 81 | pid.wrist_3_joint.d 82 | pid.wrist_3_joint.i 83 | pid.wrist_3_joint.p 84 | qos_overrides./parameter_events.publisher.depth 85 | qos_overrides./parameter_events.publisher.durability 86 | qos_overrides./parameter_events.publisher.history 87 | qos_overrides./parameter_events.publisher.reliability 88 | scientific_notation_num 89 | state_interfaces 90 | three_numbers 91 | three_numbers_of_five 92 | use_feedforward_commanded_input 93 | use_sim_time 94 | ``` 95 | 96 | All parameter are automatically declared and callbacks are setup by default. You can set a parameter by typing: 97 | 98 | `ros2 param set /admittance_controller control.frame.id new_frame` 99 | 100 | You should see: 101 | 102 | `[INFO] [1656019001.515820371] [admittance_controller]: New control frame parameter is: 'new_frame'` 103 | 104 | Congratulations, you updated the parameter! 105 | 106 | If you try to set a parameter that is read only, you will get an error. Running the following 107 | 108 | `ros2 param set /admittance_controller command_interfaces ["velocity"]` 109 | 110 | will result in the error 111 | 112 | `Setting parameter failed: parameter 'command_interfaces' cannot be set because it is read-only` 113 | 114 | Running the following 115 | 116 | `ros2 param describe /admittance_controller admittance.damping_ratio` 117 | 118 | will show a parameter's description 119 | 120 | ``` 121 | Parameter name: admittance.damping_ratio 122 | Type: double array 123 | Description: specifies damping ratio values for x, y, z, rx, ry, and rz used in the admittance calculation. The values are calculated as damping can be used instead: zeta = D / (2 * sqrt( M * S )) 124 | Constraints: 125 | Min value: 0.1 126 | Max value: 10.0 127 | ``` 128 | 129 | If you try to set a value out of the specified bounds, 130 | 131 | `ros2 param set /admittance_controller admittance.damping_ratio [-10.0,-10.0,-10.0,-10.0,-10.0,-10.0]` 132 | 133 | you will get the error 134 | 135 | `Setting parameter failed: Value -10.0 in parameter 'admittance.damping_ratio' must be within bounds [0.1, 10.0]` 136 | 137 | If you try to set a vector parameter with the wrong length, 138 | 139 | `ros2 param set /admittance_controller admittance.damping_ratio [1.0,1.0,1.0]` 140 | 141 | you will get the error 142 | 143 | `Setting parameter failed: Length of parameter 'admittance.damping_ratio' is 3 but must be equal to 6` 144 | 145 | If you try to load a yaml file with missing required parameters 146 | 147 | `ros2 run generate_parameter_library_example test_node --ros-args --params-file src/generate_parameter_library/example/config/missing_required.yaml` 148 | 149 | you will get the error 150 | 151 | ``` 152 | terminate called after throwing an instance of 'rclcpp::exceptions::ParameterUninitializedException' 153 | what(): parameter 'fixed_string_no_default' is not initialized 154 | [ros2run]: Aborted 155 | ``` 156 | -------------------------------------------------------------------------------- /example/config/implementation.yaml: -------------------------------------------------------------------------------- 1 | admittance_controller: 2 | ros__parameters: 3 | joints: 4 | - shoulder_pan_joint 5 | - shoulder_lift_joint 6 | - elbow_joint 7 | - wrist_1_joint 8 | - wrist_2_joint 9 | - wrist_3_joint 10 | 11 | fixed_string_no_default: 12 | "happy" 13 | 14 | elbow_joint: 15 | x: 16 | weight: 2.0 17 | 18 | pid: 19 | shoulder_pan_joint: 20 | i: 0.7 21 | shoulder_lift_joint: 22 | i: 0.5 23 | elbow_joint: 24 | i: 0.2 25 | wrist_1_joint: 26 | i: 1.2 27 | wrist_2_joint: 28 | i: 0.8 29 | wrist_3_joint: 30 | i: 0.5 31 | 32 | command_interfaces: 33 | - position 34 | 35 | state_interfaces: 36 | - position 37 | - velocity 38 | 39 | chainable_command_interfaces: 40 | - position 41 | - velocity 42 | 43 | kinematics: 44 | plugin_name: kdl_plugin/KDLKinematics 45 | plugin_package: kinematics_interface_kdl 46 | base: base_link # Assumed to be stationary 47 | tip: ee_link 48 | group_name: ur5e_manipulator 49 | alpha: 0.0005 50 | 51 | ft_sensor: 52 | name: tcp_fts_sensor 53 | frame: 54 | id: ee_link # ee_link Wrench measurements are in this frame 55 | external: false # force torque frame exists within URDF kinematic chain 56 | filter_coefficient: 0.005 57 | 58 | control: 59 | frame: 60 | id: ee_link # Admittance calcs (displacement etc) are done in this frame. Usually the tool or end-effector 61 | external: false # control frame exists within URDF kinematic chain 62 | 63 | fixed_world_frame: 64 | frame: # Gravity points down (neg. Z) in this frame (Usually: world or base_link) 65 | id: base_link # Admittance calcs (displacement etc) are done in this frame. Usually the tool or end-effector 66 | external: false # control frame exists within URDF kinematic chain 67 | 68 | gravity_compensation: 69 | frame: 70 | id: ee_link 71 | external: false 72 | 73 | CoG: # specifies the center of gravity of the end effector 74 | pos: 75 | - 0.1 # x 76 | - 0.0 # y 77 | - 0.0 # z 78 | force: 23.0 # mass * 9.81 79 | 80 | admittance: 81 | selected_axes: 82 | - true # x 83 | - true # y 84 | - true # z 85 | - true # rx 86 | - true # ry 87 | - true # rz 88 | 89 | # Having ".0" at the end is MUST, otherwise there is a loading error 90 | # F = M*a + D*v + S*(x - x_d) 91 | mass: 92 | - 3.0 # x 93 | - 3.0 # y 94 | - 3.0 # z 95 | - 0.05 # rx 96 | - 0.05 # ry 97 | - 0.05 # rz 98 | 99 | damping_ratio: # damping can be used instead: zeta = D / (2 * sqrt( M * S )) 100 | - 2.828427 # x 101 | - 2.828427 # y 102 | - 2.828427 # z 103 | - 2.828427 # rx 104 | - 2.828427 # ry 105 | - 2.828427 # rz 106 | 107 | stiffness: 108 | - 50.0 # x 109 | - 50.0 # y 110 | - 50.0 # z 111 | - 1.0 # rx 112 | - 1.0 # ry 113 | - 1.0 # rz 114 | 115 | # general settings 116 | enable_parameter_update_without_reactivation: true 117 | use_feedforward_commanded_input: true 118 | -------------------------------------------------------------------------------- /example/config/missing_required.yaml: -------------------------------------------------------------------------------- 1 | admittance_controller: 2 | ros__parameters: 3 | mass: 4 | - -10.0 # x 5 | - -10.0 # y 6 | - 3.0 # z 7 | - 0.05 # rx 8 | - 0.05 # ry 9 | - 0.05 # rz 10 | -------------------------------------------------------------------------------- /example/include/generate_parameter_library_example/example_validators.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PickNik Inc. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 6 | // * Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // * Neither the name of the PickNik Inc. nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | // POSSIBILITY OF SUCH DAMAGE. 28 | 29 | #pragma once 30 | 31 | #include 32 | 33 | #include 34 | 35 | #include 36 | #include 37 | 38 | namespace custom_validators { 39 | 40 | // User defined parameter validation 41 | tl::expected validate_double_array_custom_func( 42 | const rclcpp::Parameter& parameter, double max_sum, double max_element) { 43 | const auto& double_array = parameter.as_double_array(); 44 | double sum = 0.0; 45 | for (auto val : double_array) { 46 | sum += val; 47 | if (val > max_element) { 48 | return tl::make_unexpected(fmt::format( 49 | "The parameter contained an element greater than the max allowed " 50 | "value. (%f) was greater than (%f)", 51 | val, max_element)); 52 | } 53 | } 54 | if (sum > max_sum) { 55 | return tl::make_unexpected(fmt::format( 56 | "The sum of the parameter vector was greater than the max allowed " 57 | "value. (%f) was greater than (%f)", 58 | sum, max_sum)); 59 | } 60 | 61 | return {}; 62 | } 63 | 64 | tl::expected no_args_validator(const rclcpp::Parameter&) { 65 | return {}; 66 | } 67 | 68 | } // namespace custom_validators 69 | -------------------------------------------------------------------------------- /example/include/generate_parameter_library_example/minimal_publisher.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PickNik Inc. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 6 | // * Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // * Neither the name of the PickNik Inc. nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | // POSSIBILITY OF SUCH DAMAGE. 28 | 29 | #pragma once 30 | 31 | #include 32 | #include 33 | 34 | #include 35 | 36 | namespace admittance_controller { 37 | 38 | class MinimalPublisher : public rclcpp::Node { 39 | public: 40 | MinimalPublisher(const rclcpp::NodeOptions& options = rclcpp::NodeOptions()); 41 | 42 | private: 43 | void timer_callback(); 44 | 45 | rclcpp::TimerBase::SharedPtr timer_; 46 | std::shared_ptr param_listener_; 47 | admittance_controller::Params params_; 48 | }; 49 | 50 | } // namespace admittance_controller 51 | 52 | RCLCPP_COMPONENTS_REGISTER_NODE(admittance_controller::MinimalPublisher) 53 | -------------------------------------------------------------------------------- /example/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | generate_parameter_library_example 5 | 0.4.0 6 | Example usage of generate_parameter_library. 7 | Paul Gesel 8 | Nathan Brooks 9 | Shaurya Kumar 10 | BSD-3-Clause 11 | Paul Gesel 12 | 13 | generate_parameter_library 14 | rclcpp 15 | rclcpp_components 16 | 17 | ament_cmake 18 | 19 | ament_cmake_core 20 | 21 | ament_lint_auto 22 | ament_lint_common 23 | 24 | 25 | ament_cmake 26 | 27 | 28 | -------------------------------------------------------------------------------- /example/src/minimal_publisher.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 PickNik Inc. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 6 | // * Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // * Neither the name of the PickNik Inc. nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | // POSSIBILITY OF SUCH DAMAGE. 28 | 29 | #include "generate_parameter_library_example/minimal_publisher.hpp" 30 | 31 | #include 32 | 33 | #include 34 | 35 | using namespace std::chrono_literals; 36 | 37 | namespace admittance_controller { 38 | 39 | MinimalPublisher::MinimalPublisher(const rclcpp::NodeOptions& options) 40 | : Node("admittance_controller", options) { 41 | timer_ = create_wall_timer( 42 | 500ms, std::bind(&MinimalPublisher::timer_callback, this)); 43 | param_listener_ = 44 | std::make_shared(get_node_parameters_interface()); 45 | params_ = param_listener_->get_params(); 46 | 47 | [[maybe_unused]] StackParams s_params = param_listener_->get_stack_params(); 48 | 49 | RCLCPP_INFO(get_logger(), "Initial control frame parameter is: '%s'", 50 | params_.control.frame.id.c_str()); 51 | RCLCPP_INFO(get_logger(), "fixed string is: '%s'", 52 | std::string{params_.fixed_string}.c_str()); 53 | const tcb::span fixed_array = params_.fixed_array; 54 | for (auto d : fixed_array) { 55 | RCLCPP_INFO(get_logger(), "value: '%s'", std::to_string(d).c_str()); 56 | } 57 | } 58 | 59 | void MinimalPublisher::timer_callback() { 60 | if (param_listener_->is_old(params_)) { 61 | param_listener_->refresh_dynamic_parameters(); 62 | params_ = param_listener_->get_params(); 63 | RCLCPP_INFO(get_logger(), "New control frame parameter is: '%s'", 64 | params_.control.frame.id.c_str()); 65 | RCLCPP_INFO(get_logger(), "fixed string is: '%s'", 66 | std::string{params_.fixed_string}.c_str()); 67 | const tcb::span fixed_array = params_.fixed_array; 68 | for (auto d : fixed_array) { 69 | RCLCPP_INFO(get_logger(), "value: '%s'", std::to_string(d).c_str()); 70 | } 71 | } 72 | } 73 | 74 | } // namespace admittance_controller 75 | -------------------------------------------------------------------------------- /example/test/descriptor_test_gtest.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Picknik Robotics 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 6 | // * Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // * Neither the name of the PickNik Inc. nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | // POSSIBILITY OF SUCH DAMAGE. 28 | // 29 | // Author: Chance Cardona 30 | // 31 | 32 | #include "generate_parameter_library_example/admittance_controller_parameters.hpp" 33 | #include "gtest/gtest.h" 34 | #include "rclcpp/rclcpp.hpp" 35 | 36 | #include 37 | #include 38 | 39 | class DescriptorTest : public ::testing::Test { 40 | public: 41 | void SetUp() { 42 | example_test_node_ = std::make_shared("example_test_node"); 43 | 44 | std::shared_ptr param_listener = 45 | std::make_shared( 46 | example_test_node_->get_node_parameters_interface()); 47 | params_ = param_listener->get_params(); 48 | std::vector names = {"admittance.damping_ratio", "one_number", 49 | "pid.joint4.p", "lt_eq_fifteen", 50 | "gt_fifteen"}; 51 | descriptors_ = example_test_node_->describe_parameters(names); 52 | } 53 | 54 | void TearDown() { example_test_node_.reset(); } 55 | 56 | protected: 57 | std::shared_ptr example_test_node_; 58 | admittance_controller::Params params_; 59 | std::vector descriptors_; 60 | }; 61 | 62 | // Checks element_bounds<> on a float 63 | TEST_F(DescriptorTest, check_floating_point_descriptors) { 64 | EXPECT_EQ(descriptors_[0].floating_point_range.at(0).from_value, 0.1); 65 | EXPECT_EQ(descriptors_[0].floating_point_range.at(0).to_value, 10); 66 | } 67 | 68 | // Checks bounds<> on an int 69 | TEST_F(DescriptorTest, check_integer_descriptors) { 70 | EXPECT_EQ(descriptors_[1].integer_range.at(0).from_value, 1024); 71 | EXPECT_EQ(descriptors_[1].integer_range.at(0).to_value, 65535); 72 | } 73 | 74 | TEST_F(DescriptorTest, check_lower_upper_bounds) { 75 | EXPECT_EQ(descriptors_[2].floating_point_range.at(0).from_value, 0.0001); 76 | EXPECT_EQ(descriptors_[2].floating_point_range.at(0).to_value, 77 | std::numeric_limits::max()); 78 | } 79 | 80 | TEST_F(DescriptorTest, check_lt_eq) { 81 | EXPECT_EQ(descriptors_[3].integer_range.at(0).from_value, 82 | std::numeric_limits::lowest()); 83 | EXPECT_EQ(descriptors_[3].integer_range.at(0).to_value, 15); 84 | } 85 | 86 | TEST_F(DescriptorTest, check_gt) { 87 | EXPECT_EQ(descriptors_[4].integer_range.at(0).from_value, 15); 88 | EXPECT_EQ(descriptors_[4].integer_range.at(0).to_value, 89 | std::numeric_limits::max()); 90 | } 91 | 92 | int main(int argc, char** argv) { 93 | ::testing::InitGoogleTest(&argc, argv); 94 | rclcpp::init(argc, argv); 95 | int result = RUN_ALL_TESTS(); 96 | rclcpp::shutdown(); 97 | return result; 98 | } 99 | -------------------------------------------------------------------------------- /example/test/example_params.yaml: -------------------------------------------------------------------------------- 1 | example_test_node: 2 | ros__parameters: 3 | 4 | joints: 5 | - joint4 6 | - joint5 7 | - joint6 8 | 9 | ft_sensor: 10 | name: "ft_sensor" 11 | frame: 12 | id: "sensor_frame_id" 13 | filter_coefficient: 0.1 14 | 15 | 16 | # mandatory parameters not relevant for the test 17 | fixed_string_no_default: "stringy" 18 | command_interfaces: 19 | - "one" 20 | - "two" 21 | state_interfaces: 22 | - "one" 23 | - "two" 24 | chainable_command_interfaces: 25 | - "one" 26 | - "two" 27 | 28 | kinematics: 29 | plugin_name: "kinematics" 30 | plugin_package: "kinematics_pkg" 31 | base: "base_link" 32 | tip: "tcp" 33 | group_name: "manip" 34 | 35 | control: 36 | frame: 37 | id: "control_frame_id" 38 | 39 | fixed_world_frame: 40 | frame: 41 | id: "world_frame" 42 | 43 | gravity_compensation: 44 | frame: 45 | id: "world_frame" 46 | CoG: 47 | pos: [1.0, 2.2, 3.4] 48 | 49 | admittance: 50 | selected_axes: [true, false, true, false, true, false] 51 | mass: [1.2, 2.3, 3.4, 4.5, 5.6, 6.7] 52 | stiffness: [1.2, 2.3, 3.4, 4.5, 5.6, 6.7] 53 | damping_ratio: [1.1, 1.1, 1.1, 1.1, 1.1, 1.1] 54 | -------------------------------------------------------------------------------- /example/test/example_test_gmock.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Stogl Robotics Consulting 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 6 | // * Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // * Neither the name of the PickNik Inc. nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | // POSSIBILITY OF SUCH DAMAGE. 28 | // 29 | // Author: Denis Štogl 30 | // 31 | 32 | #include "generate_parameter_library_example/admittance_controller_parameters.hpp" 33 | #include "gmock/gmock.h" 34 | #include "rclcpp/rclcpp.hpp" 35 | 36 | #include 37 | 38 | class ExampleTest : public ::testing::Test { 39 | public: 40 | void SetUp() { 41 | example_test_node_ = std::make_shared("example_test_node"); 42 | 43 | std::shared_ptr param_listener = 44 | std::make_shared( 45 | example_test_node_->get_node_parameters_interface()); 46 | params_ = param_listener->get_params(); 47 | } 48 | 49 | void TearDown() { example_test_node_.reset(); } 50 | 51 | protected: 52 | std::shared_ptr example_test_node_; 53 | admittance_controller::Params params_; 54 | }; 55 | 56 | TEST_F(ExampleTest, check_parameters) { 57 | ASSERT_EQ(params_.interpolation_mode, "spline"); // default value 58 | 59 | ASSERT_THAT(params_.joints, 60 | ::testing::ElementsAreArray({"joint4", "joint5", "joint6"})); 61 | 62 | ASSERT_EQ(params_.ft_sensor.filter_coefficient, 0.1); 63 | } 64 | 65 | int main(int argc, char** argv) { 66 | ::testing::InitGoogleTest(&argc, argv); 67 | rclcpp::init(argc, argv); 68 | int result = RUN_ALL_TESTS(); 69 | rclcpp::shutdown(); 70 | return result; 71 | } 72 | -------------------------------------------------------------------------------- /example/test/example_test_gtest.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Stogl Robotics Consulting 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 6 | // * Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // * Neither the name of the PickNik Inc. nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | // POSSIBILITY OF SUCH DAMAGE. 28 | // 29 | // Author: Denis Štogl 30 | // 31 | 32 | #include "generate_parameter_library_example/admittance_controller_parameters.hpp" 33 | #include "gtest/gtest.h" 34 | #include "rclcpp/rclcpp.hpp" 35 | 36 | #include 37 | 38 | class ExampleTest : public ::testing::Test { 39 | public: 40 | void SetUp() { 41 | example_test_node_ = std::make_shared("example_test_node"); 42 | 43 | param_listener_ = std::make_shared( 44 | example_test_node_->get_node_parameters_interface()); 45 | params_ = param_listener_->get_params(); 46 | } 47 | 48 | void TearDown() { example_test_node_.reset(); } 49 | 50 | protected: 51 | std::shared_ptr example_test_node_; 52 | admittance_controller::Params params_; 53 | std::shared_ptr param_listener_; 54 | }; 55 | 56 | TEST_F(ExampleTest, check_parameters) { 57 | ASSERT_EQ(params_.interpolation_mode, "spline"); // default value 58 | 59 | ASSERT_EQ(params_.joints.size(), 3); 60 | EXPECT_EQ(params_.joints[0], "joint4"); 61 | EXPECT_EQ(params_.joints[1], "joint5"); 62 | EXPECT_EQ(params_.joints[2], "joint6"); 63 | 64 | ASSERT_EQ(params_.ft_sensor.filter_coefficient, 0.1); 65 | } 66 | 67 | TEST_F(ExampleTest, try_get_params) { 68 | ASSERT_TRUE(param_listener_->try_get_params(params_)); 69 | 70 | const rclcpp ::Parameter new_param("interpolation_mode", "linear"); 71 | example_test_node_->set_parameter(new_param); 72 | ASSERT_TRUE(param_listener_->try_get_params(params_)); 73 | ASSERT_EQ(params_.interpolation_mode, "linear"); 74 | } 75 | 76 | int main(int argc, char** argv) { 77 | ::testing::InitGoogleTest(&argc, argv); 78 | rclcpp::init(argc, argv); 79 | int result = RUN_ALL_TESTS(); 80 | rclcpp::shutdown(); 81 | return result; 82 | } 83 | -------------------------------------------------------------------------------- /example_cmake_python/CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2 | Changelog for package cmake_generate_parameter_module_example 3 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 4 | 5 | 0.4.0 (2025-01-13) 6 | ------------------ 7 | 8 | 0.3.9 (2024-10-27) 9 | ------------------ 10 | * Drop yaml brackets for consistency and readability (`#203 `_) 11 | * Contributors: Tim Clephas 12 | 13 | 0.3.8 (2024-03-27) 14 | ------------------ 15 | 16 | 0.3.7 (2024-01-12) 17 | ------------------ 18 | * Enable generate_parameter_module through ament_cmake_python (`#161 `_) 19 | * Contributors: Paul Gesel 20 | -------------------------------------------------------------------------------- /example_cmake_python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(cmake_generate_parameter_module_example) 3 | 4 | find_package(ament_cmake REQUIRED) 5 | find_package(ament_cmake_python REQUIRED) 6 | find_package(generate_parameter_library REQUIRED) 7 | 8 | generate_parameter_module(admittance_parameters 9 | cmake_generate_parameter_module_example/parameters.yaml 10 | cmake_generate_parameter_module_example.custom_validation 11 | ) 12 | 13 | ament_python_install_package(${PROJECT_NAME}) 14 | 15 | ament_package() 16 | -------------------------------------------------------------------------------- /example_cmake_python/README.md: -------------------------------------------------------------------------------- 1 | # Example: 2 | 3 | ## Build the node 4 | 5 | ``` 6 | mkdir -p colcon_ws/src 7 | cd colcon_ws/src 8 | git clone https://github.com/picknikrobotics/generate_parameter_library.git 9 | cd .. 10 | colcon build 11 | ``` 12 | 13 | ## Run the Python node 14 | 15 | ``` 16 | source install/setup.bash 17 | python3 src/generate_parameter_library/example_cmake_python/cmake_generate_parameter_module_example/minimal_publisher.py --ros-args --params-file src/generate_parameter_library/example_python/config/implementation.yaml 18 | ``` 19 | 20 | You should see an output like this: 21 | `[INFO] [1656018676.015816509] [admittance_controller]: Initial control frame parameter is: 'ee_link'` 22 | 23 | 24 | ## ROS 2 CLI 25 | 26 | Run the following: 27 | 28 | `ros2 param list` 29 | 30 | You should see: 31 | 32 | ``` 33 | /admittance_controller: 34 | admittance.damping_ratio 35 | admittance.mass 36 | admittance.selected_axes 37 | admittance.stiffness 38 | chainable_command_interfaces 39 | command_interfaces 40 | control.frame.external 41 | control.frame.id 42 | enable_parameter_update_without_reactivation 43 | fixed_array 44 | fixed_string 45 | fixed_string_no_default 46 | fixed_world_frame.frame.external 47 | fixed_world_frame.frame.id 48 | ft_sensor.filter_coefficient 49 | ft_sensor.frame.external 50 | ft_sensor.frame.id 51 | ft_sensor.name 52 | gravity_compensation.CoG.force 53 | gravity_compensation.CoG.pos 54 | gravity_compensation.frame.external 55 | gravity_compensation.frame.id 56 | interpolation_mode 57 | joints 58 | kinematics.alpha 59 | kinematics.base 60 | kinematics.group_name 61 | kinematics.plugin_name 62 | kinematics.plugin_package 63 | kinematics.tip 64 | one_number 65 | pid.elbow_joint.d 66 | pid.elbow_joint.i 67 | pid.elbow_joint.p 68 | pid.rate 69 | pid.shoulder_lift_joint.d 70 | pid.shoulder_lift_joint.i 71 | pid.shoulder_lift_joint.p 72 | pid.shoulder_pan_joint.d 73 | pid.shoulder_pan_joint.i 74 | pid.shoulder_pan_joint.p 75 | pid.wrist_1_joint.d 76 | pid.wrist_1_joint.i 77 | pid.wrist_1_joint.p 78 | pid.wrist_2_joint.d 79 | pid.wrist_2_joint.i 80 | pid.wrist_2_joint.p 81 | pid.wrist_3_joint.d 82 | pid.wrist_3_joint.i 83 | pid.wrist_3_joint.p 84 | qos_overrides./parameter_events.publisher.depth 85 | qos_overrides./parameter_events.publisher.durability 86 | qos_overrides./parameter_events.publisher.history 87 | qos_overrides./parameter_events.publisher.reliability 88 | scientific_notation_num 89 | state_interfaces 90 | three_numbers 91 | three_numbers_of_five 92 | use_feedforward_commanded_input 93 | use_sim_time 94 | ``` 95 | 96 | All parameter are automatically declared and callbacks are setup by default. You can set a parameter by typing: 97 | 98 | `ros2 param set /admittance_controller control.frame.id new_frame` 99 | 100 | You should see: 101 | 102 | `[INFO] [1656019001.515820371] [admittance_controller]: New control frame parameter is: 'new_frame'` 103 | 104 | Congratulations, you updated the parameter! 105 | 106 | If you try to set a parameter that is read only, you will get an error. Running the following 107 | 108 | `ros2 param set /admittance_controller command_interfaces ["velocity"]` 109 | 110 | will result in the error 111 | 112 | `Setting parameter failed: Trying to set a read-only parameter: command_interfaces.` 113 | 114 | Running the following 115 | 116 | `ros2 param describe /admittance_controller admittance.damping_ratio` 117 | 118 | will show a parameter's description 119 | 120 | ``` 121 | Parameter name: admittance.damping_ratio 122 | Type: double array 123 | Description: specifies damping ratio values for x, y, z, rx, ry, and rz used in the admittance calculation. The values are calculated as damping can be used instead: zeta = D / (2 * sqrt( M * S )) 124 | Constraints: 125 | Min value: 0.1 126 | Max value: 10.0 127 | ``` 128 | 129 | If you try to set a value out of the specified bounds, 130 | 131 | `ros2 param set /admittance_controller admittance.damping_ratio [-10.0,-10.0,-10.0,-10.0,-10.0,-10.0]` 132 | 133 | you will get the error 134 | 135 | `Setting parameter failed: Value array('d', [-10.0, -10.0, -10.0, -10.0, -10.0, -10.0]) in parameter 'admittance.damping_ratio' must be within bounds [0.1, 10.0]` 136 | 137 | If you try to set a vector parameter with the wrong length, 138 | 139 | `ros2 param set /admittance_controller admittance.damping_ratio [1.0,1.0,1.0]` 140 | 141 | you will get the error 142 | 143 | `Setting parameter failed: Length of parameter 'admittance.damping_ratio' is '3' but must be equal to 6` 144 | 145 | If you try to load a yaml file with missing required parameters 146 | 147 | `python3 src/generate_parameter_library/example_cmake_python/cmake_generate_parameter_module_example/minimal_publisher.py --ros-args --params-file src/generate_parameter_library/example_python/config/missing_required.yaml` 148 | 149 | you will get the error 150 | 151 | ``` 152 | Traceback (most recent call last): 153 | [...] 154 | rclpy.exceptions.ParameterUninitializedException: The parameter 'fixed_string_no_default' is not initialized 155 | [ros2run]: Process exited with failure 1 156 | ``` 157 | -------------------------------------------------------------------------------- /example_cmake_python/cmake_generate_parameter_module_example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PickNikRobotics/generate_parameter_library/7735ac9c4ec743add344ad3d41b7cbb1824e415d/example_cmake_python/cmake_generate_parameter_module_example/__init__.py -------------------------------------------------------------------------------- /example_cmake_python/cmake_generate_parameter_module_example/custom_validation.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2023 PickNik Inc. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright 8 | # notice, this list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright 11 | # notice, this list of conditions and the following disclaimer in the 12 | # documentation and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of the PickNik Inc. nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 22 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | # POSSIBILITY OF SUCH DAMAGE. 29 | 30 | 31 | def no_args_validator(param): 32 | return '' 33 | 34 | 35 | def validate_double_array_custom_func(param, arg1, arg2): 36 | return '' 37 | -------------------------------------------------------------------------------- /example_cmake_python/cmake_generate_parameter_module_example/minimal_publisher.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2023 PickNik Inc. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright 8 | # notice, this list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright 11 | # notice, this list of conditions and the following disclaimer in the 12 | # documentation and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of the PickNik Inc. nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 22 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | # POSSIBILITY OF SUCH DAMAGE. 29 | 30 | import rclpy 31 | import rclpy.node 32 | 33 | from cmake_generate_parameter_module_example.admittance_parameters import ( 34 | admittance_controller, 35 | ) 36 | 37 | 38 | class MinimalParam(rclpy.node.Node): 39 | def __init__(self): 40 | super().__init__('admittance_controller') 41 | self.timer = self.create_timer(1, self.timer_callback) 42 | 43 | self.param_listener = admittance_controller.ParamListener(self) 44 | self.params = self.param_listener.get_params() 45 | self.get_logger().info( 46 | "Initial control frame parameter is: '%s'" % self.params.control.frame.id 47 | ) 48 | self.get_logger().info("fixed string is: '%s'" % self.params.fixed_string) 49 | 50 | self.get_logger().info( 51 | "Original joints parameter is: '%s'" % str(self.params.joints) 52 | ) 53 | for d in self.params.fixed_array: 54 | self.get_logger().info("value: '%s'" % str(d)) 55 | 56 | def timer_callback(self): 57 | if self.param_listener.is_old(self.params): 58 | self.param_listener.refresh_dynamic_parameters() 59 | self.params = self.param_listener.get_params() 60 | self.get_logger().info( 61 | "New control frame parameter is: '%s'" % self.params.control.frame.id 62 | ) 63 | self.get_logger().info("fixed string is: '%s'" % self.params.fixed_string) 64 | for d in self.params.fixed_array: 65 | self.get_logger().info("value: '%s'" % str(d)) 66 | 67 | 68 | def main(args=None): 69 | rclpy.init(args=args) 70 | node = MinimalParam() 71 | rclpy.spin(node) 72 | 73 | 74 | if __name__ == '__main__': 75 | main() 76 | -------------------------------------------------------------------------------- /example_cmake_python/cmake_generate_parameter_module_example/parameters.yaml: -------------------------------------------------------------------------------- 1 | admittance_controller: 2 | scientific_notation_num: 3 | type: double 4 | default_value: 0.00000000001 5 | description: "Test scientific notation" 6 | interpolation_mode: 7 | type: string 8 | default_value: "spline" 9 | description: "specifies which algorithm to use for interpolation." 10 | validation: 11 | one_of<>: [ [ "spline", "linear" ] ] 12 | "custom_validators::no_args_validator": null 13 | subset_selection: 14 | type: string_array 15 | default_value: ["A", "B"] 16 | description: "test subset of validator." 17 | validation: 18 | subset_of<>: [ [ "A", "B", "C"] ] 19 | joints: 20 | type: string_array 21 | default_value: ["joint1", "joint2", "joint3"] 22 | description: "specifies which joints will be used by the controller" 23 | dof_names: 24 | type: string_array 25 | default_value: ["x", "y", "rz"] 26 | description: "specifies which joints will be used by the controller" 27 | 28 | pid: 29 | rate: 30 | type: double 31 | default_value: 0.005 32 | description: "update loop period in seconds" 33 | 34 | __map_joints: 35 | p: 36 | type: double 37 | default_value: 1.0 38 | description: "proportional gain term" 39 | validation: 40 | gt_eq<>: [ 0.0001 ] 41 | i: 42 | type: double 43 | default_value: 1.0 44 | description: "integral gain term" 45 | d: 46 | type: double 47 | default_value: 1.0 48 | description: "derivative gain term" 49 | 50 | gains: 51 | __map_dof_names: 52 | k: 53 | type: double 54 | default_value: 2.0 55 | description: "general gain" 56 | 57 | fixed_string: 58 | type: string_fixed_25 59 | default_value: "string_value" 60 | description: "test code generation for fixed sized string" 61 | fixed_array: 62 | type: double_array_fixed_10 63 | default_value: [1.0, 2.3, 4.0 ,5.4, 3.3] 64 | description: "test code generation for fixed sized array" 65 | fixed_string_no_default: 66 | type: string_fixed_25 67 | description: "test code generation for fixed sized string with no default" 68 | command_interfaces: 69 | type: string_array 70 | description: "specifies which command interfaces to claim" 71 | read_only: true 72 | 73 | state_interfaces: 74 | type: string_array 75 | description: "specifies which state interfaces to claim" 76 | read_only: true 77 | 78 | chainable_command_interfaces: 79 | type: string_array 80 | description: "specifies which chainable interfaces to claim" 81 | read_only: true 82 | 83 | kinematics: 84 | plugin_name: 85 | type: string 86 | description: "specifies which kinematics plugin to load" 87 | plugin_package: 88 | type: string 89 | description: "specifies the package to load the kinematics plugin from" 90 | base: 91 | type: string 92 | description: "specifies the base link of the robot description used by the kinematics plugin" 93 | tip: 94 | type: string 95 | description: "specifies the end effector link of the robot description used by the kinematics plugin" 96 | alpha: 97 | type: double 98 | default_value: 0.0005 99 | description: "specifies the damping coefficient for the Jacobian pseudo inverse" 100 | group_name: 101 | type: string 102 | description: "specifies the group name for planning with Moveit" 103 | 104 | ft_sensor: 105 | name: 106 | type: string 107 | description: "name of the force torque sensor in the robot description" 108 | frame: 109 | id: 110 | type: string 111 | description: "frame of the force torque sensor" 112 | external: 113 | type: bool 114 | default_value: false 115 | description: "specifies if the force torque sensor is contained in the kinematics chain from the base to the tip" 116 | filter_coefficient: 117 | type: double 118 | default_value: 0.005 119 | description: "specifies the coefficient for the sensor's exponential filter" 120 | 121 | control: 122 | frame: 123 | id: 124 | type: string 125 | description: "control frame used for admittance control" 126 | external: 127 | type: bool 128 | default_value: false 129 | description: "specifies if the control frame is contained in the kinematics chain from the base to the tip" 130 | 131 | fixed_world_frame: # Gravity points down (neg. Z) in this frame (Usually: world or base_link) 132 | frame: 133 | id: 134 | type: string 135 | description: "world frame, gravity points down (neg. Z) in this frame" 136 | external: 137 | type: bool 138 | default_value: false 139 | description: "specifies if the world frame is contained in the kinematics chain from the base to the tip" 140 | 141 | gravity_compensation: 142 | frame: 143 | id: 144 | type: string 145 | description: "frame which center of gravity (CoG) is defined in" 146 | external: 147 | type: bool 148 | default_value: false 149 | description: "specifies if the center of gravity frame is contained in the kinematics chain from the base to the tip" 150 | CoG: # specifies the center of gravity of the end effector 151 | pos: 152 | type: double_array 153 | description: "position of the center of gravity (CoG) in its frame" 154 | validation: 155 | fixed_size<>: 3 156 | force: 157 | type: double 158 | default_value: .NAN 159 | description: "weight of the end effector, e.g mass * 9.81" 160 | 161 | admittance: 162 | selected_axes: 163 | type: bool_array 164 | description: "specifies if the axes x, y, z, rx, ry, and rz are enabled" 165 | validation: 166 | fixed_size<>: 6 167 | 168 | # Having ".0" at the end is MUST, otherwise there is a loading error 169 | # F = M*a + D*v + S*(x - x_d) 170 | mass: 171 | type: double_array 172 | description: "specifies mass values for x, y, z, rx, ry, and rz used in the admittance calculation" 173 | validation: 174 | fixed_size<>: 6 175 | element_bounds<>: [ 0.0001, 1000000.0 ] 176 | 177 | damping_ratio: 178 | type: double_array 179 | description: "specifies damping ratio values for x, y, z, rx, ry, and rz used in the admittance calculation. 180 | The values are calculated as damping can be used instead: zeta = D / (2 * sqrt( M * S ))" 181 | validation: 182 | fixed_size<>: 6 183 | "custom_validators::validate_double_array_custom_func": [ 20.3, 5.0 ] 184 | element_bounds<>: [ 0.1, 10.0 ] 185 | 186 | stiffness: 187 | type: double_array 188 | description: "specifies stiffness values for x, y, z, rx, ry, and rz used in the admittance calculation" 189 | validation: 190 | element_bounds: [ 0.0001, 100000.0 ] 191 | 192 | # general settings 193 | enable_parameter_update_without_reactivation: 194 | type: bool 195 | default_value: true 196 | description: "if enabled, read_only parameters will be dynamically updated in the control loop" 197 | use_feedforward_commanded_input: 198 | type: bool 199 | default_value: false 200 | description: "if enabled, the velocity commanded to the admittance controller is added to its calculated admittance velocity" 201 | lt_eq_fifteen: 202 | type: int 203 | default_value: 1 204 | description: "should be a number less than or equal to 15" 205 | validation: 206 | lt_eq<>: [ 15 ] 207 | gt_fifteen: 208 | type: int 209 | default_value: 16 210 | description: "should be a number greater than 15" 211 | validation: 212 | gt<>: [ 15 ] 213 | one_number: 214 | type: int 215 | default_value: 14540 216 | read_only: true 217 | validation: 218 | bounds<>: [ 1024, 65535 ] 219 | three_numbers: 220 | type: int_array 221 | default_value: [3,4,5] 222 | read_only: true 223 | three_numbers_of_five: 224 | type: int_array_fixed_5 225 | default_value: [3,3,3] 226 | read_only: true 227 | -------------------------------------------------------------------------------- /example_cmake_python/config/implementation.yaml: -------------------------------------------------------------------------------- 1 | admittance_controller: 2 | ros__parameters: 3 | joints: 4 | - shoulder_pan_joint 5 | - shoulder_lift_joint 6 | - elbow_joint 7 | - wrist_1_joint 8 | - wrist_2_joint 9 | - wrist_3_joint 10 | 11 | fixed_string_no_default: 12 | "happy" 13 | 14 | pid: 15 | shoulder_pan_joint: 16 | i: 0.7 17 | shoulder_lift_joint: 18 | i: 0.5 19 | elbow_joint: 20 | i: 0.2 21 | wrist_1_joint: 22 | i: 1.2 23 | wrist_2_joint: 24 | i: 0.8 25 | wrist_3_joint: 26 | i: 0.5 27 | 28 | command_interfaces: 29 | - position 30 | 31 | state_interfaces: 32 | - position 33 | - velocity 34 | 35 | chainable_command_interfaces: 36 | - position 37 | - velocity 38 | 39 | kinematics: 40 | plugin_name: kdl_plugin/KDLKinematics 41 | plugin_package: kinematics_interface_kdl 42 | base: base_link # Assumed to be stationary 43 | tip: ee_link 44 | group_name: ur5e_manipulator 45 | alpha: 0.0005 46 | 47 | ft_sensor: 48 | name: tcp_fts_sensor 49 | frame: 50 | id: ee_link # ee_link Wrench measurements are in this frame 51 | external: false # force torque frame exists within URDF kinematic chain 52 | filter_coefficient: 0.005 53 | 54 | control: 55 | frame: 56 | id: ee_link # Admittance calcs (displacement etc) are done in this frame. Usually the tool or end-effector 57 | external: false # control frame exists within URDF kinematic chain 58 | 59 | fixed_world_frame: 60 | frame: # Gravity points down (neg. Z) in this frame (Usually: world or base_link) 61 | id: base_link # Admittance calcs (displacement etc) are done in this frame. Usually the tool or end-effector 62 | external: false # control frame exists within URDF kinematic chain 63 | 64 | gravity_compensation: 65 | frame: 66 | id: ee_link 67 | external: false 68 | 69 | CoG: # specifies the center of gravity of the end effector 70 | pos: 71 | - 0.1 # x 72 | - 0.0 # y 73 | - 0.0 # z 74 | force: 23.0 # mass * 9.81 75 | 76 | admittance: 77 | selected_axes: 78 | - true # x 79 | - true # y 80 | - true # z 81 | - true # rx 82 | - true # ry 83 | - true # rz 84 | 85 | # Having ".0" at the end is MUST, otherwise there is a loading error 86 | # F = M*a + D*v + S*(x - x_d) 87 | mass: 88 | - 3.0 # x 89 | - 3.0 # y 90 | - 3.0 # z 91 | - 0.05 # rx 92 | - 0.05 # ry 93 | - 0.05 # rz 94 | 95 | damping_ratio: # damping can be used instead: zeta = D / (2 * sqrt( M * S )) 96 | - 2.828427 # x 97 | - 2.828427 # y 98 | - 2.828427 # z 99 | - 2.828427 # rx 100 | - 2.828427 # ry 101 | - 2.828427 # rz 102 | 103 | stiffness: 104 | - 50.0 # x 105 | - 50.0 # y 106 | - 50.0 # z 107 | - 1.0 # rx 108 | - 1.0 # ry 109 | - 1.0 # rz 110 | 111 | # general settings 112 | enable_parameter_update_without_reactivation: true 113 | use_feedforward_commanded_input: true 114 | -------------------------------------------------------------------------------- /example_cmake_python/config/missing_required.yaml: -------------------------------------------------------------------------------- 1 | admittance_controller: 2 | ros__parameters: 3 | mass: 4 | - -10.0 # x 5 | - -10.0 # y 6 | - 3.0 # z 7 | - 0.05 # rx 8 | - 0.05 # ry 9 | - 0.05 # rz 10 | -------------------------------------------------------------------------------- /example_cmake_python/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | cmake_generate_parameter_module_example 5 | 0.4.0 6 | Example usage of generate_parameter_library for a python module with cmake. 7 | Paul Gesel 8 | Nathan Brooks 9 | Shaurya Kumar 10 | BSD-3-Clause 11 | Paul Gesel 12 | 13 | generate_parameter_library 14 | rclpy 15 | 16 | ament_cmake_python 17 | 18 | ament_lint_auto 19 | ament_lint_common 20 | 21 | 22 | ament_cmake 23 | 24 | 25 | -------------------------------------------------------------------------------- /example_external/CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2 | Changelog for package generate_parameter_library_example_external 3 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 4 | 5 | 0.4.0 (2025-01-13) 6 | ------------------ 7 | * Change header install path (`#213 `_) 8 | * Contributors: Auguste Bourgois 9 | -------------------------------------------------------------------------------- /example_external/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(generate_parameter_library_example_external) 3 | 4 | if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") 5 | add_compile_options(-Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wsign-conversion -Wold-style-cast) 6 | endif() 7 | 8 | find_package(ament_cmake REQUIRED) 9 | find_package(ament_cmake_python REQUIRED) 10 | find_package(rclcpp REQUIRED) 11 | find_package(rclcpp_components REQUIRED) 12 | find_package(rclpy REQUIRED) 13 | find_package(generate_parameter_library_example REQUIRED) 14 | 15 | add_library(minimal_publisher_external SHARED 16 | src/minimal_publisher_external.cpp 17 | ) 18 | target_include_directories(minimal_publisher_external PUBLIC 19 | $ 20 | $ 21 | ) 22 | target_link_libraries(minimal_publisher_external 23 | PUBLIC 24 | rclcpp::rclcpp 25 | rclcpp_components::component 26 | generate_parameter_library_example::admittance_controller_parameters 27 | ) 28 | rclcpp_components_register_node(minimal_publisher_external 29 | PLUGIN "admittance_controller::MinimalPublisher" 30 | EXECUTABLE test_node 31 | ) 32 | 33 | install( 34 | DIRECTORY include/ 35 | DESTINATION include/generate_parameter_library_example_external 36 | ) 37 | 38 | install(TARGETS minimal_publisher_external 39 | EXPORT export_generate_parameter_library_example_external 40 | ARCHIVE DESTINATION lib 41 | LIBRARY DESTINATION lib 42 | RUNTIME DESTINATION bin 43 | ) 44 | 45 | install( 46 | TARGETS test_node 47 | DESTINATION lib/generate_parameter_library_example_external 48 | ) 49 | 50 | ament_export_targets(export_generate_parameter_library_example_external HAS_LIBRARY_TARGET) 51 | ament_export_dependencies(rclcpp rclcpp_components generate_parameter_library_example) 52 | ament_package() 53 | -------------------------------------------------------------------------------- /example_external/README.md: -------------------------------------------------------------------------------- 1 | # Using parameters defined in another package 2 | 3 | This package is a minimal example demonstrating how the parameters defined in `generate_parameter_library/example` 4 | can be used in a different package (i.e. the current one : `generate_parameter_library/example_external`). 5 | 6 | In particular, check the `CMakeLists.txt` file and the `#include` instructions in the source files. 7 | 8 | ## Build the node 9 | 10 | ``` 11 | mkdir colcon_ws 12 | mkdir colcon_ws/src 13 | cd colcon_ws/src 14 | git clone https://github.com/picknikrobotics/generate_parameter_library.git 15 | cd .. 16 | colcon build 17 | ``` 18 | 19 | ## Run the C++ node 20 | 21 | ``` 22 | source install/setup.bash 23 | ros2 run generate_parameter_library_example_external test_node --ros-args --params-file src/generate_parameter_library/example_external/config/implementation.yaml 24 | ``` 25 | 26 | You should see an output like this: 27 | `[INFO] [1656018676.015816509] [admittance_controller]: Control frame is: 'ee_link'` 28 | 29 | ## ROS 2 CLI 30 | 31 | Run the following: 32 | 33 | `ros2 param list` 34 | 35 | You should see: 36 | 37 | ``` 38 | /admittance_controller: 39 | admittance.damping_ratio 40 | admittance.mass 41 | admittance.selected_axes 42 | admittance.stiffness 43 | chainable_command_interfaces 44 | command_interfaces 45 | control.frame.external 46 | control.frame.id 47 | enable_parameter_update_without_reactivation 48 | fixed_array 49 | fixed_string 50 | fixed_string_no_default 51 | fixed_world_frame.frame.external 52 | fixed_world_frame.frame.id 53 | ft_sensor.filter_coefficient 54 | ft_sensor.frame.external 55 | ft_sensor.frame.id 56 | ft_sensor.name 57 | gravity_compensation.CoG.force 58 | gravity_compensation.CoG.pos 59 | gravity_compensation.frame.external 60 | gravity_compensation.frame.id 61 | interpolation_mode 62 | joints 63 | kinematics.alpha 64 | kinematics.base 65 | kinematics.group_name 66 | kinematics.plugin_name 67 | kinematics.plugin_package 68 | kinematics.tip 69 | one_number 70 | pid.elbow_joint.d 71 | pid.elbow_joint.i 72 | pid.elbow_joint.p 73 | pid.rate 74 | pid.shoulder_lift_joint.d 75 | pid.shoulder_lift_joint.i 76 | pid.shoulder_lift_joint.p 77 | pid.shoulder_pan_joint.d 78 | pid.shoulder_pan_joint.i 79 | pid.shoulder_pan_joint.p 80 | pid.wrist_1_joint.d 81 | pid.wrist_1_joint.i 82 | pid.wrist_1_joint.p 83 | pid.wrist_2_joint.d 84 | pid.wrist_2_joint.i 85 | pid.wrist_2_joint.p 86 | pid.wrist_3_joint.d 87 | pid.wrist_3_joint.i 88 | pid.wrist_3_joint.p 89 | qos_overrides./parameter_events.publisher.depth 90 | qos_overrides./parameter_events.publisher.durability 91 | qos_overrides./parameter_events.publisher.history 92 | qos_overrides./parameter_events.publisher.reliability 93 | scientific_notation_num 94 | state_interfaces 95 | three_numbers 96 | three_numbers_of_five 97 | use_feedforward_commanded_input 98 | use_sim_time 99 | ``` 100 | 101 | All parameters are automatically declared and callbacks are setup by default. 102 | You can set a parameter by typing: 103 | 104 | `ros2 param set /admittance_controller control.frame.id new_frame` 105 | 106 | You should see: 107 | 108 | `[INFO] [1656019001.515820371] [admittance_controller]: New control frame parameter is: 'new_frame'` 109 | 110 | Congratulations, you updated the parameter! 111 | 112 | If you try to set a parameter that is read only, you will get an error. 113 | Running the following 114 | 115 | `ros2 param set /admittance_controller command_interfaces ["velocity"]` 116 | 117 | will result in the error 118 | 119 | `Setting parameter failed: parameter 'command_interfaces' cannot be set because it is read-only` 120 | 121 | Running the following 122 | 123 | `ros2 param describe /admittance_controller admittance.damping_ratio` 124 | 125 | will show a parameter's description 126 | 127 | ``` 128 | Parameter name: admittance.damping_ratio 129 | Type: double array 130 | Description: specifies damping ratio values for x, y, z, rx, ry, and rz used in the admittance calculation. The values are calculated as damping can be used instead: zeta = D / (2 * sqrt( M * S )) 131 | Constraints: 132 | Min value: 0.1 133 | Max value: 10.0 134 | ``` 135 | 136 | If you try to set a value out of the specified bounds, 137 | 138 | `ros2 param set /admittance_controller admittance.damping_ratio [-10.0,-10.0,-10.0,-10.0,-10.0,-10.0]` 139 | 140 | you will get the error 141 | 142 | `Setting parameter failed: Value -10.0 in parameter 'admittance.damping_ratio' must be within bounds [0.1, 10.0]` 143 | 144 | If you try to set a vector parameter with the wrong length, 145 | 146 | `ros2 param set /admittance_controller admittance.damping_ratio [1.0,1.0,1.0]` 147 | 148 | you will get the error 149 | 150 | `Setting parameter failed: Length of parameter 'admittance.damping_ratio' is 3 but must be equal to 6` 151 | 152 | If you try to load a yaml file with missing required parameters 153 | 154 | `ros2 run generate_parameter_library_example test_node --ros-args --params-file src/generate_parameter_library/example_external/config/missing_required.yaml` 155 | 156 | you will get the error 157 | 158 | ``` 159 | terminate called after throwing an instance of 'rclcpp::exceptions::ParameterUninitializedException' 160 | what(): parameter 'fixed_string_no_default' is not initialized 161 | [ros2run]: Aborted 162 | ``` 163 | -------------------------------------------------------------------------------- /example_external/config/implementation.yaml: -------------------------------------------------------------------------------- 1 | admittance_controller: 2 | ros__parameters: 3 | joints: 4 | - shoulder_pan_joint 5 | - shoulder_lift_joint 6 | - elbow_joint 7 | - wrist_1_joint 8 | - wrist_2_joint 9 | - wrist_3_joint 10 | 11 | fixed_string_no_default: 12 | "happy" 13 | 14 | elbow_joint: 15 | x: 16 | weight: 2.0 17 | 18 | pid: 19 | shoulder_pan_joint: 20 | i: 0.7 21 | shoulder_lift_joint: 22 | i: 0.5 23 | elbow_joint: 24 | i: 0.2 25 | wrist_1_joint: 26 | i: 1.2 27 | wrist_2_joint: 28 | i: 0.8 29 | wrist_3_joint: 30 | i: 0.5 31 | 32 | command_interfaces: 33 | - position 34 | 35 | state_interfaces: 36 | - position 37 | - velocity 38 | 39 | chainable_command_interfaces: 40 | - position 41 | - velocity 42 | 43 | kinematics: 44 | plugin_name: kdl_plugin/KDLKinematics 45 | plugin_package: kinematics_interface_kdl 46 | base: base_link # Assumed to be stationary 47 | tip: ee_link 48 | group_name: ur5e_manipulator 49 | alpha: 0.0005 50 | 51 | ft_sensor: 52 | name: tcp_fts_sensor 53 | frame: 54 | id: ee_link # ee_link Wrench measurements are in this frame 55 | external: false # force torque frame exists within URDF kinematic chain 56 | filter_coefficient: 0.005 57 | 58 | control: 59 | frame: 60 | id: ee_link # Admittance calcs (displacement etc) are done in this frame. Usually the tool or end-effector 61 | external: false # control frame exists within URDF kinematic chain 62 | 63 | fixed_world_frame: 64 | frame: # Gravity points down (neg. Z) in this frame (Usually: world or base_link) 65 | id: base_link # Admittance calcs (displacement etc) are done in this frame. Usually the tool or end-effector 66 | external: false # control frame exists within URDF kinematic chain 67 | 68 | gravity_compensation: 69 | frame: 70 | id: ee_link 71 | external: false 72 | 73 | CoG: # specifies the center of gravity of the end effector 74 | pos: 75 | - 0.1 # x 76 | - 0.0 # y 77 | - 0.0 # z 78 | force: 23.0 # mass * 9.81 79 | 80 | admittance: 81 | selected_axes: 82 | - true # x 83 | - true # y 84 | - true # z 85 | - true # rx 86 | - true # ry 87 | - true # rz 88 | 89 | # Having ".0" at the end is MUST, otherwise there is a loading error 90 | # F = M*a + D*v + S*(x - x_d) 91 | mass: 92 | - 3.0 # x 93 | - 3.0 # y 94 | - 3.0 # z 95 | - 0.05 # rx 96 | - 0.05 # ry 97 | - 0.05 # rz 98 | 99 | damping_ratio: # damping can be used instead: zeta = D / (2 * sqrt( M * S )) 100 | - 2.828427 # x 101 | - 2.828427 # y 102 | - 2.828427 # z 103 | - 2.828427 # rx 104 | - 2.828427 # ry 105 | - 2.828427 # rz 106 | 107 | stiffness: 108 | - 50.0 # x 109 | - 50.0 # y 110 | - 50.0 # z 111 | - 1.0 # rx 112 | - 1.0 # ry 113 | - 1.0 # rz 114 | 115 | # general settings 116 | enable_parameter_update_without_reactivation: true 117 | use_feedforward_commanded_input: true 118 | -------------------------------------------------------------------------------- /example_external/config/missing_required.yaml: -------------------------------------------------------------------------------- 1 | admittance_controller: 2 | ros__parameters: 3 | mass: 4 | - -10.0 # x 5 | - -10.0 # y 6 | - 3.0 # z 7 | - 0.05 # rx 8 | - 0.05 # ry 9 | - 0.05 # rz 10 | -------------------------------------------------------------------------------- /example_external/include/generate_parameter_library_example_external/minimal_publisher_external.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Forssea Robotics 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 6 | // * Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // * Neither the name of Forssea Robotics nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | // POSSIBILITY OF SUCH DAMAGE. 28 | 29 | #pragma once 30 | 31 | #include 32 | #include 33 | 34 | #include 35 | 36 | namespace admittance_controller { 37 | 38 | class MinimalPublisher : public rclcpp::Node { 39 | public: 40 | MinimalPublisher(const rclcpp::NodeOptions& options = rclcpp::NodeOptions()); 41 | 42 | private: 43 | void timer_callback(); 44 | 45 | rclcpp::TimerBase::SharedPtr timer_; 46 | std::shared_ptr param_listener_; 47 | admittance_controller::Params params_; 48 | }; 49 | 50 | } // namespace admittance_controller 51 | 52 | RCLCPP_COMPONENTS_REGISTER_NODE(admittance_controller::MinimalPublisher) 53 | -------------------------------------------------------------------------------- /example_external/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | generate_parameter_library_example_external 5 | 0.4.0 6 | Example usage of a parameter header generated in another package. 7 | Paul Gesel 8 | Nathan Brooks 9 | Shaurya Kumar 10 | BSD-3-Clause 11 | Auguste Bourgois 12 | 13 | generate_parameter_library_example 14 | rclcpp 15 | rclcpp_components 16 | 17 | ament_cmake 18 | 19 | ament_cmake_core 20 | 21 | ament_lint_auto 22 | ament_lint_common 23 | 24 | 25 | ament_cmake 26 | 27 | 28 | -------------------------------------------------------------------------------- /example_external/src/minimal_publisher_external.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Forssea Robotics 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 6 | // * Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // * Neither the name of Forssea Robotics nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | // POSSIBILITY OF SUCH DAMAGE. 28 | 29 | #include "generate_parameter_library_example_external/minimal_publisher_external.hpp" 30 | 31 | #include 32 | 33 | #include 34 | 35 | using namespace std::chrono_literals; 36 | 37 | namespace admittance_controller { 38 | 39 | MinimalPublisher::MinimalPublisher(const rclcpp::NodeOptions& options) 40 | : Node("admittance_controller", options) { 41 | timer_ = create_wall_timer( 42 | 500ms, std::bind(&MinimalPublisher::timer_callback, this)); 43 | param_listener_ = 44 | std::make_shared(get_node_parameters_interface()); 45 | params_ = param_listener_->get_params(); 46 | 47 | [[maybe_unused]] StackParams s_params = param_listener_->get_stack_params(); 48 | 49 | RCLCPP_INFO(get_logger(), "Initial control frame parameter is: '%s'", 50 | params_.control.frame.id.c_str()); 51 | RCLCPP_INFO(get_logger(), "fixed string is: '%s'", 52 | std::string{params_.fixed_string}.c_str()); 53 | const tcb::span fixed_array = params_.fixed_array; 54 | for (auto d : fixed_array) { 55 | RCLCPP_INFO(get_logger(), "value: '%s'", std::to_string(d).c_str()); 56 | } 57 | } 58 | 59 | void MinimalPublisher::timer_callback() { 60 | if (param_listener_->is_old(params_)) { 61 | param_listener_->refresh_dynamic_parameters(); 62 | params_ = param_listener_->get_params(); 63 | RCLCPP_INFO(get_logger(), "New control frame parameter is: '%s'", 64 | params_.control.frame.id.c_str()); 65 | RCLCPP_INFO(get_logger(), "fixed string is: '%s'", 66 | std::string{params_.fixed_string}.c_str()); 67 | const tcb::span fixed_array = params_.fixed_array; 68 | for (auto d : fixed_array) { 69 | RCLCPP_INFO(get_logger(), "value: '%s'", std::to_string(d).c_str()); 70 | } 71 | } 72 | } 73 | 74 | } // namespace admittance_controller 75 | -------------------------------------------------------------------------------- /example_python/CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2 | Changelog for package generate_parameter_module_example 3 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 4 | 5 | 0.4.0 (2025-01-13) 6 | ------------------ 7 | * Unit test friendly folder structure for Python examples (`#237 `_) 8 | * Fix flake8 error in publisher example (`#229 `_) 9 | * Contributors: Sebastian Castro 10 | 11 | 0.3.9 (2024-10-27) 12 | ------------------ 13 | * Add "additional_constraints" support (`#221 `_) 14 | * Drop yaml brackets for consistency and readability (`#203 `_) 15 | * Contributors: David Revay, Tim Clephas 16 | 17 | 0.3.8 (2024-03-27) 18 | ------------------ 19 | * Restore functionality for mapped params with no struct name (`#185 _`) 20 | * Fix newline issue (`#176 `_) 21 | * fix new line rendering for Python 22 | * Support nested mapped parameters (`#166 `_) 23 | * Contributors: Paul Gesel, Sebastian Castro 24 | 25 | 0.3.7 (2024-01-12) 26 | ------------------ 27 | * Fix rolling CI (`#162 `_) 28 | * Split example/README.md into C++ and Python version; updated content (`#138 `_) 29 | * Contributors: Paul Gesel, chriseichmann 30 | 31 | 0.3.6 (2023-07-31) 32 | ------------------ 33 | * Prevent module import when running `setup.py clean` (`#137 `_) 34 | * Contributors: Paul Gesel 35 | 36 | 0.3.5 (2023-07-28) 37 | ------------------ 38 | * add dependency to python example package (`#136 `_) 39 | * Contributors: Paul Gesel 40 | 41 | 0.3.4 (2023-07-24) 42 | ------------------ 43 | * Fix Python install (`#122 `_) 44 | * Add Python support for generate_parameter_library (`#110 `_) 45 | Co-authored-by: Tyler Weaver 46 | * Contributors: Paul Gesel 47 | -------------------------------------------------------------------------------- /example_python/README.md: -------------------------------------------------------------------------------- 1 | # Example: 2 | 3 | ## Build the node 4 | 5 | ``` 6 | mkdir colcon_ws 7 | mkdir colcon_ws/src 8 | cd colcon_ws/src 9 | git clone https://github.com/picknikrobotics/generate_parameter_library.git 10 | cd .. 11 | colcon build 12 | ``` 13 | 14 | ## Run the Python node 15 | 16 | ``` 17 | source install/setup.bash 18 | ros2 run generate_parameter_module_example test_node --ros-args --params-file src/generate_parameter_library/example_python/config/implementation.yaml 19 | ``` 20 | 21 | You should see an output like this: 22 | `[INFO] [1656018676.015816509] [admittance_controller]: Initial control frame parameter is: 'ee_link'` 23 | 24 | 25 | ## ROS 2 CLI 26 | 27 | Run the following: 28 | 29 | `ros2 param list` 30 | 31 | You should see: 32 | 33 | ``` 34 | /admittance_controller: 35 | admittance.damping_ratio 36 | admittance.mass 37 | admittance.selected_axes 38 | admittance.stiffness 39 | chainable_command_interfaces 40 | command_interfaces 41 | control.frame.external 42 | control.frame.id 43 | enable_parameter_update_without_reactivation 44 | fixed_array 45 | fixed_string 46 | fixed_string_no_default 47 | fixed_world_frame.frame.external 48 | fixed_world_frame.frame.id 49 | ft_sensor.filter_coefficient 50 | ft_sensor.frame.external 51 | ft_sensor.frame.id 52 | ft_sensor.name 53 | gravity_compensation.CoG.force 54 | gravity_compensation.CoG.pos 55 | gravity_compensation.frame.external 56 | gravity_compensation.frame.id 57 | interpolation_mode 58 | joints 59 | kinematics.alpha 60 | kinematics.base 61 | kinematics.group_name 62 | kinematics.plugin_name 63 | kinematics.plugin_package 64 | kinematics.tip 65 | one_number 66 | pid.elbow_joint.d 67 | pid.elbow_joint.i 68 | pid.elbow_joint.p 69 | pid.rate 70 | pid.shoulder_lift_joint.d 71 | pid.shoulder_lift_joint.i 72 | pid.shoulder_lift_joint.p 73 | pid.shoulder_pan_joint.d 74 | pid.shoulder_pan_joint.i 75 | pid.shoulder_pan_joint.p 76 | pid.wrist_1_joint.d 77 | pid.wrist_1_joint.i 78 | pid.wrist_1_joint.p 79 | pid.wrist_2_joint.d 80 | pid.wrist_2_joint.i 81 | pid.wrist_2_joint.p 82 | pid.wrist_3_joint.d 83 | pid.wrist_3_joint.i 84 | pid.wrist_3_joint.p 85 | qos_overrides./parameter_events.publisher.depth 86 | qos_overrides./parameter_events.publisher.durability 87 | qos_overrides./parameter_events.publisher.history 88 | qos_overrides./parameter_events.publisher.reliability 89 | scientific_notation_num 90 | state_interfaces 91 | three_numbers 92 | three_numbers_of_five 93 | use_feedforward_commanded_input 94 | use_sim_time 95 | ``` 96 | 97 | All parameter are automatically declared and callbacks are setup by default. You can set a parameter by typing: 98 | 99 | `ros2 param set /admittance_controller control.frame.id new_frame` 100 | 101 | You should see: 102 | 103 | `[INFO] [1656019001.515820371] [admittance_controller]: New control frame parameter is: 'new_frame'` 104 | 105 | Congratulations, you updated the parameter! 106 | 107 | If you try to set a parameter that is read only, you will get an error. Running the following 108 | 109 | `ros2 param set /admittance_controller command_interfaces ["velocity"]` 110 | 111 | will result in the error 112 | 113 | `Setting parameter failed: Trying to set a read-only parameter: command_interfaces.` 114 | 115 | Running the following 116 | 117 | `ros2 param describe /admittance_controller admittance.damping_ratio` 118 | 119 | will show a parameter's description 120 | 121 | ``` 122 | Parameter name: admittance.damping_ratio 123 | Type: double array 124 | Description: specifies damping ratio values for x, y, z, rx, ry, and rz used in the admittance calculation. The values are calculated as damping can be used instead: zeta = D / (2 * sqrt( M * S )) 125 | Constraints: 126 | Min value: 0.1 127 | Max value: 10.0 128 | ``` 129 | 130 | If you try to set a value out of the specified bounds, 131 | 132 | `ros2 param set /admittance_controller admittance.damping_ratio [-10.0,-10.0,-10.0,-10.0,-10.0,-10.0]` 133 | 134 | you will get the error 135 | 136 | `Setting parameter failed: Value array('d', [-10.0, -10.0, -10.0, -10.0, -10.0, -10.0]) in parameter 'admittance.damping_ratio' must be within bounds [0.1, 10.0]` 137 | 138 | If you try to set a vector parameter with the wrong length, 139 | 140 | `ros2 param set /admittance_controller admittance.damping_ratio [1.0,1.0,1.0]` 141 | 142 | you will get the error 143 | 144 | `Setting parameter failed: Length of parameter 'admittance.damping_ratio' is '3' but must be equal to 6` 145 | 146 | If you try to load a yaml file with missing required parameters 147 | 148 | `ros2 run generate_parameter_module_example test_node --ros-args --params-file src/generate_parameter_library/example_python/config/missing_required.yaml` 149 | 150 | you will get the error 151 | 152 | ``` 153 | Traceback (most recent call last): 154 | [...] 155 | rclpy.exceptions.ParameterUninitializedException: The parameter 'fixed_string_no_default' is not initialized 156 | [ros2run]: Process exited with failure 1 157 | ``` 158 | -------------------------------------------------------------------------------- /example_python/config/implementation.yaml: -------------------------------------------------------------------------------- 1 | admittance_controller: 2 | ros__parameters: 3 | joints: 4 | - shoulder_pan_joint 5 | - shoulder_lift_joint 6 | - elbow_joint 7 | - wrist_1_joint 8 | - wrist_2_joint 9 | - wrist_3_joint 10 | 11 | fixed_string_no_default: 12 | "happy" 13 | 14 | elbow_joint: 15 | x: 16 | weight: 2.0 17 | 18 | pid: 19 | shoulder_pan_joint: 20 | i: 0.7 21 | shoulder_lift_joint: 22 | i: 0.5 23 | elbow_joint: 24 | i: 0.2 25 | wrist_1_joint: 26 | i: 1.2 27 | wrist_2_joint: 28 | i: 0.8 29 | wrist_3_joint: 30 | i: 0.5 31 | 32 | command_interfaces: 33 | - position 34 | 35 | state_interfaces: 36 | - position 37 | - velocity 38 | 39 | chainable_command_interfaces: 40 | - position 41 | - velocity 42 | 43 | kinematics: 44 | plugin_name: kdl_plugin/KDLKinematics 45 | plugin_package: kinematics_interface_kdl 46 | base: base_link # Assumed to be stationary 47 | tip: ee_link 48 | group_name: ur5e_manipulator 49 | alpha: 0.0005 50 | 51 | ft_sensor: 52 | name: tcp_fts_sensor 53 | frame: 54 | id: ee_link # ee_link Wrench measurements are in this frame 55 | external: false # force torque frame exists within URDF kinematic chain 56 | filter_coefficient: 0.005 57 | 58 | control: 59 | frame: 60 | id: ee_link # Admittance calcs (displacement etc) are done in this frame. Usually the tool or end-effector 61 | external: false # control frame exists within URDF kinematic chain 62 | 63 | fixed_world_frame: 64 | frame: # Gravity points down (neg. Z) in this frame (Usually: world or base_link) 65 | id: base_link # Admittance calcs (displacement etc) are done in this frame. Usually the tool or end-effector 66 | external: false # control frame exists within URDF kinematic chain 67 | 68 | gravity_compensation: 69 | frame: 70 | id: ee_link 71 | external: false 72 | 73 | CoG: # specifies the center of gravity of the end effector 74 | pos: 75 | - 0.1 # x 76 | - 0.0 # y 77 | - 0.0 # z 78 | force: 23.0 # mass * 9.81 79 | 80 | admittance: 81 | selected_axes: 82 | - true # x 83 | - true # y 84 | - true # z 85 | - true # rx 86 | - true # ry 87 | - true # rz 88 | 89 | # Having ".0" at the end is MUST, otherwise there is a loading error 90 | # F = M*a + D*v + S*(x - x_d) 91 | mass: 92 | - 3.0 # x 93 | - 3.0 # y 94 | - 3.0 # z 95 | - 0.05 # rx 96 | - 0.05 # ry 97 | - 0.05 # rz 98 | 99 | damping_ratio: # damping can be used instead: zeta = D / (2 * sqrt( M * S )) 100 | - 2.828427 # x 101 | - 2.828427 # y 102 | - 2.828427 # z 103 | - 2.828427 # rx 104 | - 2.828427 # ry 105 | - 2.828427 # rz 106 | 107 | stiffness: 108 | - 50.0 # x 109 | - 50.0 # y 110 | - 50.0 # z 111 | - 1.0 # rx 112 | - 1.0 # ry 113 | - 1.0 # rz 114 | 115 | # general settings 116 | enable_parameter_update_without_reactivation: true 117 | use_feedforward_commanded_input: true 118 | -------------------------------------------------------------------------------- /example_python/config/missing_required.yaml: -------------------------------------------------------------------------------- 1 | admittance_controller: 2 | ros__parameters: 3 | mass: 4 | - -10.0 # x 5 | - -10.0 # y 6 | - 3.0 # z 7 | - 0.05 # rx 8 | - 0.05 # ry 9 | - 0.05 # rz 10 | -------------------------------------------------------------------------------- /example_python/generate_parameter_module_example/custom_validation.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2023 PickNik Inc. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright 8 | # notice, this list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright 11 | # notice, this list of conditions and the following disclaimer in the 12 | # documentation and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of the PickNik Inc. nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 22 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | # POSSIBILITY OF SUCH DAMAGE. 29 | 30 | 31 | def no_args_validator(param): 32 | return '' 33 | 34 | 35 | def validate_double_array_custom_func(param, arg1, arg2): 36 | return '' 37 | -------------------------------------------------------------------------------- /example_python/generate_parameter_module_example/minimal_publisher.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2023 PickNik Inc. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright 8 | # notice, this list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright 11 | # notice, this list of conditions and the following disclaimer in the 12 | # documentation and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of the PickNik Inc. nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 22 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | # POSSIBILITY OF SUCH DAMAGE. 29 | 30 | from generate_parameter_module_example.admittance_parameters import ( 31 | admittance_controller, 32 | ) 33 | 34 | import rclpy 35 | import rclpy.node 36 | 37 | 38 | class MinimalParam(rclpy.node.Node): 39 | 40 | def __init__(self): 41 | super().__init__('admittance_controller') 42 | self.timer = self.create_timer(1, self.timer_callback) 43 | 44 | self.param_listener = admittance_controller.ParamListener(self) 45 | self.params = self.param_listener.get_params() 46 | self.get_logger().info( 47 | "Initial control frame parameter is: '%s'" % self.params.control.frame.id 48 | ) 49 | self.get_logger().info("fixed string is: '%s'" % self.params.fixed_string) 50 | 51 | self.get_logger().info( 52 | "Original joints parameter is: '%s'" % str(self.params.joints) 53 | ) 54 | for d in self.params.fixed_array: 55 | self.get_logger().info("value: '%s'" % str(d)) 56 | 57 | def timer_callback(self): 58 | if self.param_listener.is_old(self.params): 59 | self.param_listener.refresh_dynamic_parameters() 60 | self.params = self.param_listener.get_params() 61 | self.get_logger().info( 62 | "New control frame parameter is: '%s'" % self.params.control.frame.id 63 | ) 64 | self.get_logger().info("fixed string is: '%s'" % self.params.fixed_string) 65 | for d in self.params.fixed_array: 66 | self.get_logger().info("value: '%s'" % str(d)) 67 | 68 | 69 | def main(args=None): 70 | rclpy.init(args=args) 71 | node = MinimalParam() 72 | rclpy.spin(node) 73 | 74 | 75 | if __name__ == '__main__': 76 | main() 77 | -------------------------------------------------------------------------------- /example_python/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | generate_parameter_module_example 5 | 0.4.0 6 | Example usage of generate_parameter_library for a python module 7 | paul 8 | Nathan Brooks 9 | Shaurya Kumar 10 | BSD-3-Clause 11 | 12 | generate_parameter_library 13 | generate_parameter_library_py 14 | rclpy 15 | 16 | ament_copyright 17 | ament_flake8 18 | ament_pep257 19 | python3-pytest 20 | 21 | 22 | ament_python 23 | 24 | 25 | -------------------------------------------------------------------------------- /example_python/resource/generate_parameter_module_example: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PickNikRobotics/generate_parameter_library/7735ac9c4ec743add344ad3d41b7cbb1824e415d/example_python/resource/generate_parameter_module_example -------------------------------------------------------------------------------- /example_python/setup.cfg: -------------------------------------------------------------------------------- 1 | [develop] 2 | script_dir=$base/lib/generate_parameter_module_example 3 | [install] 4 | install_scripts=$base/lib/generate_parameter_module_example 5 | -------------------------------------------------------------------------------- /example_python/setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import sys 3 | 4 | from setuptools import setup 5 | 6 | package_name = 'generate_parameter_module_example' 7 | 8 | if len(sys.argv) >= 2 and sys.argv[1] != 'clean': 9 | from generate_parameter_library_py.setup_helper import generate_parameter_module 10 | 11 | # set module_name and yaml file 12 | module_name = 'admittance_parameters' 13 | yaml_file = 'generate_parameter_module_example/parameters.yaml' 14 | validation_module = 'generate_parameter_module_example.custom_validation' 15 | generate_parameter_module( 16 | module_name, yaml_file, validation_module=validation_module 17 | ) 18 | 19 | setup( 20 | name=package_name, 21 | version='0.4.0', 22 | packages=[package_name], 23 | data_files=[ 24 | ('share/ament_index/resource_index/packages', ['resource/' + package_name]), 25 | ('share/' + package_name, ['package.xml']), 26 | ], 27 | install_requires=['setuptools'], 28 | zip_safe=True, 29 | maintainer='Paul Gesel', 30 | maintainer_email='paulgesel@gmail.com', 31 | description='Example usage of generate_parameter_library for a python module', 32 | license='BSD-3-Clause', 33 | tests_require=['pytest'], 34 | entry_points={ 35 | 'console_scripts': [ 36 | 'test_node = generate_parameter_module_example.minimal_publisher:main' 37 | ], 38 | }, 39 | ) 40 | -------------------------------------------------------------------------------- /example_python/test/test_copyright.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2015 Open Source Robotics Foundation, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | from ament_copyright.main import main 17 | import pytest 18 | 19 | 20 | # Remove the `skip` decorator once the source file(s) have a copyright header 21 | @pytest.mark.skip( 22 | reason='No copyright header has been placed in the generated source file.' 23 | ) 24 | @pytest.mark.copyright 25 | @pytest.mark.linter 26 | def test_copyright(): 27 | rc = main(argv=['.', 'test']) 28 | assert rc == 0, 'Found errors' 29 | -------------------------------------------------------------------------------- /example_python/test/test_flake8.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2017 Open Source Robotics Foundation, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | from ament_flake8.main import main_with_errors 17 | import pytest 18 | 19 | 20 | @pytest.mark.flake8 21 | @pytest.mark.linter 22 | def test_flake8(): 23 | rc, errors = main_with_errors(argv=[]) 24 | assert rc == 0, 'Found %d code style errors / warnings:\n' % len( 25 | errors 26 | ) + '\n'.join(errors) 27 | -------------------------------------------------------------------------------- /example_python/test/test_load_modules.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import generate_parameter_module_example 3 | 4 | print(f'Imported module from: {generate_parameter_module_example.__file__}') 5 | 6 | import generate_parameter_module_example.admittance_parameters # noqa: E402 7 | 8 | print('Imported generated parameter module') 9 | 10 | import generate_parameter_module_example.minimal_publisher # noqa: E402 11 | 12 | print('Imported minimal publisher module') 13 | -------------------------------------------------------------------------------- /example_python/test/test_pep257.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2015 Open Source Robotics Foundation, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | from ament_pep257.main import main 17 | import pytest 18 | 19 | 20 | @pytest.mark.linter 21 | @pytest.mark.pep257 22 | def test_pep257(): 23 | rc = main(argv=['.', 'test']) 24 | assert rc == 0, 'Found code style errors / warnings' 25 | -------------------------------------------------------------------------------- /generate_parameter_library/CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2 | Changelog for package generate_parameter_library 3 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 4 | 5 | 0.4.0 (2025-01-13) 6 | ------------------ 7 | * Change header install path (`#213 `_) 8 | * Contributors: Auguste Bourgois 9 | 10 | 0.3.9 (2024-10-27) 11 | ------------------ 12 | * Disable cache for program (`#218 `_) 13 | * Contributors: Paul Gesel 14 | 15 | 0.3.8 (2024-03-27) 16 | ------------------ 17 | * use python_install_dir (`#178 `_) 18 | * Update CMakeLists.txt (`#173 `_) 19 | * Contributors: Christoph Fröhlich, Paul Gesel 20 | 21 | 0.3.7 (2024-01-12) 22 | ------------------ 23 | * Enable generate_parameter_module through ament_cmake_python (`#161 `_) 24 | * Contributors: Paul Gesel 25 | 26 | 0.3.6 (2023-07-31) 27 | ------------------ 28 | 29 | 0.3.5 (2023-07-28) 30 | ------------------ 31 | 32 | 0.3.4 (2023-07-24) 33 | ------------------ 34 | * Add Python support for generate_parameter_library (`#110 `_) 35 | Co-authored-by: Tyler Weaver 36 | * Contributors: Paul Gesel 37 | 38 | 0.3.3 (2023-04-13) 39 | ------------------ 40 | 41 | 0.3.2 (2023-04-12) 42 | ------------------ 43 | 44 | 0.3.1 (2023-02-01) 45 | ------------------ 46 | * Add keyword INTERFACE to fix build error 'ar: no archive members specified' since the generated target is header-only (`#93 `_) 47 | * Make it easy for users to override (`#92 `_) 48 | * Contributors: Tyler Weaver, light-tech 49 | 50 | 0.3.0 (2022-11-15) 51 | ------------------ 52 | * Migrate from parameter_traits to RSL (take 2) (`#91 `_) 53 | * Contributors: Tyler Weaver 54 | 55 | 0.2.8 (2022-11-03) 56 | ------------------ 57 | 58 | 0.2.7 (2022-10-28) 59 | ------------------ 60 | * Standardize cmake (`#79 `_) 61 | * Contributors: Tyler Weaver 62 | 63 | 0.2.6 (2022-09-28) 64 | ------------------ 65 | * Depend on python package (`#75 `_) 66 | * Drop requirement for CMake to 3.16 (`#73 `_) 67 | * Added -- for ros-args to find (`#71 `_) 68 | * Contributors: Michael Wrock, Tyler Weaver 69 | 70 | 0.2.5 (2022-09-20) 71 | ------------------ 72 | * 🚀 Add cmake macros for using tests with example yaml files. 🤖 (`#57 `_) 73 | Co-authored-by: Tyler Weaver 74 | * Contributors: Denis Štogl 75 | 76 | 0.2.4 (2022-08-19) 77 | ------------------ 78 | * 0.2.3 79 | * Contributors: Tyler Weaver 80 | 81 | 0.2.3 (2022-08-05) 82 | ------------------ 83 | 84 | 0.2.2 (2022-08-03) 85 | ------------------ 86 | 87 | 0.2.1 (2022-08-02) 88 | ------------------ 89 | 90 | 0.2.0 (2022-08-01) 91 | ------------------ 92 | * Fixed length arrays (`#44 `_) 93 | * Change package name (`#40 `_) 94 | * Cleaup cmake paths (`#38 `_) 95 | * parameter validators interface library (`#32 `_) 96 | * Support exporting parameter libraries (`#28 `_) 97 | * Contributors: Paul Gesel, Tyler Weaver 98 | 99 | 0.1.0 (2022-07-27) 100 | ------------------ 101 | * CMake to generate ROS parameter library. 102 | * Contributors: Paul Gesel, Tyler Weaver 103 | -------------------------------------------------------------------------------- /generate_parameter_library/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | project(generate_parameter_library NONE) 3 | 4 | # find dependencies 5 | find_package(ament_cmake REQUIRED) 6 | 7 | install( 8 | DIRECTORY cmake 9 | DESTINATION share/generate_parameter_library 10 | ) 11 | 12 | if(BUILD_TESTING) 13 | find_package(ament_lint_auto REQUIRED) 14 | set(ament_cmake_cpplint_FOUND TRUE) # Conflicts with clang-foramt 15 | set(ament_cmake_flake8_FOUND TRUE) # Conflicts with black 16 | set(ament_cmake_uncrustify_FOUND TRUE) # Conflicts with clang-format 17 | ament_lint_auto_find_test_dependencies() 18 | endif() 19 | 20 | ament_package( 21 | CONFIG_EXTRAS "generate_parameter_library-extras.cmake" 22 | ) 23 | -------------------------------------------------------------------------------- /generate_parameter_library/generate_parameter_library-extras.cmake: -------------------------------------------------------------------------------- 1 | # Copyright 2022 PickNik Inc. 2 | # 3 | # Redistribution and use in source and binary forms, with or without 4 | # modification, are permitted provided that the following conditions are met: 5 | # 6 | # * Redistributions of source code must retain the above copyright 7 | # notice, this list of conditions and the following disclaimer. 8 | # 9 | # * Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # * Neither the name of the PickNik Inc. nor the names of its 14 | # contributors may be used to endorse or promote products derived from 15 | # this software without specific prior written permission. 16 | # 17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | # POSSIBILITY OF SUCH DAMAGE. 28 | 29 | find_package(fmt REQUIRED) 30 | find_package(parameter_traits REQUIRED) 31 | find_package(rclcpp REQUIRED) 32 | find_package(rsl REQUIRED) 33 | find_package(rclcpp_lifecycle REQUIRED) 34 | find_package(tcb_span REQUIRED) 35 | find_package(tl_expected REQUIRED) 36 | 37 | include("${generate_parameter_library_DIR}/generate_parameter_library.cmake") 38 | -------------------------------------------------------------------------------- /generate_parameter_library/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | generate_parameter_library 5 | 0.4.0 6 | CMake to generate ROS parameter library. 7 | Paul Gesel 8 | Nathan Brooks 9 | Shaurya Kumar 10 | BSD-3-Clause 11 | Paul Gesel 12 | 13 | ament_cmake 14 | ament_cmake_python 15 | 16 | generate_parameter_library_py 17 | 18 | fmt 19 | parameter_traits 20 | rclcpp 21 | rclpy 22 | rclcpp_lifecycle 23 | rsl 24 | tcb_span 25 | tl_expected 26 | 27 | ament_lint_auto 28 | ament_lint_common 29 | 30 | 31 | ament_cmake 32 | 33 | 34 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PickNikRobotics/generate_parameter_library/7735ac9c4ec743add344ad3d41b7cbb1824e415d/generate_parameter_library_py/generate_parameter_library_py/__init__.py -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/generate_cpp_header.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright 2022 PickNik Inc. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are met: 8 | # 9 | # * Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # 16 | # * Neither the name of the PickNik Inc. nor the names of its 17 | # contributors may be used to endorse or promote products derived from 18 | # this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | import argparse 33 | import sys 34 | import os 35 | 36 | from generate_parameter_library_py.parse_yaml import GenerateCode 37 | 38 | 39 | def run(output_file, yaml_file, validate_header=''): 40 | gen_param_struct = GenerateCode('cpp') 41 | output_dir = os.path.dirname(output_file) 42 | if not os.path.isdir(output_dir): 43 | os.makedirs(output_dir) 44 | 45 | gen_param_struct.parse(yaml_file, validate_header) 46 | 47 | code = str(gen_param_struct) 48 | with open(output_file, 'w') as f: 49 | f.write(code) 50 | 51 | 52 | def parse_args(): 53 | parser = argparse.ArgumentParser() 54 | parser.add_argument('output_cpp_header_file') 55 | parser.add_argument('input_yaml_file') 56 | parser.add_argument('validate_header', nargs='?', default='') 57 | return parser.parse_args() 58 | 59 | 60 | def main(): 61 | args = parse_args() 62 | output_file = args.output_cpp_header_file 63 | yaml_file = args.input_yaml_file 64 | validate_header = args.validate_header 65 | 66 | run(output_file, yaml_file, validate_header) 67 | 68 | 69 | if __name__ == '__main__': 70 | sys.exit(main()) 71 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/generate_python_module.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright 2022 PickNik Inc. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are met: 8 | # 9 | # * Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # 16 | # * Neither the name of the PickNik Inc. nor the names of its 17 | # contributors may be used to endorse or promote products derived from 18 | # this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | import argparse 33 | import sys 34 | import os 35 | 36 | from generate_parameter_library_py.parse_yaml import GenerateCode 37 | 38 | 39 | def run(output_file, yaml_file, validation_module=''): 40 | print(f'Running {__file__} {output_file} {yaml_file} {validation_module}') 41 | gen_param_struct = GenerateCode('python') 42 | output_dir = os.path.dirname(output_file) 43 | os.makedirs(output_dir, exist_ok=True) 44 | 45 | gen_param_struct.parse(yaml_file, validation_module) 46 | 47 | code = str(gen_param_struct) 48 | with open(output_file, 'w') as f: 49 | f.write(code) 50 | 51 | # Put an __init__.py file if one does not yet exist. 52 | init_file = os.path.join(os.path.dirname(output_file), '__init__.py') 53 | open(init_file, 'a').close() 54 | 55 | 56 | def parse_args(): 57 | parser = argparse.ArgumentParser() 58 | parser.add_argument('output_python_module_file') 59 | parser.add_argument('input_yaml_file') 60 | parser.add_argument('validate_file', nargs='?', default='') 61 | return parser.parse_args() 62 | 63 | 64 | def main(): 65 | args = parse_args() 66 | output_file = args.output_python_module_file 67 | yaml_file = args.input_yaml_file 68 | validate_file = args.validate_file 69 | run(output_file, yaml_file, validate_file) 70 | 71 | 72 | if __name__ == '__main__': 73 | sys.exit(main()) 74 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/cpp/declare_parameter: -------------------------------------------------------------------------------- 1 | if (!parameters_interface_->has_parameter(prefix_ + "{{parameter_name}}")) { 2 | {%- filter indent(width=4) %} 3 | rcl_interfaces::msg::ParameterDescriptor descriptor; 4 | descriptor.description = {{parameter_description | valid_string_cpp}}; 5 | descriptor.read_only = {{parameter_read_only}}; 6 | {%- if parameter_additional_constraints|length %} 7 | descriptor.additional_constraints = {{parameter_additional_constraints | valid_string_cpp}}; 8 | {% endif -%} 9 | {%- for validation in parameter_validations if ("bounds" in validation.function_name or "lt" in validation.function_name or "gt" in validation.function_name) %} 10 | {%- if "DOUBLE" in parameter_type %} 11 | {%- if validation.arguments|length == 2 %} 12 | descriptor.floating_point_range.resize({{loop.index}}); 13 | descriptor.floating_point_range.at({{loop.index0}}).from_value = {{validation.arguments[0]}}; 14 | descriptor.floating_point_range.at({{loop.index0}}).to_value = {{validation.arguments[1]}}; 15 | {%- elif ("lower" in validation.function_name or "gt" == validation.function_base_name or "gt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} 16 | descriptor.floating_point_range.resize({{loop.index}}); 17 | descriptor.floating_point_range.at({{loop.index0}}).from_value = {{validation.arguments[0]}}; 18 | descriptor.floating_point_range.at({{loop.index0}}).to_value = std::numeric_limits::max(); 19 | {%- elif ("upper" in validation.function_name or "lt" == validation.function_base_name or "lt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} 20 | descriptor.floating_point_range.resize({{loop.index}}); 21 | descriptor.floating_point_range.at({{loop.index0}}).from_value = std::numeric_limits::lowest(); 22 | descriptor.floating_point_range.at({{loop.index0}}).to_value = {{validation.arguments[0]}}; 23 | {%- endif %} 24 | {%- elif "INTEGER" in parameter_type %} 25 | {%- if validation.arguments|length == 2 %} 26 | descriptor.integer_range.resize({{loop.index}}); 27 | descriptor.integer_range.at({{loop.index0}}).from_value = {{validation.arguments[0]}}; 28 | descriptor.integer_range.at({{loop.index0}}).to_value = {{validation.arguments[1]}}; 29 | {%- elif ("lower" in validation.function_name or "gt" == validation.function_base_name or "gt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} 30 | descriptor.integer_range.resize({{loop.index}}); 31 | descriptor.integer_range.at({{loop.index0}}).from_value = {{validation.arguments[0]}}; 32 | descriptor.integer_range.at({{loop.index0}}).to_value = std::numeric_limits::max(); 33 | {%- elif ("upper" in validation.function_name or "lt" == validation.function_base_name or "lt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} 34 | descriptor.integer_range.resize({{loop.index}}); 35 | descriptor.integer_range.at({{loop.index0}}).from_value = std::numeric_limits::lowest(); 36 | descriptor.integer_range.at({{loop.index0}}).to_value = {{validation.arguments[0]}}; 37 | {%- endif %} 38 | {%- endif %} 39 | {%- endfor %} 40 | {%- if not parameter_value|length %} 41 | auto parameter = rclcpp::ParameterType::PARAMETER_{{parameter_type}}; 42 | {% endif -%} 43 | {%- if parameter_value|length %} 44 | auto parameter = to_parameter_value(updated_params.{{parameter_value}}); 45 | {% endif -%} 46 | parameters_interface_->declare_parameter(prefix_ + "{{parameter_name}}", parameter, descriptor); 47 | {% endfilter -%} 48 | } 49 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/cpp/declare_runtime_parameter: -------------------------------------------------------------------------------- 1 | {% for mapped_param in mapped_params -%} 2 | for (const auto & value_{{loop.index}} : updated_params.{{mapped_param}}) { 3 | {% endfor -%} 4 | {%- filter indent(width=4) -%} 5 | {% if struct_name != "" %} 6 | auto& entry = {{param_struct_instance}}.{{struct_name}}{% for map in parameter_map%}.{{map}}[value_{{loop.index}}]{% endfor %}; 7 | {% else %} 8 | auto& entry = {{param_struct_instance}}{% for map in parameter_map%}.{{map}}[value_{{loop.index}}]{% endfor %}; 9 | {% endif -%} 10 | std::string value = fmt::format("{%- for mapped_param in mapped_params -%}{% if loop.index == 1 %}{}{% else %}.{}{% endif -%} {%- endfor -%}", 11 | {%- for mapped_param in mapped_params -%}{% if loop.index == 1 %} value_{{loop.index}}{% else %}, value_{{loop.index}}{% endif -%} {%- endfor %}); 12 | {% if struct_name != "" %} 13 | auto param_name = fmt::format("{}{}.{}.{}", prefix_, "{{struct_name}}", value, "{{parameter_field}}"); 14 | {% else %} 15 | auto param_name = fmt::format("{}{}.{}", prefix_, value, "{{parameter_field}}"); 16 | {% endif -%} 17 | if (!parameters_interface_->has_parameter(param_name)) { 18 | {%- filter indent(width=4) %} 19 | rcl_interfaces::msg::ParameterDescriptor descriptor; 20 | descriptor.description = {{parameter_description | valid_string_cpp}}; 21 | descriptor.read_only = {{parameter_read_only}}; 22 | {%- if parameter_additional_constraints|length %} 23 | descriptor.additional_constraints = {{parameter_additional_constraints | valid_string_cpp}}; 24 | {% endif -%} 25 | {%- for validation in parameter_validations if ("bounds" in validation.function_name or "lt" in validation.function_name or "gt" in validation.function_name) %} 26 | {%- if "DOUBLE" in parameter_type %} 27 | {%- if validation.arguments|length == 2 %} 28 | descriptor.floating_point_range.resize({{loop.index}}); 29 | descriptor.floating_point_range.at({{loop.index0}}).from_value = {{validation.arguments[0]}}; 30 | descriptor.floating_point_range.at({{loop.index0}}).to_value = {{validation.arguments[1]}}; 31 | {%- elif ("lower" in validation.function_name or "gt" == validation.function_base_name or "gt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} 32 | descriptor.floating_point_range.resize({{loop.index}}); 33 | descriptor.floating_point_range.at({{loop.index0}}).from_value = {{validation.arguments[0]}}; 34 | descriptor.floating_point_range.at({{loop.index0}}).to_value = std::numeric_limits::max(); 35 | {%- elif ("upper" in validation.function_name or "lt" == validation.function_base_name or "lt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} 36 | descriptor.floating_point_range.resize({{loop.index}}); 37 | descriptor.floating_point_range.at({{loop.index0}}).from_value = std::numeric_limits::lowest(); 38 | descriptor.floating_point_range.at({{loop.index0}}).to_value = {{validation.arguments[0]}}; 39 | {%- endif %} 40 | {%- elif "INTEGER" in parameter_type %} 41 | {%- if validation.arguments|length == 2 %} 42 | descriptor.integer_range.resize({{loop.index}}); 43 | descriptor.integer_range.at({{loop.index0}}).from_value = {{validation.arguments[0]}}; 44 | descriptor.integer_range.at({{loop.index0}}).to_value = {{validation.arguments[1]}}; 45 | {%- elif ("lower" in validation.function_name or "gt" == validation.function_base_name or "gt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} 46 | descriptor.integer_range.resize({{loop.index}}); 47 | descriptor.integer_range.at({{loop.index0}}).from_value = {{validation.arguments[0]}}; 48 | descriptor.integer_range.at({{loop.index0}}).to_value = std::numeric_limits::max(); 49 | {%- elif ("upper" in validation.function_name or "lt" == validation.function_base_name or "lt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} 50 | descriptor.integer_range.resize({{loop.index}}); 51 | descriptor.integer_range.at({{loop.index0}}).from_value = std::numeric_limits::lowest(); 52 | descriptor.integer_range.at({{loop.index0}}).to_value = {{validation.arguments[0]}}; 53 | {%- endif %} 54 | {%- endif %} 55 | {%- endfor %} 56 | {%- if not default_value|length %} 57 | auto parameter = rclcpp::ParameterType::PARAMETER_{{parameter_type}}; 58 | {% endif -%} 59 | {%- if default_value|length %} 60 | auto parameter = rclcpp::ParameterValue(entry.{{parameter_field}}); 61 | {% endif -%} 62 | parameters_interface_->declare_parameter(param_name, parameter, descriptor); 63 | {% endfilter -%} 64 | } 65 | {{set_runtime_parameter-}} 66 | {% endfilter -%} 67 | {%- for mapped_param in mapped_params -%} 68 | } 69 | {% endfor -%} 70 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/cpp/declare_struct: -------------------------------------------------------------------------------- 1 | struct {{struct_name}} { 2 | {%- filter indent(width=4) %} 3 | {% if struct_fields|length -%} 4 | {{struct_fields-}} 5 | {% endif -%} 6 | {%if sub_structs|length -%} 7 | {{sub_structs-}} 8 | {% endif -%} 9 | {% endfilter -%} 10 | {% if struct_instance|length -%} 11 | } {{struct_instance}}; 12 | {% endif -%} 13 | {% if not struct_instance|length -%} 14 | }; 15 | std::map {{map_name-}}; 16 | {% endif -%} 17 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/cpp/declare_variable: -------------------------------------------------------------------------------- 1 | {% if value|length -%} 2 | {{type}} {{name}} = {{value}}; 3 | {% else -%} 4 | {{type}} {{name}}; 5 | {% endif -%} 6 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/cpp/parameter_library_header: -------------------------------------------------------------------------------- 1 | {{comments}} 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | {% if user_validation_file|length -%} 31 | #include "{{user_validation_file}}" 32 | {% endif %} 33 | 34 | namespace {{namespace}} { 35 | 36 | // Use validators from RSL 37 | using rsl::unique; 38 | using rsl::subset_of; 39 | using rsl::fixed_size; 40 | using rsl::size_gt; 41 | using rsl::size_lt; 42 | using rsl::not_empty; 43 | using rsl::element_bounds; 44 | using rsl::lower_element_bounds; 45 | using rsl::upper_element_bounds; 46 | using rsl::bounds; 47 | using rsl::lt; 48 | using rsl::gt; 49 | using rsl::lt_eq; 50 | using rsl::gt_eq; 51 | using rsl::one_of; 52 | using rsl::to_parameter_result_msg; 53 | 54 | // temporarily needed for backwards compatibility for custom validators 55 | using namespace parameter_traits; 56 | 57 | template 58 | [[nodiscard]] auto to_parameter_value(T value) { 59 | return rclcpp::ParameterValue(value); 60 | } 61 | 62 | template 63 | [[nodiscard]] auto to_parameter_value(rsl::StaticString const& value) { 64 | return rclcpp::ParameterValue(rsl::to_string(value)); 65 | } 66 | 67 | template 68 | [[nodiscard]] auto to_parameter_value(rsl::StaticVector const& value) { 69 | return rclcpp::ParameterValue(rsl::to_vector(value)); 70 | } 71 | 72 | {%- filter indent(width=4) %} 73 | struct Params { 74 | {%- filter indent(width=4) %} 75 | {{field_content-}} 76 | {{sub_struct_content-}} 77 | // for detecting if the parameter struct has been updated 78 | rclcpp::Time __stamp; 79 | {% endfilter -%} 80 | }; 81 | {%- endfilter %} 82 | 83 | {%- filter indent(width=4) %} 84 | struct StackParams { 85 | {%- filter indent(width=4) %} 86 | {{stack_field_content-}} 87 | {{stack_sub_struct_content-}} 88 | {% endfilter -%} 89 | }; 90 | {%- endfilter %} 91 | 92 | class ParamListener{ 93 | public: 94 | // throws rclcpp::exceptions::InvalidParameterValueException on initialization if invalid parameter are loaded 95 | ParamListener(rclcpp::Node::SharedPtr node, std::string const& prefix = "") 96 | : ParamListener(node->get_node_parameters_interface(), node->get_logger(), prefix) {} 97 | 98 | ParamListener(rclcpp_lifecycle::LifecycleNode::SharedPtr node, std::string const& prefix = "") 99 | : ParamListener(node->get_node_parameters_interface(), node->get_logger(), prefix) {} 100 | 101 | ParamListener(const std::shared_ptr& parameters_interface, 102 | std::string const& prefix = "") 103 | : ParamListener(parameters_interface, rclcpp::get_logger("{{namespace}}"), prefix) { 104 | RCLCPP_DEBUG(logger_, "ParameterListener: Not using node logger, recommend using other constructors to use a node logger"); 105 | } 106 | 107 | ParamListener(const std::shared_ptr& parameters_interface, 108 | rclcpp::Logger logger, std::string const& prefix = "") { 109 | logger_ = std::move(logger); 110 | prefix_ = prefix; 111 | if (!prefix_.empty() && prefix_.back() != '.') { 112 | prefix_ += "."; 113 | } 114 | 115 | parameters_interface_ = parameters_interface; 116 | declare_params(); 117 | auto update_param_cb = [this](const std::vector ¶meters){return this->update(parameters);}; 118 | handle_ = parameters_interface_->add_on_set_parameters_callback(update_param_cb); 119 | clock_ = rclcpp::Clock(); 120 | } 121 | 122 | Params get_params() const{ 123 | std::lock_guard lock(mutex_); 124 | return params_; 125 | } 126 | 127 | bool try_get_params(Params & params_in) const { 128 | if (mutex_.try_lock()) { 129 | if (const bool is_old = params_in.__stamp != params_.__stamp; is_old) { 130 | params_in = params_; 131 | } 132 | mutex_.unlock(); 133 | return true; 134 | } 135 | return false; 136 | } 137 | 138 | bool is_old(Params const& other) const { 139 | std::lock_guard lock(mutex_); 140 | return params_.__stamp != other.__stamp; 141 | } 142 | 143 | StackParams get_stack_params() { 144 | Params params = get_params(); 145 | StackParams output; 146 | 147 | {%- filter indent(width=6) %} 148 | {{set_stack_params}} 149 | {%- endfilter %} 150 | 151 | return output; 152 | } 153 | 154 | void refresh_dynamic_parameters() { 155 | auto updated_params = get_params(); 156 | // TODO remove any destroyed dynamic parameters 157 | {%- filter indent(width=6) %} 158 | {{remove_dynamic_parameters}} 159 | {%- endfilter %} 160 | // declare any new dynamic parameters 161 | rclcpp::Parameter param; 162 | {%- filter indent(width=6) %} 163 | {{update_declare_dynamic_parameters}} 164 | {%- endfilter %} 165 | } 166 | 167 | rcl_interfaces::msg::SetParametersResult update(const std::vector ¶meters) { 168 | auto updated_params = get_params(); 169 | 170 | for (const auto ¶m: parameters) { 171 | {%- filter indent(width=8) %} 172 | {{update_params_set}} 173 | {%- endfilter %} 174 | } 175 | {% if update_dynamic_parameters|length %} 176 | // update dynamic parameters 177 | for (const auto ¶m: parameters) { 178 | {%- filter indent(width=8) %} 179 | {{update_dynamic_parameters}} 180 | {%- endfilter %} 181 | } 182 | {%- endif %} 183 | updated_params.__stamp = clock_.now(); 184 | update_internal_params(updated_params); 185 | return rsl::to_parameter_result_msg({}); 186 | } 187 | 188 | void declare_params(){ 189 | auto updated_params = get_params(); 190 | // declare all parameters and give default values to non-required ones 191 | {%- filter indent(width=6) %} 192 | {{declare_params}} 193 | {%- endfilter %} 194 | // get parameters and fill struct fields 195 | rclcpp::Parameter param; 196 | 197 | {%- filter indent(width=6) %} 198 | {{declare_params_set}} 199 | {%- endfilter %} 200 | {% if declare_set_dynamic_params|length %} 201 | {% filter indent(width=6) %} 202 | // declare and set all dynamic parameters 203 | {{declare_set_dynamic_params}} 204 | {%- endfilter %} 205 | {%- endif %} 206 | 207 | updated_params.__stamp = clock_.now(); 208 | update_internal_params(updated_params); 209 | } 210 | 211 | private: 212 | void update_internal_params(Params updated_params) { 213 | std::lock_guard lock(mutex_); 214 | params_ = std::move(updated_params); 215 | } 216 | 217 | std::string prefix_; 218 | Params params_; 219 | rclcpp::Clock clock_; 220 | std::shared_ptr handle_; 221 | std::shared_ptr parameters_interface_; 222 | 223 | // rclcpp::Logger cannot be default-constructed 224 | // so we must provide a initialization here even though 225 | // every one of our constructors initializes logger_ 226 | rclcpp::Logger logger_ = rclcpp::get_logger("{{namespace}}"); 227 | std::mutex mutable mutex_; 228 | }; 229 | 230 | } // namespace {{namespace}} 231 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/cpp/parameter_validation: -------------------------------------------------------------------------------- 1 | if(auto validation_result = {{validation_function}}; 2 | !validation_result) { 3 | {%- filter indent(width=4) %} 4 | {{invalid_effect}} 5 | {% endfilter -%} 6 | } 7 | {% if valid_effect|length -%} 8 | {{valid_effect-}} 9 | {% endif -%} 10 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/cpp/remove_runtime_parameter: -------------------------------------------------------------------------------- 1 | { 2 | std::set {{mapped_param}}_set(updated_params.{{mapped_param}}.begin(), updated_params.{{mapped_param}}.end()); 3 | for (const auto &it: updated_params.{{parameter_map}}) { 4 | if ({{mapped_param}}_set.find(it.first) == {{mapped_param}}_set.end()) { 5 | {% if struct_name != "" %} 6 | auto param_name = fmt::format("{}{}.{}.{}", prefix_, "{{struct_name}}", it.first, "{{parameter_field}}"); 7 | {% else %} 8 | auto param_name = fmt::format("{}{}.{}", prefix_, it.first, "{{parameter_field}}"); 9 | {% endif -%} 10 | parameters_interface_->undeclare_parameter(param_name); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/cpp/set_parameter: -------------------------------------------------------------------------------- 1 | param = parameters_interface_->get_parameter(prefix_ + "{{parameter_name}}"); 2 | RCLCPP_DEBUG_STREAM(logger_, param.get_name() << ": " << param.get_type_name() << " = " << param.value_to_string()); 3 | {% if parameter_validations|length -%} 4 | {{parameter_validations-}} 5 | {% endif -%} 6 | updated_params.{{parameter_name}} = param.{{parameter_as_function}}; 7 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/cpp/set_runtime_parameter: -------------------------------------------------------------------------------- 1 | param = parameters_interface_->get_parameter(param_name); 2 | RCLCPP_DEBUG_STREAM(logger_, param.get_name() << ": " << param.get_type_name() << " = " << param.value_to_string()); 3 | {% if parameter_validations|length -%} 4 | {{parameter_validations-}} 5 | {% endif -%} 6 | entry.{{parameter_field}} = param.{{parameter_as_function}}; 7 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/cpp/set_stack_params: -------------------------------------------------------------------------------- 1 | output.{{parameter_name}} = params.{{parameter_name}}; 2 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/cpp/update_parameter: -------------------------------------------------------------------------------- 1 | if (param.get_name() == (prefix_ + "{{parameter_name}}")) { 2 | {%- filter indent(width=4) %} 3 | {% if parameter_validations|length -%} 4 | {{parameter_validations-}} 5 | {% endif -%} 6 | updated_params.{{parameter_name}} = param.{{parameter_as_function}}; 7 | RCLCPP_DEBUG_STREAM(logger_, param.get_name() << ": " << param.get_type_name() << " = " << param.value_to_string()); 8 | {% endfilter -%} 9 | } 10 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/cpp/update_runtime_parameter: -------------------------------------------------------------------------------- 1 | {% for mapped_param in mapped_params -%} 2 | for (const auto & value_{{loop.index}} : updated_params.{{mapped_param}}) { 3 | {% endfor -%} 4 | {%- filter indent(width=4) -%} 5 | std::string value = fmt::format("{%- for mapped_param in mapped_params -%}{% if loop.index == 1 %}{}{% else %}.{}{% endif -%} {%- endfor -%}", 6 | {%- for mapped_param in mapped_params -%}{% if loop.index == 1 %} value_{{loop.index}}{% else %}, value_{{loop.index}}{% endif -%} {%- endfor %}); 7 | {% if struct_name != "" %} 8 | auto param_name = fmt::format("{}{}.{}.{}", prefix_, "{{struct_name}}", value, "{{parameter_field}}"); 9 | {% else %} 10 | auto param_name = fmt::format("{}{}.{}", prefix_, value, "{{parameter_field}}"); 11 | {% endif -%} 12 | if (param.get_name() == param_name) { 13 | {%- filter indent(width=4) %} 14 | {% if parameter_validations|length -%} 15 | {{parameter_validations-}} 16 | {% endif -%} 17 | {% if struct_name != "" %} 18 | updated_params.{{struct_name}}{% for map in parameter_map%}.{{map}}[value_{{loop.index}}]{% endfor %}.{{parameter_field}} = param.{{parameter_as_function}}; 19 | {% else %} 20 | updated_params{% for map in parameter_map%}.{{map}}[value_{{loop.index}}]{% endfor %}.{{parameter_field}} = param.{{parameter_as_function}}; 21 | {% endif -%} 22 | RCLCPP_DEBUG_STREAM(logger_, param.get_name() << ": " << param.get_type_name() << " = " << param.value_to_string()); 23 | {% endfilter -%} 24 | } 25 | {% endfilter -%} 26 | {%- for mapped_param in mapped_params -%} 27 | } 28 | {% endfor -%} 29 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/markdown/default_config: -------------------------------------------------------------------------------- 1 | Default Config 2 | ```yaml 3 | {{namespace}}: 4 | ros__parameters: 5 | {%- filter indent(width=4) %} 6 | {{default_param_values}} 7 | {% endfilter -%} 8 | ``` 9 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/markdown/documentation: -------------------------------------------------------------------------------- 1 | # {{title}} Parameters 2 | 3 | {{default_config}} 4 | 5 | {{parameter_details}} 6 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/markdown/parameter_detail: -------------------------------------------------------------------------------- 1 | ## {{name}} 2 | {% if description|length %} 3 | {{description}} 4 | {% endif %} 5 | * Type: `{{type}}` 6 | {%- if default_value|length %} 7 | * Default Value: {{default_value}}{% endif %}{% if read_only %} 8 | * Read only: {{read_only}}{% endif %}{%- if constraints|length %} 9 | 10 | *Constraints:* 11 | {{constraints}} 12 | 13 | *Additional Constraints:* 14 | {{additional_constraints}} 15 | 16 | {% else %} 17 | {% endif %} 18 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/declare_parameter: -------------------------------------------------------------------------------- 1 | if not self.node_.has_parameter(self.prefix_ + "{{parameter_name}}"): 2 | {%- filter indent(width=4) %} 3 | descriptor = ParameterDescriptor(description="{{parameter_description|valid_string_python}}", read_only = {{parameter_read_only}}) 4 | {%- if parameter_additional_constraints|length %} 5 | descriptor.additional_constraints = "{{parameter_additional_constraints|valid_string_python}}" 6 | {% endif -%} 7 | {%- for validation in parameter_validations if ("bounds" in validation.function_name or "lt" in validation.function_name or "gt" in validation.function_name) %} 8 | {%- if "DOUBLE" in parameter_type %} 9 | {%- if validation.arguments|length == 2 %} 10 | descriptor.floating_point_range.append(FloatingPointRange()) 11 | descriptor.floating_point_range[-1].from_value = {{validation.arguments[0]}} 12 | descriptor.floating_point_range[-1].to_value = {{validation.arguments[1]}} 13 | {%- elif ("lower" in validation.function_name or "gt" == validation.function_base_name or "gt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} 14 | descriptor.floating_point_range.append(FloatingPointRange()) 15 | descriptor.floating_point_range[-1].from_value = {{validation.arguments[0]}} 16 | descriptor.floating_point_range[-1].to_value = float('inf') 17 | {%- elif ("upper" in validation.function_name or "lt" == validation.function_base_name or "lt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} 18 | descriptor.floating_point_range.append(FloatingPointRange()) 19 | descriptor.floating_point_range[-1].from_value = -float('inf') 20 | descriptor.floating_point_range[-1].to_value = {{validation.arguments[0]}} 21 | {%- endif %} 22 | {%- elif "INTEGER" in parameter_type %} 23 | {%- if validation.arguments|length == 2 %} 24 | descriptor.integer_range.append(IntegerRange()) 25 | descriptor.integer_range[-1].from_value = {{validation.arguments[0]}} 26 | descriptor.integer_range[-1].to_value = {{validation.arguments[1]}} 27 | {%- elif ("lower" in validation.function_name or "gt" == validation.function_base_name or "gt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} 28 | descriptor.integer_range.append(IntegerRange()) 29 | descriptor.integer_range[-1].from_value = {{validation.arguments[0]}} 30 | descriptor.integer_range[-1].to_value = 2**31-1 31 | {%- elif ("upper" in validation.function_name or "lt" == validation.function_base_name or "lt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} 32 | descriptor.integer_range.append(IntegerRange()) 33 | descriptor.integer_range[-1].from_value = -2**31-1 34 | descriptor.integer_range[-1].to_value = {{validation.arguments[0]}} 35 | {%- endif %} 36 | {%- endif %} 37 | {%- endfor %} 38 | {%- if not parameter_value|length %} 39 | parameter = rclpy.Parameter.Type.{{parameter_type}} 40 | {% endif -%} 41 | {%- if parameter_value|length %} 42 | parameter = updated_params.{{parameter_value}} 43 | {% endif -%} 44 | self.node_.declare_parameter(self.prefix_ + "{{parameter_name}}", parameter, descriptor) 45 | {% endfilter -%} 46 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/declare_runtime_parameter: -------------------------------------------------------------------------------- 1 | {% for mapped_param in mapped_params -%} 2 | {%- filter indent(width=4*loop.index0) %} 3 | for value_{{loop.index}} in updated_params.{{mapped_param}}: 4 | {%- endfilter -%} 5 | {% endfor -%} 6 | {%- filter indent(width=4*(mapped_params|length)) %} 7 | {% if struct_name != "" %} 8 | {{param_struct_instance}}.{{struct_name}}{% for map in parameter_map%}.add_entry(value_{{loop.index}}){% endfor %} 9 | entry = {{param_struct_instance}}.{{struct_name}}{% for map in parameter_map%}.get_entry(value_{{loop.index}}){% endfor %} 10 | param_name = f"{self.prefix_}{{struct_name}}.{% for map in parameter_map%}{value_{{loop.index}}}.{% endfor %}{{parameter_field}}" 11 | {% else %} 12 | {{param_struct_instance}}{% for map in parameter_map%}.add_entry(value_{{loop.index}}){% endfor %} 13 | entry = {{param_struct_instance}}{% for map in parameter_map%}.get_entry(value_{{loop.index}}){% endfor %} 14 | param_name = f"{self.prefix_}{% for map in parameter_map%}{value_{{loop.index}}}.{% endfor %}{{parameter_field}}" 15 | {% endif -%} 16 | if not self.node_.has_parameter(self.prefix_ + param_name): 17 | {%- filter indent(width=4) %} 18 | descriptor = ParameterDescriptor(description="{{parameter_description|valid_string_python}}", read_only = {{parameter_read_only}}) 19 | {%- if parameter_additional_constraints|length %} 20 | descriptor.additional_constraints = "{{parameter_additional_constraints|valid_string_python}}" 21 | {% endif -%} 22 | {%- for validation in parameter_validations if ("bounds" in validation.function_name or "lt" in validation.function_name or "gt" in validation.function_name) %} 23 | {%- if "DOUBLE" in parameter_type %} 24 | {%- if validation.arguments|length == 2 %} 25 | descriptor.floating_point_range.append(FloatingPointRange()) 26 | descriptor.floating_point_range[-1].from_value = {{validation.arguments[0]}} 27 | descriptor.floating_point_range[-1].to_value = {{validation.arguments[1]}} 28 | {%- elif ("lower" in validation.function_name or "gt" == validation.function_base_name or "gt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} 29 | descriptor.floating_point_range.append(FloatingPointRange()) 30 | descriptor.floating_point_range[-1].from_value = {{validation.arguments[0]}} 31 | descriptor.floating_point_range[-1].to_value = float('inf') 32 | {%- elif ("upper" in validation.function_name or "lt" == validation.function_base_name or "lt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} 33 | descriptor.floating_point_range.append(FloatingPointRange()) 34 | descriptor.floating_point_range[-1].from_value = -float('inf') 35 | descriptor.floating_point_range[-1].to_value = {{validation.arguments[0]}} 36 | {%- endif %} 37 | {%- elif "INTEGER" in parameter_type %} 38 | {%- if validation.arguments|length == 2 %} 39 | descriptor.integer_range.append(IntegerRange()) 40 | descriptor.integer_range[-1].from_value = {{validation.arguments[0]}} 41 | descriptor.integer_range[-1].to_value = {{validation.arguments[1]}} 42 | {%- elif ("lower" in validation.function_name or "gt" == validation.function_base_name or "gt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} 43 | descriptor.integer_range.append(IntegerRange()) 44 | descriptor.integer_range[-1].from_value = {{validation.arguments[0]}} 45 | descriptor.integer_range[-1].to_value = 2**31-1 46 | {%- elif ("upper" in validation.function_name or "lt" == validation.function_base_name or "lt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} 47 | descriptor.integer_range.append(IntegerRange()) 48 | descriptor.integer_range[-1].from_value = -2**31-1 49 | descriptor.integer_range[-1].to_value = {{validation.arguments[0]}} 50 | {%- endif %} 51 | {%- endif %} 52 | {%- endfor %} 53 | {%- if not default_value|length %} 54 | parameter = rclpy.Parameter.Type.{{parameter_type}} 55 | {% endif -%} 56 | {%- if default_value|length %} 57 | parameter = entry.{{parameter_field}} 58 | {% endif -%} 59 | self.node_.declare_parameter(param_name, parameter, descriptor) 60 | {% endfilter -%} 61 | {{set_runtime_parameter-}} 62 | {% endfilter -%} 63 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/declare_struct: -------------------------------------------------------------------------------- 1 | class __{{struct_name}}: 2 | {%- filter indent(width=4) %} 3 | {% if struct_fields|length -%} 4 | {{struct_fields-}} 5 | {% endif -%} 6 | {%if sub_structs|length -%} 7 | {{sub_structs-}} 8 | {% endif -%} 9 | {% endfilter -%} 10 | {% if struct_instance|length -%} 11 | {{struct_instance}} = __{{struct_name}}() 12 | {% endif -%} 13 | {% if not struct_instance|length -%} 14 | __map_type = __{{struct_name}} 15 | def add_entry(self, name): 16 | if not hasattr(self, name): 17 | setattr(self, name, self.__map_type()) 18 | return getattr(self, name) 19 | def get_entry(self, name): 20 | return getattr(self, name) 21 | {% endif -%} 22 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/declare_variable: -------------------------------------------------------------------------------- 1 | {% if value|length -%} 2 | {{name}} = {{value}} 3 | {% else -%} 4 | {{name}} = None 5 | {% endif -%} 6 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/parameter_library_header: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | {{comments}} 4 | 5 | from rcl_interfaces.msg import ParameterDescriptor 6 | from rcl_interfaces.msg import SetParametersResult 7 | from rcl_interfaces.msg import FloatingPointRange, IntegerRange 8 | from rclpy.clock import Clock 9 | from rclpy.exceptions import InvalidParameterValueException 10 | from rclpy.time import Time 11 | import copy 12 | import rclpy 13 | import rclpy.parameter 14 | from generate_parameter_library_py.python_validators import ParameterValidators 15 | 16 | {% if user_validation_file|length -%} 17 | import {{user_validation_file}} as custom_validators 18 | {% endif %} 19 | 20 | class {{namespace}}: 21 | {%- filter indent(width=4) %} 22 | 23 | class Params: 24 | {%- filter indent(width=4) %} 25 | # for detecting if the parameter struct has been updated 26 | stamp_ = Time() 27 | 28 | {{field_content-}} 29 | {{sub_struct_content-}} 30 | {% endfilter -%} 31 | {%- endfilter %} 32 | 33 | 34 | class ParamListener: 35 | def __init__(self, node, prefix=""): 36 | self.prefix_ = prefix 37 | self.params_ = {{namespace}}.Params() 38 | self.node_ = node 39 | self.logger_ = rclpy.logging.get_logger("{{namespace}}." + prefix) 40 | 41 | self.declare_params() 42 | 43 | self.node_.add_on_set_parameters_callback(self.update) 44 | self.clock_ = Clock() 45 | 46 | def get_params(self): 47 | tmp = self.params_.stamp_ 48 | self.params_.stamp_ = None 49 | paramCopy = copy.deepcopy(self.params_) 50 | paramCopy.stamp_ = tmp 51 | self.params_.stamp_ = tmp 52 | return paramCopy 53 | 54 | def is_old(self, other_param): 55 | return self.params_.stamp_ != other_param.stamp_ 56 | 57 | def unpack_parameter_dict(self, namespace: str, parameter_dict: dict): 58 | """ 59 | Flatten a parameter dictionary recursively. 60 | 61 | :param namespace: The namespace to prepend to the parameter names. 62 | :param parameter_dict: A dictionary of parameters keyed by the parameter names 63 | :return: A list of rclpy Parameter objects 64 | """ 65 | parameters = [] 66 | for param_name, param_value in parameter_dict.items(): 67 | full_param_name = namespace + param_name 68 | # Unroll nested parameters 69 | if isinstance(param_value, dict): 70 | nested_params = self.unpack_parameter_dict( 71 | namespace=full_param_name + rclpy.parameter.PARAMETER_SEPARATOR_STRING, 72 | parameter_dict=param_value) 73 | parameters.extend(nested_params) 74 | else: 75 | parameters.append(rclpy.parameter.Parameter(full_param_name, value=param_value)) 76 | return parameters 77 | 78 | def set_params_from_dict(self, param_dict): 79 | params_to_set = self.unpack_parameter_dict('', param_dict) 80 | self.update(params_to_set) 81 | 82 | def refresh_dynamic_parameters(self): 83 | updated_params = self.get_params() 84 | # TODO remove any destroyed dynamic parameters 85 | {%- filter indent(width=6) %} 86 | {{remove_dynamic_parameters}} 87 | {%- endfilter %} 88 | # declare any new dynamic parameters 89 | {%- filter indent(width=12) %} 90 | {{update_declare_dynamic_parameters}} 91 | {%- endfilter %} 92 | 93 | def update(self, parameters): 94 | updated_params = self.get_params() 95 | 96 | for param in parameters: 97 | {%- filter indent(width=16) %} 98 | {{update_params_set}} 99 | {%- endfilter %} 100 | {% if update_dynamic_parameters|length %} 101 | {%- filter indent(width=12) %} 102 | # update dynamic parameters 103 | for param in parameters: 104 | {%- filter indent(width=4) %} 105 | {{update_dynamic_parameters}} 106 | {%- endfilter %} 107 | {%- endfilter %} 108 | {%- endif %} 109 | 110 | updated_params.stamp_ = self.clock_.now() 111 | self.update_internal_params(updated_params) 112 | return SetParametersResult(successful=True) 113 | 114 | def update_internal_params(self, updated_params): 115 | self.params_ = updated_params 116 | 117 | def declare_params(self): 118 | updated_params = self.get_params() 119 | # declare all parameters and give default values to non-required ones 120 | {%- filter indent(width=12) %} 121 | {{declare_params}} 122 | {%- endfilter %} 123 | # TODO: need validation 124 | # get parameters and fill struct fields 125 | 126 | {%- filter indent(width=12) %} 127 | {{declare_params_set}} 128 | {%- endfilter %} 129 | {% if declare_set_dynamic_params|length %} 130 | {% filter indent(width=12) %} 131 | # declare and set all dynamic parameters 132 | {{declare_set_dynamic_params}} 133 | {%- endfilter %} 134 | {%- endif %} 135 | 136 | self.update_internal_params(updated_params) 137 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/parameter_validation: -------------------------------------------------------------------------------- 1 | validation_result = {{validation_function}} 2 | if validation_result: 3 | {%- filter indent(width=4) %} 4 | {{invalid_effect}} 5 | {% endfilter -%} 6 | 7 | {% if valid_effect|length -%} 8 | {{valid_effect-}} 9 | {% endif -%} 10 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/remove_runtime_parameter: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PickNikRobotics/generate_parameter_library/7735ac9c4ec743add344ad3d41b7cbb1824e415d/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/remove_runtime_parameter -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/set_parameter: -------------------------------------------------------------------------------- 1 | param = self.node_.get_parameter(self.prefix_ + "{{parameter_name}}") 2 | self.logger_.debug(param.name + ": " + param.type_.name + " = " + str(param.value)) 3 | {% if parameter_validations|length -%} 4 | {{parameter_validations-}} 5 | {% endif -%} 6 | updated_params.{{parameter_name}} = param.{{parameter_as_function}} 7 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/set_runtime_parameter: -------------------------------------------------------------------------------- 1 | param = self.node_.get_parameter(param_name) 2 | self.logger_.debug(param.name + ": " + param.type_.name + " = " + str(param.value)) 3 | {% if parameter_validations|length -%} 4 | {{parameter_validations-}} 5 | {% endif -%} 6 | entry.{{parameter_field}} = param.{{parameter_as_function}} 7 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/set_stack_params: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PickNikRobotics/generate_parameter_library/7735ac9c4ec743add344ad3d41b7cbb1824e415d/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/set_stack_params -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/update_parameter: -------------------------------------------------------------------------------- 1 | if param.name == self.prefix_ + "{{parameter_name}}": 2 | {%- filter indent(width=4) %} 3 | {% if parameter_validations|length -%} 4 | {{parameter_validations-}} 5 | {% endif -%} 6 | updated_params.{{parameter_name}} = param.{{parameter_as_function}} 7 | self.logger_.debug(param.name + ": " + param.type_.name + " = " + str(param.value)) 8 | {% endfilter -%} 9 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/update_runtime_parameter: -------------------------------------------------------------------------------- 1 | {% for mapped_param in mapped_params -%} 2 | {%- filter indent(width=4*loop.index) %} 3 | for value_{{loop.index}} in updated_params.{{mapped_param}}: 4 | {%- endfilter -%} 5 | {% endfor -%} 6 | {%- filter indent(width=4*(1+mapped_params|length)) %} 7 | {% if struct_name != "" %} 8 | param_name = f"{self.prefix_}{{struct_name}}{% for map in parameter_map%}.{value_{{loop.index}}}.{% endfor %}{{parameter_field}}" 9 | {% else %} 10 | param_name = f"{self.prefix_}{% for map in parameter_map%}.{value_{{loop.index}}}.{% endfor %}{{parameter_field}}" 11 | {% endif -%} 12 | if param.name == param_name: 13 | {%- filter indent(width=4) %} 14 | {% if parameter_validations|length -%} 15 | {{parameter_validations-}} 16 | {% endif -%} 17 | {% if struct_name != "" %} 18 | updated_params.{{struct_name}}{% for map in parameter_map%}.get_entry(value_{{loop.index}}){% endfor %}.{{parameter_field}} = param.{{parameter_as_function}} 19 | {% else %} 20 | updated_params{% for map in parameter_map%}.get_entry(value_{{loop.index}}){% endfor %}.{{parameter_field}} = param.{{parameter_as_function}} 21 | {% endif -%} 22 | self.logger_.debug(param.name + ": " + param.type_.name + " = " + str(param.value)) 23 | {% endfilter -%} 24 | {% endfilter -%} 25 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/rst/default_config: -------------------------------------------------------------------------------- 1 | .. code:: yaml 2 | {%- filter indent(width=2) %} 3 | 4 | {{namespace}}: 5 | ros__parameters: 6 | {%- filter indent(width=4) %} 7 | {{default_param_values}} 8 | {% endfilter -%} 9 | {% endfilter -%} 10 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/rst/documentation: -------------------------------------------------------------------------------- 1 | {{title}} Parameters 2 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 3 | 4 | {{default_config}} 5 | 6 | {{parameter_details}} 7 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/jinja_templates/rst/parameter_detail: -------------------------------------------------------------------------------- 1 | {{name}} ({{type}}){%- filter indent(width=2) %}{% if description|length %} 2 | {{description}} 3 | {% endif %} 4 | {%- if read_only %} 5 | Read only: {{read_only}} 6 | {% endif %} 7 | {%- if default_value|length %} 8 | Default: {{default_value}} 9 | {% endif %} 10 | {%- if constraints|length %} 11 | Constraints: 12 | 13 | {%- filter indent(width=2) %} 14 | 15 | {{constraints}} 16 | {% endfilter -%} 17 | 18 | {% endif %} 19 | {% endfilter -%} 20 | 21 | {%- if additional_constraints|length %} 22 | Additional Constraints: 23 | {{additional_constraints}} 24 | {% endif %} 25 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/python_conversions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from typing import List, Union 3 | from jinja2 import Template 4 | from typeguard import typechecked 5 | import os 6 | import yaml 7 | from yaml.parser import ParserError 8 | from yaml.scanner import ScannerError 9 | 10 | 11 | class PythonConversions: 12 | def __init__(self): 13 | self.defined_type_to_lang_type = { 14 | 'none': lambda defined_type, templates: None, 15 | 'bool': lambda defined_type, templates: 'bool', 16 | 'double': lambda defined_type, templates: 'float', 17 | 'int': lambda defined_type, templates: 'int', 18 | 'string': lambda defined_type, templates: 'str', 19 | 'bool_array': lambda defined_type, templates: '[bool]', 20 | 'double_array': lambda defined_type, templates: '[float]', 21 | 'int_array': lambda defined_type, templates: '[int]', 22 | 'string_array': lambda defined_type, templates: '[str]', 23 | 'double_array_fixed': lambda defined_type, templates: '[float]', 24 | 'int_array_fixed': lambda defined_type, templates: '[int]', 25 | 'string_array_fixed': lambda defined_type, templates: '[str]', 26 | 'string_fixed': lambda defined_type, templates: 'str', 27 | } 28 | self.yaml_type_to_as_function = { 29 | 'none': None, 30 | 'string_array': 'value', 31 | 'double_array': 'value', 32 | 'int_array': 'value', 33 | 'bool_array': 'value', 34 | 'string': 'value', 35 | 'double': 'value', 36 | 'int': 'value', 37 | 'bool': 'value', 38 | 'bool_array_fixed': 'value', 39 | 'double_array_fixed': 'value', 40 | 'int_array_fixed': 'value', 41 | 'string_array_fixed': 'value', 42 | 'string_fixed': 'value', 43 | } 44 | self.lang_str_value_func = { 45 | 'none': self.no_code, 46 | 'bool': self.bool_to_str, 47 | 'double': self.float_to_str, 48 | 'int': self.int_to_str, 49 | 'string': self.str_to_str, 50 | 'bool_array': self.bool_array_to_str, 51 | 'double_array': self.float_array_to_str, 52 | 'int_array': self.int_array_to_str, 53 | 'string_array': self.str_array_to_str, 54 | 'bool_array_fixed': self.bool_array_fixed_to_str, 55 | 'double_array_fixed': self.float_array_fixed_to_str, 56 | 'int_array_fixed': self.int_array_fixed_to_str, 57 | 'string_array_fixed': self.str_array_fixed_to_str, 58 | 'string_fixed': self.str_fixed_to_str, 59 | } 60 | self.python_val_to_str_func = { 61 | "": self.bool_to_str, 62 | "": self.float_to_str, 63 | "": self.int_to_str, 64 | "": self.str_to_str, 65 | } 66 | self.python_val_to_yaml_type = { 67 | "": 'bool', 68 | "": 'double', 69 | "": 'int', 70 | "": 'str', 71 | } 72 | self.python_list_to_yaml_type = { 73 | "": 'bool_array', 74 | "": 'double_array', 75 | "": 'integer_array', 76 | "": 'string_array', 77 | } 78 | 79 | self.open_bracket = '[' 80 | self.close_bracket = ']' 81 | 82 | @typechecked 83 | def get_func_signature(self, function_name: str, base_type: str) -> str: 84 | if function_name.__contains__('::'): 85 | # user defined function 86 | function_name = function_name.replace('::', '.') 87 | else: 88 | function_name = 'ParameterValidators.' + function_name 89 | if function_name.__contains__('<>'): 90 | function_name = function_name.replace('<>', '') 91 | return function_name 92 | 93 | @typechecked 94 | def initialization_fail_validation(self, param_name: str) -> str: 95 | return f"raise InvalidParameterValueException('{param_name}',param.value, 'Invalid value set during initialization for parameter {param_name}: ' + validation_result)" 96 | 97 | @typechecked 98 | def initialization_pass_validation(self, param_name: str) -> str: 99 | return '' 100 | 101 | @typechecked 102 | def update_parameter_fail_validation(self) -> str: 103 | return 'return SetParametersResult(successful=False, reason=validation_result)' 104 | 105 | @typechecked 106 | def update_parameter_pass_validation(self) -> str: 107 | return '' 108 | 109 | @typechecked 110 | def no_code(self, s: Union[None, str]): 111 | return '' 112 | 113 | # value to c++ string conversion functions 114 | @typechecked 115 | def bool_to_str(self, cond: Union[None, bool]): 116 | if cond is None: 117 | return '' 118 | return 'True' if cond else 'False' 119 | 120 | @typechecked 121 | def float_to_str(self, num: Union[None, float]): 122 | if num is None: 123 | return '' 124 | str_num = str(num) 125 | if str_num == 'nan': 126 | str_num = "float('nan')" 127 | elif str_num == 'inf': 128 | str_num = "float('inf')" 129 | elif str_num == '-inf': 130 | str_num = "-float('inf')" 131 | else: 132 | if len(str_num.split('.')) == 1 and not str_num.__contains__('e'): 133 | str_num += '.0' 134 | 135 | return str_num 136 | 137 | @typechecked 138 | def int_to_str(self, num: Union[None, int]): 139 | if num is None: 140 | return '' 141 | return str(num) 142 | 143 | @typechecked 144 | def str_to_str(self, s: Union[None, str]): 145 | if s is None: 146 | return '' 147 | return f'"{s}"' 148 | 149 | @typechecked 150 | def bool_array_to_str(self, values: Union[None, list]): 151 | if values is None: 152 | return '' 153 | return '[' + ', '.join(self.bool_to_str(x) for x in values) + ']' 154 | 155 | @typechecked 156 | def float_array_to_str(self, values: Union[None, list]): 157 | if values is None: 158 | return '' 159 | return '[' + ', '.join(self.float_to_str(x) for x in values) + ']' 160 | 161 | @typechecked 162 | def int_array_to_str(self, values: Union[None, list]): 163 | if values is None: 164 | return '' 165 | return '[' + ', '.join(self.int_to_str(x) for x in values) + ']' 166 | 167 | @typechecked 168 | def str_array_to_str(self, s: Union[None, list]): 169 | if s is None: 170 | return '' 171 | return '[' + ', '.join(self.str_to_str(x) for x in s) + ']' 172 | 173 | @typechecked 174 | def str_array_fixed_to_str(self, s: Union[None, list]): 175 | raise compile_error('not implemented') 176 | 177 | @typechecked 178 | def str_fixed_to_str(self, s: Union[None, str]): 179 | if s is None: 180 | return '' 181 | return '%s' % self.str_to_str(s) 182 | 183 | @typechecked 184 | def float_array_fixed_to_str(self, values: Union[None, list]): 185 | if values is None: 186 | return '' 187 | return '[' + ', '.join(self.float_to_str(x) for x in values) + ']' 188 | 189 | @typechecked 190 | def int_array_fixed_to_str(self, values: Union[None, list]): 191 | if values is None: 192 | return '' 193 | return '[' + ', '.join(self.int_to_str(x) for x in values) + ']' 194 | 195 | @typechecked 196 | def bool_array_fixed_to_str(self, values: Union[None, list]): 197 | if values is None: 198 | return '' 199 | return '[' + ', '.join(self.bool_to_str(x) for x in values) + ']' 200 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/python_validators.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright 2023 PickNik Inc. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are met: 8 | # 9 | # * Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # 12 | # * Redistributions in binary form must reproduce the above copyright 13 | # notice, this list of conditions and the following disclaimer in the 14 | # documentation and/or other materials provided with the distribution. 15 | # 16 | # * Neither the name of the PickNik Inc. nor the names of its 17 | # contributors may be used to endorse or promote products derived from 18 | # this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | # POSSIBILITY OF SUCH DAMAGE. 31 | 32 | 33 | # TODO: ParameterValidators should be moved somewhere else, like rsl for C++ 34 | class ParameterValidators: 35 | # Value validators 36 | 37 | def lt(param, value): 38 | if not param.value < value: 39 | tmp = 'less than' 40 | return f"Parameter '{param.name}' with the value {param.value} must be {tmp} {value}" 41 | return '' 42 | 43 | def gt(param, value): 44 | if not param.value > value: 45 | tmp = 'greater than' 46 | return f"Parameter '{param.name}' with the value {param.value} must be {tmp} {value}" 47 | return '' 48 | 49 | def lt_eq(param, value): 50 | if not param.value <= value: 51 | tmp = 'below upper bound of' 52 | return f"Parameter '{param.name}' with the value {param.value} must be {tmp} {value}" 53 | return '' 54 | 55 | def gt_eq(param, value): 56 | if not param.value >= value: 57 | tmp = 'above lower bound of' 58 | return f"Parameter '{param.name}' with the value {param.value} must be {tmp} {value}" 59 | return '' 60 | 61 | def not_empty(param): 62 | if len(param.value) == 0: 63 | tmp = 'above lower bound of' 64 | return f"Parameter '{param.name}' cannot be empty" 65 | return '' 66 | 67 | def one_of(param, values): 68 | if not param.value in values: 69 | return f"Parameter '{param.name}' with the value '{param.value}' is not in the set {str(values)}" 70 | return '' 71 | 72 | # Array validators 73 | def unique(param): 74 | if len(set(param.value)) != len(param.value): 75 | return f"Parameter '{param.name}' must only contain unique values" 76 | return '' 77 | 78 | def subset_of(param, values): 79 | for val in param.value: 80 | if not val in values: 81 | return f"Entry '{val}' in parameter '{param.name}' is not in the set {str(values)}" 82 | return '' 83 | 84 | def fixed_size(param, length): 85 | if not len(param.value) == length: 86 | tmp = 'equal to' 87 | return f"Length of parameter '{param.name}' is '{len(param.value)}' but must be {tmp} {length}" 88 | return '' 89 | 90 | def size_gt(param, length): 91 | if not len(param.value) > length: 92 | tmp = 'greater than' 93 | return f"Length of parameter '{param.name}' is '{len(param.value)}' but must be {tmp} {length}" 94 | return '' 95 | 96 | def size_lt(param, length): 97 | if not len(param.value) < length: 98 | tmp = 'less than' 99 | return f"Length of parameter '{param.name}' is '{len(param.value)}' but must be {tmp} {length}" 100 | return '' 101 | 102 | def element_bounds(param, lower, upper): 103 | for val in param.value: 104 | if val > upper or val < lower: 105 | return f"Value {param.value} in parameter '{param.name}' must be within bounds [{lower}, {upper}]" 106 | return '' 107 | 108 | def lower_element_bounds(param, lower): 109 | for val in param.value: 110 | if val < lower: 111 | return f"Value {val} in parameter '{param.name}' must be above lower bound of {lower}" 112 | return '' 113 | 114 | def upper_element_bounds(param, upper): 115 | for val in param.value: 116 | if val > upper: 117 | return f"Value {val} in parameter '{param.name}' must be above lower bound of {upper}" 118 | return '' 119 | 120 | def bounds(param, lower, upper): 121 | if param.value > upper or param.value < lower: 122 | return f"Value {param.value} in parameter '{param.name}' must be within bounds [{lower}, {upper}]" 123 | return '' 124 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/setup_helper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2023 PickNik Inc. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright 8 | # notice, this list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright 11 | # notice, this list of conditions and the following disclaimer in the 12 | # documentation and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of the PickNik Inc. nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 22 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | # POSSIBILITY OF SUCH DAMAGE. 29 | 30 | import sys 31 | import os 32 | from generate_parameter_library_py.generate_python_module import run 33 | 34 | 35 | def generate_parameter_module( 36 | module_name, yaml_file, validation_module='', install_base=None, merge_install=False 37 | ): 38 | # TODO there must be a better way to do this. I need to find the build directory so I can place the python 39 | # module there 40 | build_dir = None 41 | install_dir = None 42 | for i, arg in enumerate(sys.argv): 43 | # Look for the `--build-directory` option in the command line arguments 44 | if arg == '--build-directory' or arg == '--build-base': 45 | build_arg = sys.argv[i + 1] 46 | 47 | path_split = os.path.split(build_arg) 48 | path_split = os.path.split(path_split[0]) 49 | pkg_name = path_split[1] 50 | path_split = os.path.split(path_split[0]) 51 | colcon_ws = path_split[0] 52 | 53 | tmp = sys.version.split()[0] 54 | tmp = tmp.split('.') 55 | py_version = f'python{tmp[0]}.{tmp[1]}' 56 | 57 | if not install_base: 58 | install_base = os.path.join(colcon_ws, 'install') 59 | 60 | install_base = ( 61 | install_base if merge_install else os.path.join(install_base, pkg_name) 62 | ) 63 | install_dir = os.path.join( 64 | install_base, 65 | 'lib', 66 | py_version, 67 | 'site-packages', 68 | pkg_name, 69 | ) 70 | build_dir = os.path.join(colcon_ws, 'build', pkg_name, pkg_name) 71 | break 72 | 73 | if build_dir: 74 | run(os.path.join(build_dir, module_name + '.py'), yaml_file, validation_module) 75 | if install_dir: 76 | run( 77 | os.path.join(install_dir, module_name + '.py'), yaml_file, validation_module 78 | ) 79 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/string_filters_cpp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | def valid_string_cpp(description): 3 | """ 4 | Filter a string to make it a valid C++ string literal. 5 | 6 | Args: 7 | description (str): The input string to be filtered. 8 | 9 | Returns: 10 | str: The filtered string that is a valid C++ string. 11 | """ 12 | if description: 13 | # remove possible markdown/rst syntax, but add proper indent for cpp-header files. 14 | filtered_description = ( 15 | description.replace('\\', '\\\\').replace('`', '').replace('\n', '\\n ') 16 | ) 17 | return f'"{filtered_description}"' 18 | else: 19 | return '""' 20 | 21 | 22 | def valid_string_python(description): 23 | """ 24 | Filter a string to make it a valid Python string literal. 25 | 26 | Args: 27 | description (str): The input string to be filtered. 28 | 29 | Returns: 30 | str: The filtered string that is a valid Python string. 31 | """ 32 | if description: 33 | return description.replace('\n', '\\n ') 34 | else: 35 | return '' 36 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/test/YAML_parse_error_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2022 PickNik, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import pytest 17 | from unittest.mock import patch 18 | import sys 19 | import os 20 | 21 | from ament_index_python.packages import get_package_share_path 22 | from generate_parameter_library_py.generate_cpp_header import run as run_cpp 23 | from generate_parameter_library_py.generate_python_module import run as run_python 24 | from generate_parameter_library_py.generate_markdown import run as run_md 25 | from generate_parameter_library_py.parse_yaml import YAMLSyntaxError 26 | from generate_parameter_library_py.generate_cpp_header import parse_args 27 | 28 | 29 | def set_up(yaml_test_file): 30 | full_file_path = os.path.join( 31 | get_package_share_path('generate_parameter_library_py'), 'test', yaml_test_file 32 | ) 33 | testargs = [sys.argv[0], '/tmp/' + yaml_test_file + '.h', full_file_path] 34 | 35 | with patch.object(sys, 'argv', testargs): 36 | args = parse_args() 37 | output_file = args.output_cpp_header_file 38 | yaml_file = args.input_yaml_file 39 | validate_header = args.validate_header 40 | run_cpp(output_file, yaml_file, validate_header) 41 | 42 | testargs = [sys.argv[0], '/tmp/' + yaml_test_file + '.py', full_file_path] 43 | 44 | with patch.object(sys, 'argv', testargs): 45 | args = parse_args() 46 | output_file = args.output_cpp_header_file 47 | yaml_file = args.input_yaml_file 48 | validate_header = args.validate_header 49 | run_python(output_file, yaml_file, validate_header) 50 | 51 | testargs = [sys.argv[0], '/tmp/' + yaml_test_file + '.md', full_file_path] 52 | 53 | with patch.object(sys, 'argv', testargs): 54 | args = parse_args() 55 | output_file = args.output_cpp_header_file 56 | yaml_file = args.input_yaml_file 57 | validate_header = args.validate_header 58 | run_md(yaml_file, output_file, 'markdown') 59 | 60 | testargs = [sys.argv[0], '/tmp/' + yaml_test_file + '.rst', full_file_path] 61 | 62 | with patch.object(sys, 'argv', testargs): 63 | args = parse_args() 64 | output_file = args.output_cpp_header_file 65 | yaml_file = args.input_yaml_file 66 | validate_header = args.validate_header 67 | run_md(yaml_file, output_file, 'rst') 68 | 69 | 70 | # class TestViewValidCodeGen(unittest.TestCase): 71 | @pytest.mark.parametrize( 72 | 'test_input,expected', 73 | [ 74 | (file_name, YAMLSyntaxError) 75 | for file_name in [ 76 | 'wrong_default_type.yaml', 77 | 'missing_type.yaml', 78 | 'invalid_syntax.yaml', 79 | 'invalid_parameter_type.yaml', 80 | ] 81 | ], 82 | ) 83 | def test_expected(test_input, expected): 84 | with pytest.raises(expected) as e: 85 | yaml_test_file = test_input 86 | set_up(yaml_test_file) 87 | print(e.value) 88 | 89 | 90 | def test_parse_valid_parameter_file(): 91 | try: 92 | yaml_test_file = 'valid_parameters.yaml' 93 | set_up(yaml_test_file) 94 | except Exception as e: 95 | assert False, f'failed to parse valid file, reason:{e}' 96 | 97 | 98 | def test_parse_valid_parameter_file_including_none_type(): 99 | try: 100 | yaml_test_file = 'valid_parameters_with_none_type.yaml' 101 | set_up(yaml_test_file) 102 | except Exception as e: 103 | assert False, f'failed to parse valid file, reason:{e}' 104 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/test/invalid_parameter_type.yaml: -------------------------------------------------------------------------------- 1 | namespace: 2 | param_name: 3 | type: string_non-type 4 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/test/invalid_syntax.yaml: -------------------------------------------------------------------------------- 1 | namespace: 2 | param_name: 3 | type: double 4 | description: "Test scientific notation" 5 | - 6 | - 7 | - 8 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/test/missing_type.yaml: -------------------------------------------------------------------------------- 1 | namespace: 2 | param_name: 3 | description: "Test scientific notation" 4 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/test/valid_parameters.yaml: -------------------------------------------------------------------------------- 1 | admittance_controller: 2 | scientific_notation_num: 3 | type: double 4 | default_value: 0.00000000001 5 | description: "Test scientific notation" 6 | interpolation_mode: 7 | type: string 8 | default_value: "spline" 9 | description: "specifies which algorithm to use for interpolation." 10 | validation: 11 | one_of<>: [ [ "spline", "linear" ] ] 12 | no_args_validator: null 13 | joints: 14 | type: string_array 15 | default_value: ["joint1", "joint2", "joint3"] 16 | description: "specifies which joints will be used by the controller" 17 | pid: 18 | rate: 19 | type: double 20 | default_value: 0.005 21 | description: "update loop period in seconds" 22 | 23 | __map_joints: 24 | p: 25 | type: double 26 | default_value: 1.0 27 | description: "proportional gain term" 28 | validation: 29 | gt_eq<>: [ 0 ] 30 | i: 31 | type: double 32 | description: "integral gain term" 33 | d: 34 | type: double 35 | default_value: 1.0 36 | description: "derivative gain term" 37 | fixed_string: 38 | type: string_fixed_25 39 | default_value: "string_value" 40 | description: "test code generation for fixed sized string" 41 | fixed_array: 42 | type: double_array_fixed_10 43 | default_value: [1.0, 2.3, 4.0 ,5.4, 3.3] 44 | description: "test code generation for fixed sized array" 45 | fixed_string_no_default: 46 | type: string_fixed_25 47 | description: "test code generation for fixed sized string with no default" 48 | command_interfaces: 49 | type: string_array 50 | description: "specifies which command interfaces to claim" 51 | additional_constraints: "cmd1 | cmd2 | cmd3" 52 | read_only: true 53 | 54 | state_interfaces: 55 | type: string_array 56 | description: "specifies which state interfaces to claim" 57 | read_only: true 58 | 59 | chainable_command_interfaces: 60 | type: string_array 61 | description: "specifies which chainable interfaces to claim" 62 | read_only: true 63 | 64 | kinematics: 65 | plugin_name: 66 | type: string 67 | description: "specifies which kinematics plugin to load" 68 | plugin_package: 69 | type: string 70 | description: "specifies the package to load the kinematics plugin from" 71 | base: 72 | type: string 73 | description: "specifies the base link of the robot description used by the kinematics plugin" 74 | tip: 75 | type: string 76 | description: "specifies the end effector link of the robot description used by the kinematics plugin" 77 | alpha: 78 | type: double 79 | default_value: 0.0005 80 | description: "specifies the damping coefficient for the Jacobian pseudo inverse" 81 | group_name: 82 | type: string 83 | description: "specifies the group name for planning with Moveit" 84 | 85 | ft_sensor: 86 | name: 87 | type: string 88 | description: "name of the force torque sensor in the robot description" 89 | frame: 90 | id: 91 | type: string 92 | description: "frame of the force torque sensor" 93 | external: 94 | type: bool 95 | default_value: false 96 | description: "specifies if the force torque sensor is contained in the kinematics chain from the base to the tip" 97 | filter_coefficient: 98 | type: double 99 | default_value: 0.005 100 | description: "specifies the coefficient for the sensor's exponential filter" 101 | 102 | control: 103 | frame: 104 | id: 105 | type: string 106 | description: "control frame used for admittance control" 107 | external: 108 | type: bool 109 | default_value: false 110 | description: "specifies if the control frame is contained in the kinematics chain from the base to the tip" 111 | 112 | fixed_world_frame: # Gravity points down (neg. Z) in this frame (Usually: world or base_link) 113 | frame: 114 | id: 115 | type: string 116 | description: "world frame, gravity points down (neg. Z) in this frame" 117 | external: 118 | type: bool 119 | default_value: false 120 | description: "specifies if the world frame is contained in the kinematics chain from the base to the tip" 121 | 122 | gravity_compensation: 123 | frame: 124 | id: 125 | type: string 126 | description: "frame which center of gravity (CoG) is defined in" 127 | external: 128 | type: bool 129 | default_value: false 130 | description: "specifies if the center of gravity frame is contained in the kinematics chain from the base to the tip" 131 | CoG: # specifies the center of gravity of the end effector 132 | pos: 133 | type: double_array 134 | description: "position of the center of gravity (CoG) in its frame" 135 | validation: 136 | fixed_size<>: 3 137 | force: 138 | type: double 139 | default_value: .NAN 140 | description: "weight of the end effector, e.g mass * 9.81" 141 | 142 | admittance: 143 | selected_axes: 144 | type: bool_array 145 | description: "specifies if the axes x, y, z, rx, ry, and rz are enabled" 146 | validation: 147 | fixed_size<>: 6 148 | 149 | # Having ".0" at the end is MUST, otherwise there is a loading error 150 | # F = M*a + D*v + S*(x - x_d) 151 | mass: 152 | type: double_array 153 | description: "specifies mass values for x, y, z, rx, ry, and rz used in the admittance calculation" 154 | validation: 155 | fixed_size<>: 6 156 | element_bounds<>: [ 0.0001, 1000000.0 ] 157 | 158 | damping_ratio: 159 | type: double_array 160 | description: "specifies damping ratio values for x, y, z, rx, ry, and rz used in the admittance calculation. 161 | The values are calculated as damping can be used instead: zeta = D / (2 * sqrt( M * S ))" 162 | validation: 163 | fixed_size<>: 6 164 | validate_double_array_custom_func: [ 20.3, 5.0 ] 165 | element_bounds<>: [ 0.1, 10.0 ] 166 | 167 | stiffness: 168 | type: double_array 169 | description: "specifies stiffness values for x, y, z, rx, ry, and rz used in the admittance calculation" 170 | validation: 171 | element_bounds: [ 0.0001, 100000.0 ] 172 | 173 | # general settings 174 | enable_parameter_update_without_reactivation: 175 | type: bool 176 | default_value: true 177 | description: "if enabled, read_only parameters will be dynamically updated in the control loop" 178 | use_feedforward_commanded_input: 179 | type: bool 180 | default_value: false 181 | description: "if enabled, the velocity commanded to the admittance controller is added to its calculated admittance velocity" 182 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/test/valid_parameters_with_none_type.yaml: -------------------------------------------------------------------------------- 1 | some_controller: 2 | some_parameter: 3 | type: string 4 | default_value: "default" 5 | description: "Test parameter that should generate code." 6 | 7 | some_external_parameter: 8 | type: none 9 | description: "Parameter that should not generate any code but must be accepted." 10 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/test/validation_only_parameters.yaml: -------------------------------------------------------------------------------- 1 | admittance_controller: 2 | stiffness: 3 | type: double_array 4 | description: "specifies stiffness values for x, y, z, rx, ry, and rz used in the admittance calculation" 5 | validation: 6 | element_bounds: [ 0.0001, 100000.0 ] 7 | dankness: 8 | type: int_array 9 | description: "specifies stiffness values for x, y, z, rx, ry, and rz used in the admittance calculation" 10 | validation: 11 | element_bounds: [ 1, 100 ] 12 | 13 | mass: 14 | type: double_array 15 | description: "specifies mass values for x, y, z, rx, ry, and rz used in the admittance calculation" 16 | validation: 17 | fixed_size<>: 6 18 | element_bounds<>: [ 0.0001, 1000000.0 ] 19 | 20 | one_number: 21 | type: int 22 | default_value: 14540 23 | read_only: true 24 | validation: 25 | bounds<>: [ 1024, 65535 ] 26 | 27 | p: 28 | type: double 29 | default_value: 1.0 30 | description: "proportional gain term" 31 | validation: 32 | gt_eq<>: [ 0 ] 33 | 34 | p2: 35 | type: double 36 | default_value: 1.0 37 | description: "proportional gain term updated" 38 | validation: 39 | gt_eq: [ 0.0001 ] 40 | 41 | under_ten: 42 | type: double 43 | default_value: 1.0 44 | description: "should be a number less than 10" 45 | validation: 46 | lt_eq<>: [ 10 ] 47 | lt_eq_fifteen: 48 | type: int 49 | default_value: 1 50 | description: "should be a number less than or equal to 15" 51 | validation: 52 | lt_eq<>: [ 15 ] 53 | gt_fifteen: 54 | type: int 55 | default_value: 16 56 | description: "should be a number greater than 15" 57 | validation: 58 | gt<>: [ 15 ] 59 | # This shouldn't populate any description range constraints 60 | fixed_array: 61 | type: double_array_fixed_10 62 | default_value: [1.0, 2.3, 4.0 ,5.4, 3.3] 63 | description: "test code generation for fixed sized array" 64 | 65 | # general settings 66 | enable_parameter_update_without_reactivation: 67 | type: bool 68 | default_value: true 69 | description: "if enabled, read_only parameters will be dynamically updated in the control loop" 70 | use_feedforward_commanded_input: 71 | type: bool 72 | default_value: false 73 | description: "if enabled, the velocity commanded to the admittance controller is added to its calculated admittance velocity" 74 | -------------------------------------------------------------------------------- /generate_parameter_library_py/generate_parameter_library_py/test/wrong_default_type.yaml: -------------------------------------------------------------------------------- 1 | namespace: 2 | param_name: 3 | type: double 4 | default_value: "string type" 5 | description: "Test scientific notation" 6 | -------------------------------------------------------------------------------- /generate_parameter_library_py/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | generate_parameter_library_py 5 | 0.4.0 6 | Python to generate ROS parameter library. 7 | Paul Gesel 8 | Nathan Brooks 9 | Shaurya Kumar 10 | BSD-3-Clause 11 | Paul Gesel 12 | 13 | python3 14 | python3-jinja2 15 | python3-typeguard 16 | python3-yaml 17 | 18 | ament_copyright 19 | python3-pytest 20 | 21 | 22 | ament_python 23 | 24 | 25 | -------------------------------------------------------------------------------- /generate_parameter_library_py/resource/generate_parameter_library_py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PickNikRobotics/generate_parameter_library/7735ac9c4ec743add344ad3d41b7cbb1824e415d/generate_parameter_library_py/resource/generate_parameter_library_py -------------------------------------------------------------------------------- /generate_parameter_library_py/setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from setuptools import find_packages 3 | from setuptools import setup 4 | 5 | package_name = 'generate_parameter_library_py' 6 | 7 | setup( 8 | name=package_name, 9 | version='0.4.0', 10 | packages=find_packages(), 11 | data_files=[ 12 | ('share/' + package_name, ['package.xml']), 13 | ( 14 | 'share/' + package_name + '/test', 15 | ['generate_parameter_library_py/test/wrong_default_type.yaml'], 16 | ), 17 | ( 18 | 'share/' + package_name + '/test', 19 | ['generate_parameter_library_py/test/missing_type.yaml'], 20 | ), 21 | ( 22 | 'share/' + package_name + '/test', 23 | ['generate_parameter_library_py/test/invalid_syntax.yaml'], 24 | ), 25 | ( 26 | 'share/' + package_name + '/test', 27 | ['generate_parameter_library_py/test/invalid_parameter_type.yaml'], 28 | ), 29 | ( 30 | 'share/' + package_name + '/test', 31 | ['generate_parameter_library_py/test/valid_parameters.yaml'], 32 | ), 33 | ( 34 | 'share/' + package_name + '/test', 35 | ['generate_parameter_library_py/test/valid_parameters_with_none_type.yaml'], 36 | ), 37 | ('share/ament_index/resource_index/packages', ['resource/' + package_name]), 38 | ], 39 | install_requires=['setuptools', 'typeguard', 'jinja2', 'pyyaml'], 40 | package_data={ 41 | '': [ 42 | 'jinja_templates/cpp/declare_parameter', 43 | 'jinja_templates/cpp/declare_runtime_parameter', 44 | 'jinja_templates/cpp/declare_struct', 45 | 'jinja_templates/cpp/declare_variable', 46 | 'jinja_templates/cpp/parameter_library_header', 47 | 'jinja_templates/cpp/parameter_validation', 48 | 'jinja_templates/cpp/remove_runtime_parameter', 49 | 'jinja_templates/cpp/set_parameter', 50 | 'jinja_templates/cpp/set_runtime_parameter', 51 | 'jinja_templates/cpp/set_stack_params', 52 | 'jinja_templates/cpp/update_parameter', 53 | 'jinja_templates/cpp/update_runtime_parameter', 54 | 'jinja_templates/markdown/default_config', 55 | 'jinja_templates/markdown/documentation', 56 | 'jinja_templates/markdown/parameter_detail', 57 | 'jinja_templates/rst/default_config', 58 | 'jinja_templates/rst/documentation', 59 | 'jinja_templates/rst/parameter_detail', 60 | 'jinja_templates/python/declare_parameter', 61 | 'jinja_templates/python/declare_runtime_parameter', 62 | 'jinja_templates/python/declare_struct', 63 | 'jinja_templates/python/declare_variable', 64 | 'jinja_templates/python/parameter_library_header', 65 | 'jinja_templates/python/parameter_validation', 66 | 'jinja_templates/python/remove_runtime_parameter', 67 | 'jinja_templates/python/set_parameter', 68 | 'jinja_templates/python/set_runtime_parameter', 69 | 'jinja_templates/python/set_stack_params', 70 | 'jinja_templates/python/update_parameter', 71 | 'jinja_templates/python/update_runtime_parameter', 72 | ] 73 | }, 74 | zip_safe=False, 75 | author='Paul Gesel', 76 | author_email='paul.gesel@picknik.ai', 77 | url='https://github.com/PickNikRobotics/generate_parameter_library', 78 | download_url='https://github.com/PickNikRobotics/generate_parameter_library/releases', 79 | keywords=['ROS'], 80 | classifiers=[ 81 | 'Intended Audience :: Developers', 82 | 'License :: OSI Approved :: BSD-3-Clause', 83 | 'Programming Language :: Python', 84 | 'Topic :: Software Development', 85 | ], 86 | description='Generate the ROS parameter struct in C++ and Python with callbacks for updating.', 87 | license='BSD-3-Clause', 88 | tests_require=['pytest'], 89 | entry_points={ 90 | 'console_scripts': [ 91 | 'generate_parameter_library_cpp = generate_parameter_library_py.generate_cpp_header:main', 92 | 'generate_parameter_library_python = generate_parameter_library_py.generate_python_module:main', 93 | 'generate_parameter_library_markdown = generate_parameter_library_py.generate_markdown:main', 94 | ], 95 | }, 96 | ) 97 | -------------------------------------------------------------------------------- /generate_parameter_library_py/test/test_copyright.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2015 Open Source Robotics Foundation, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | from ament_copyright.main import main 17 | import pytest 18 | 19 | 20 | # Remove the `skip` decorator once the source file(s) have a copyright header 21 | @pytest.mark.skip( 22 | reason='No copyright header has been placed in the generated source file.' 23 | ) 24 | @pytest.mark.copyright 25 | @pytest.mark.linter 26 | def test_copyright(): 27 | rc = main(argv=['.', 'test']) 28 | assert rc == 0, 'Found errors' 29 | -------------------------------------------------------------------------------- /parameter_traits/CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2 | Changelog for package parameter_traits 3 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 4 | 5 | 0.4.0 (2025-01-13) 6 | ------------------ 7 | 8 | 0.3.9 (2024-10-27) 9 | ------------------ 10 | 11 | 0.3.8 (2024-03-27) 12 | ------------------ 13 | 14 | 0.3.7 (2024-01-12) 15 | ------------------ 16 | 17 | 0.3.6 (2023-07-31) 18 | ------------------ 19 | 20 | 0.3.5 (2023-07-28) 21 | ------------------ 22 | 23 | 0.3.4 (2023-07-24) 24 | ------------------ 25 | 26 | 0.3.3 (2023-04-13) 27 | ------------------ 28 | 29 | 0.3.2 (2023-04-12) 30 | ------------------ 31 | 32 | 0.3.1 (2023-02-01) 33 | ------------------ 34 | * Make it easy for users to override (`#92 `_) 35 | * Contributors: Tyler Weaver 36 | 37 | 0.3.0 (2022-11-15) 38 | ------------------ 39 | * Migrate from parameter_traits to RSL (take 2) (`#91 `_) 40 | * Contributors: Tyler Weaver 41 | 42 | 0.2.8 (2022-11-03) 43 | ------------------ 44 | 45 | 0.2.7 (2022-10-28) 46 | ------------------ 47 | * lt/gt/lt_eq/gt_eq validators (`#80 `_) 48 | * Standardize cmake (`#79 `_) 49 | * Add missing tcb_span ament export dep (`#78 `_) 50 | * Contributors: Denis Štogl, Tyler Weaver 51 | 52 | 0.2.6 (2022-09-28) 53 | ------------------ 54 | * Remove unused member variable (`#77 `_) 55 | * Depend on tcb_span (`#76 `_) 56 | * Drop requirement for CMake to 3.16 (`#73 `_) 57 | * Contributors: Tyler Weaver 58 | 59 | 0.2.5 (2022-09-20) 60 | ------------------ 61 | * Test validators and fix bugs (`#66 `_) 62 | * Contributors: Tyler Weaver 63 | 64 | 0.2.4 (2022-08-19) 65 | ------------------ 66 | * 0.2.3 67 | * Contributors: Tyler Weaver 68 | 69 | 0.2.3 (2022-08-05) 70 | ------------------ 71 | 72 | 0.2.2 (2022-08-03) 73 | ------------------ 74 | 75 | 0.2.1 (2022-08-02) 76 | ------------------ 77 | 78 | 0.2.0 (2022-08-01) 79 | ------------------ 80 | * Fixed length arrays (`#44 `_) 81 | * Move fixed size string to parameter traits (`#42 `_) 82 | * static OK to fix ODR errors (`#41 `_) 83 | * Change package name (`#40 `_) 84 | * Contributors: Paul Gesel, Tyler Weaver 85 | 86 | 0.1.0 (2022-07-27) 87 | ------------------ 88 | -------------------------------------------------------------------------------- /parameter_traits/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(parameter_traits) 3 | 4 | # find dependencies 5 | find_package(ament_cmake REQUIRED) 6 | find_package(fmt REQUIRED) 7 | find_package(rclcpp REQUIRED) 8 | find_package(rsl REQUIRED) 9 | find_package(tcb_span REQUIRED) 10 | find_package(tl_expected REQUIRED) 11 | 12 | add_library(parameter_traits INTERFACE) 13 | target_include_directories(parameter_traits INTERFACE 14 | $ 15 | $) 16 | target_compile_features(parameter_traits INTERFACE cxx_std_17) 17 | target_link_libraries(parameter_traits 18 | INTERFACE 19 | fmt::fmt 20 | rclcpp::rclcpp 21 | rsl::rsl 22 | tcb_span::tcb_span 23 | tl_expected::tl_expected 24 | ) 25 | 26 | if(BUILD_TESTING) 27 | find_package(ament_lint_auto REQUIRED) 28 | # the following lines skip linters 29 | set(ament_cmake_cpplint_FOUND TRUE) # Conflicts with clang-foramt 30 | set(ament_cmake_uncrustify_FOUND TRUE) # Conflicts with clang-format 31 | ament_lint_auto_find_test_dependencies() 32 | endif() 33 | 34 | install( 35 | DIRECTORY include/ 36 | DESTINATION include/parameter_traits 37 | ) 38 | 39 | install( 40 | TARGETS parameter_traits 41 | EXPORT export_parameter_traits 42 | LIBRARY DESTINATION lib 43 | ARCHIVE DESTINATION lib 44 | RUNTIME DESTINATION bin 45 | ) 46 | 47 | ament_export_targets(export_parameter_traits HAS_LIBRARY_TARGET) 48 | ament_export_dependencies(fmt rclcpp rsl tcb_span tl_expected) 49 | ament_package() 50 | -------------------------------------------------------------------------------- /parameter_traits/include/parameter_traits/parameter_traits.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, PickNik Inc. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 6 | // * Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // * Neither the name of the copyright holder nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | // POSSIBILITY OF SUCH DAMAGE. 28 | 29 | #pragma once 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | namespace parameter_traits { 36 | 37 | using Result 38 | [[deprecated("Use tl::expected for return instead. " 39 | "`#include `.")]] = 40 | tl::expected; 41 | 42 | template 43 | [[deprecated( 44 | "When returning tl::expected you can call fmt::format " 45 | "directly.")]] auto 46 | ERROR(const std::string& format, Args... args) 47 | -> tl::expected { 48 | return tl::make_unexpected(fmt::format(format, args...)); 49 | } 50 | 51 | auto static OK 52 | [[deprecated("When returning tl::expected default " 53 | "construct for OK with `{}`.")]] = 54 | tl::expected{}; 55 | 56 | template 57 | [[deprecated("Use rsl::contains instead. `#include `")]] bool 58 | contains(std::vector const& vec, T const& val) { 59 | return rsl::contains(vec, val); 60 | } 61 | 62 | template 63 | [[deprecated( 64 | "Use rsl::is_unique instead. `#include `")]] bool 65 | is_unique(std::vector const& x) { 66 | return rsl::is_unique(x); 67 | } 68 | 69 | } // namespace parameter_traits 70 | -------------------------------------------------------------------------------- /parameter_traits/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | parameter_traits 5 | 0.4.0 6 | Functions and types for rclcpp::Parameter 7 | Tyler Weaver 8 | Paul Gesel 9 | Nathan Brooks 10 | Shaurya Kumar 11 | BSD-3-Clause 12 | 13 | ament_cmake 14 | 15 | fmt 16 | rclcpp 17 | rsl 18 | tcb_span 19 | tl_expected 20 | 21 | ament_cmake_gtest 22 | ament_lint_auto 23 | ament_lint_common 24 | 25 | 26 | ament_cmake 27 | 28 | 29 | -------------------------------------------------------------------------------- /upstream.repos: -------------------------------------------------------------------------------- 1 | repositories: 2 | rsl: 3 | type: git 4 | url: https://github.com/picknikrobotics/RSL 5 | version: main 6 | --------------------------------------------------------------------------------