From 5285ff576cef3e9e02846851e7ef648cabd748be Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Mon, 30 Mar 2026 13:34:58 +0000 Subject: [PATCH 1/9] Fail at build time with conflicting element bounds --- .../parse_yaml.py | 22 +++++++++++++++++++ .../test/YAML_parse_error_test.py | 1 + .../test/conflicting_element_bounds.yaml | 8 +++++++ 3 files changed, 31 insertions(+) create mode 100644 generate_parameter_library_py/generate_parameter_library_py/test/conflicting_element_bounds.yaml diff --git a/generate_parameter_library_py/generate_parameter_library_py/parse_yaml.py b/generate_parameter_library_py/generate_parameter_library_py/parse_yaml.py index ed00d29..d46a40c 100644 --- a/generate_parameter_library_py/generate_parameter_library_py/parse_yaml.py +++ b/generate_parameter_library_py/generate_parameter_library_py/parse_yaml.py @@ -98,6 +98,26 @@ def int_to_integer_str(value: str): return value.replace('int', 'integer') +@typechecked +def validation_base_name(function_name: str): + return function_name.replace('<>', '') + + +@typechecked +def validate_validator_combinations(param_name: str, validations_dict: dict): + validation_names = {validation_base_name(name) for name in validations_dict} + + if { + 'lower_element_bounds', + 'upper_element_bounds', + }.issubset(validation_names): + raise compile_error( + "Parameter {} cannot combine 'lower_element_bounds' and " + "'upper_element_bounds'. Use 'element_bounds' instead so the " + 'generator emits a single ROS descriptor range.'.format(param_name) + ) + + def get_dynamic_parameter_field(yaml_parameter_name: str): tmp = yaml_parameter_name.split('.') num_nested = [i for i, val in enumerate(tmp) if is_mapped_parameter(val)] @@ -745,6 +765,8 @@ def preprocess_inputs(language, name, value, nested_name_list): if is_fixed_type(defined_type): validations_dict['size_lt<>'] = fixed_type_size(defined_type) + 1 + validate_validator_combinations(param_name, validations_dict) + for func_name in validations_dict: args = validations_dict[func_name] if args is not None and not isinstance(args, list): diff --git a/generate_parameter_library_py/generate_parameter_library_py/test/YAML_parse_error_test.py b/generate_parameter_library_py/generate_parameter_library_py/test/YAML_parse_error_test.py index eb524b7..a173cd2 100644 --- a/generate_parameter_library_py/generate_parameter_library_py/test/YAML_parse_error_test.py +++ b/generate_parameter_library_py/generate_parameter_library_py/test/YAML_parse_error_test.py @@ -74,6 +74,7 @@ def set_up(yaml_test_file): 'missing_type.yaml', 'invalid_syntax.yaml', 'invalid_parameter_type.yaml', + 'conflicting_element_bounds.yaml', ] ], ) diff --git a/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_element_bounds.yaml b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_element_bounds.yaml new file mode 100644 index 0000000..51cbf72 --- /dev/null +++ b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_element_bounds.yaml @@ -0,0 +1,8 @@ +admittance_controller: + acceleration_limits: + type: double_array + description: "specifies maximum acceleration limits for x, y and z axis" + validation: + fixed_size<>: 3 + lower_element_bounds<>: -10.0 + upper_element_bounds<>: 10.0 From 9567b9fc083d2a97861d2cd9a0264e3db2f64ff2 Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Mon, 30 Mar 2026 13:47:21 +0000 Subject: [PATCH 2/9] Fail at build time with conflicting scalar bounds --- .../parse_yaml.py | 12 +++++++ .../test/YAML_parse_error_test.py | 1 + .../test/conflicting_scalar_bounds.yaml | 32 +++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds.yaml diff --git a/generate_parameter_library_py/generate_parameter_library_py/parse_yaml.py b/generate_parameter_library_py/generate_parameter_library_py/parse_yaml.py index d46a40c..c3bf86a 100644 --- a/generate_parameter_library_py/generate_parameter_library_py/parse_yaml.py +++ b/generate_parameter_library_py/generate_parameter_library_py/parse_yaml.py @@ -117,6 +117,18 @@ def validate_validator_combinations(param_name: str, validations_dict: dict): 'generator emits a single ROS descriptor range.'.format(param_name) ) + scalar_lower_bounds = {'gt', 'gt_eq'} + scalar_upper_bounds = {'lt', 'lt_eq'} + if validation_names.intersection( + scalar_lower_bounds + ) and validation_names.intersection(scalar_upper_bounds): + raise compile_error( + 'Parameter {} cannot combine lower and upper scalar bound validators ' + '(gt/gt_eq with lt/lt_eq). ' + "Use 'bounds<>' for inclusive ranges or a custom validator for " + 'exclusive/mixed bounds.'.format(param_name) + ) + def get_dynamic_parameter_field(yaml_parameter_name: str): tmp = yaml_parameter_name.split('.') diff --git a/generate_parameter_library_py/generate_parameter_library_py/test/YAML_parse_error_test.py b/generate_parameter_library_py/generate_parameter_library_py/test/YAML_parse_error_test.py index a173cd2..163086a 100644 --- a/generate_parameter_library_py/generate_parameter_library_py/test/YAML_parse_error_test.py +++ b/generate_parameter_library_py/generate_parameter_library_py/test/YAML_parse_error_test.py @@ -75,6 +75,7 @@ def set_up(yaml_test_file): 'invalid_syntax.yaml', 'invalid_parameter_type.yaml', 'conflicting_element_bounds.yaml', + 'conflicting_scalar_bounds.yaml', ] ], ) diff --git a/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds.yaml b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds.yaml new file mode 100644 index 0000000..742e76c --- /dev/null +++ b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds.yaml @@ -0,0 +1,32 @@ +admittance_controller: + conflicting_inclusive_bounds: + type: int + default_value: 16 + description: "should be a number between 10 and 20" + validation: + gt_eq<>: [ 10 ] + lt_eq<>: [ 20 ] + + conflicting_exclusive_bounds: + type: int + default_value: 16 + description: "should be a number between 10 and 20" + validation: + gt<>: [ 10 ] + lt<>: [ 20 ] + + conflicting_mixed_bounds_1: + type: int + default_value: 16 + description: "should be a number between 10 and 20" + validation: + gt_eq<>: [ 10 ] + lt<>: [ 20 ] + + conflicting_mixed_bounds_2: + type: int + default_value: 16 + description: "should be a number between 10 and 20" + validation: + gt<>: [ 10 ] + lt_eq<>: [ 20 ] From 40936f8a0bf89ac38c145d54a3757890f85d0271 Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Mon, 30 Mar 2026 17:16:07 +0000 Subject: [PATCH 3/9] Install test yamls --- generate_parameter_library_py/setup.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/generate_parameter_library_py/setup.py b/generate_parameter_library_py/setup.py index 1a9b4b8..4c275c8 100644 --- a/generate_parameter_library_py/setup.py +++ b/generate_parameter_library_py/setup.py @@ -55,6 +55,14 @@ 'share/' + package_name + '/test', ['generate_parameter_library_py/test/invalid_parameter_type.yaml'], ), + ( + 'share/' + package_name + '/test', + ['generate_parameter_library_py/test/conflicting_element_bounds.yaml'], + ), + ( + 'share/' + package_name + '/test', + ['generate_parameter_library_py/test/conflicting_scalar_bounds.yaml'], + ), ( 'share/' + package_name + '/test', ['generate_parameter_library_py/test/valid_parameters.yaml'], From b7878839cabbb7c5672420b136874d2df4ad56d1 Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Tue, 7 Apr 2026 19:00:57 +0000 Subject: [PATCH 4/9] Also fail on combination of bounds and gt/lt variants --- .../generate_parameter_library_py/parse_yaml.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/generate_parameter_library_py/generate_parameter_library_py/parse_yaml.py b/generate_parameter_library_py/generate_parameter_library_py/parse_yaml.py index c3bf86a..e23c1b5 100644 --- a/generate_parameter_library_py/generate_parameter_library_py/parse_yaml.py +++ b/generate_parameter_library_py/generate_parameter_library_py/parse_yaml.py @@ -117,6 +117,16 @@ def validate_validator_combinations(param_name: str, validations_dict: dict): 'generator emits a single ROS descriptor range.'.format(param_name) ) + scalar_bound_validators = {'gt', 'gt_eq', 'lt', 'lt_eq'} + if 'bounds' in validation_names and validation_names.intersection( + scalar_bound_validators + ): + raise compile_error( + "Parameter {} cannot combine 'bounds' with scalar bound validators " + "(gt/gt_eq/lt/lt_eq). Use only 'bounds<>' for inclusive ranges, " + 'or only scalar bound validators.'.format(param_name) + ) + scalar_lower_bounds = {'gt', 'gt_eq'} scalar_upper_bounds = {'lt', 'lt_eq'} if validation_names.intersection( From bc7e7ff4018c408471574cd294d141092801da4c Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Tue, 7 Apr 2026 19:01:22 +0000 Subject: [PATCH 5/9] Split failure yaml files --- .../test/YAML_parse_error_test.py | 6 +++- .../test/conflicting_scalar_bounds.yaml | 32 ------------------- .../conflicting_scalar_bounds_exclusive.yaml | 8 +++++ .../conflicting_scalar_bounds_inclusive.yaml | 8 +++++ .../conflicting_scalar_bounds_mixed_1.yaml | 8 +++++ .../conflicting_scalar_bounds_mixed_2.yaml | 8 +++++ ...conflicting_scalar_bounds_with_bounds.yaml | 8 +++++ generate_parameter_library_py/setup.py | 28 +++++++++++++++- 8 files changed, 72 insertions(+), 34 deletions(-) delete mode 100644 generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds.yaml create mode 100644 generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_exclusive.yaml create mode 100644 generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_inclusive.yaml create mode 100644 generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_mixed_1.yaml create mode 100644 generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_mixed_2.yaml create mode 100644 generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_with_bounds.yaml diff --git a/generate_parameter_library_py/generate_parameter_library_py/test/YAML_parse_error_test.py b/generate_parameter_library_py/generate_parameter_library_py/test/YAML_parse_error_test.py index 163086a..3ea6ef7 100644 --- a/generate_parameter_library_py/generate_parameter_library_py/test/YAML_parse_error_test.py +++ b/generate_parameter_library_py/generate_parameter_library_py/test/YAML_parse_error_test.py @@ -75,7 +75,11 @@ def set_up(yaml_test_file): 'invalid_syntax.yaml', 'invalid_parameter_type.yaml', 'conflicting_element_bounds.yaml', - 'conflicting_scalar_bounds.yaml', + 'conflicting_scalar_bounds_with_bounds.yaml', + 'conflicting_scalar_bounds_inclusive.yaml', + 'conflicting_scalar_bounds_exclusive.yaml', + 'conflicting_scalar_bounds_mixed_1.yaml', + 'conflicting_scalar_bounds_mixed_2.yaml', ] ], ) diff --git a/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds.yaml b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds.yaml deleted file mode 100644 index 742e76c..0000000 --- a/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds.yaml +++ /dev/null @@ -1,32 +0,0 @@ -admittance_controller: - conflicting_inclusive_bounds: - type: int - default_value: 16 - description: "should be a number between 10 and 20" - validation: - gt_eq<>: [ 10 ] - lt_eq<>: [ 20 ] - - conflicting_exclusive_bounds: - type: int - default_value: 16 - description: "should be a number between 10 and 20" - validation: - gt<>: [ 10 ] - lt<>: [ 20 ] - - conflicting_mixed_bounds_1: - type: int - default_value: 16 - description: "should be a number between 10 and 20" - validation: - gt_eq<>: [ 10 ] - lt<>: [ 20 ] - - conflicting_mixed_bounds_2: - type: int - default_value: 16 - description: "should be a number between 10 and 20" - validation: - gt<>: [ 10 ] - lt_eq<>: [ 20 ] diff --git a/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_exclusive.yaml b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_exclusive.yaml new file mode 100644 index 0000000..ea09f4a --- /dev/null +++ b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_exclusive.yaml @@ -0,0 +1,8 @@ +admittance_controller: + conflicting_exclusive_bounds: + type: int + default_value: 16 + description: "should be a number between 10 and 20" + validation: + gt<>: [ 10 ] + lt<>: [ 20 ] diff --git a/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_inclusive.yaml b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_inclusive.yaml new file mode 100644 index 0000000..d4f3ba2 --- /dev/null +++ b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_inclusive.yaml @@ -0,0 +1,8 @@ +admittance_controller: + conflicting_inclusive_bounds: + type: int + default_value: 16 + description: "should be a number between 10 and 20" + validation: + gt_eq<>: [ 10 ] + lt_eq<>: [ 20 ] diff --git a/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_mixed_1.yaml b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_mixed_1.yaml new file mode 100644 index 0000000..c9270e8 --- /dev/null +++ b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_mixed_1.yaml @@ -0,0 +1,8 @@ +admittance_controller: + conflicting_mixed_bounds_1: + type: int + default_value: 16 + description: "should be a number between 10 and 20" + validation: + gt_eq<>: [ 10 ] + lt<>: [ 20 ] diff --git a/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_mixed_2.yaml b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_mixed_2.yaml new file mode 100644 index 0000000..b0cdcef --- /dev/null +++ b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_mixed_2.yaml @@ -0,0 +1,8 @@ +admittance_controller: + conflicting_mixed_bounds_2: + type: int + default_value: 16 + description: "should be a number between 10 and 20" + validation: + gt<>: [ 10 ] + lt_eq<>: [ 20 ] diff --git a/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_with_bounds.yaml b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_with_bounds.yaml new file mode 100644 index 0000000..52c3e3b --- /dev/null +++ b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_with_bounds.yaml @@ -0,0 +1,8 @@ +admittance_controller: + conflicting_bounds: + type: int + default_value: 16 + description: "should be a number between 10 and 20" + validation: + bounds<>: [ 10, 20 ] + lt_eq<>: [ 20 ] diff --git a/generate_parameter_library_py/setup.py b/generate_parameter_library_py/setup.py index 4c275c8..3cd7e96 100644 --- a/generate_parameter_library_py/setup.py +++ b/generate_parameter_library_py/setup.py @@ -61,7 +61,33 @@ ), ( 'share/' + package_name + '/test', - ['generate_parameter_library_py/test/conflicting_scalar_bounds.yaml'], + [ + 'generate_parameter_library_py/test/conflicting_scalar_bounds_with_bounds.yaml' + ], + ), + ( + 'share/' + package_name + '/test', + [ + 'generate_parameter_library_py/test/conflicting_scalar_bounds_inclusive.yaml' + ], + ), + ( + 'share/' + package_name + '/test', + [ + 'generate_parameter_library_py/test/conflicting_scalar_bounds_exclusive.yaml' + ], + ), + ( + 'share/' + package_name + '/test', + [ + 'generate_parameter_library_py/test/conflicting_scalar_bounds_mixed_1.yaml' + ], + ), + ( + 'share/' + package_name + '/test', + [ + 'generate_parameter_library_py/test/conflicting_scalar_bounds_mixed_2.yaml' + ], ), ( 'share/' + package_name + '/test', From b897c6a06ee8f179da7d42e624094edadcdee380 Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Tue, 7 Apr 2026 19:05:21 +0000 Subject: [PATCH 6/9] Add notes to README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index b54f345..5c0b353 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,8 @@ The built-in validator functions provided by this package are: | gt_eq<> | [value] | parameter >= value | | one_of<> | [[val1, val2, ...]] | Value is one of the specified values | +Note: `lt<>`, `gt<>`, `lt_eq<>`, `gt_eq<>`, or `bounds<>` cannot be used together. + **String validators** | Function | Arguments | Description | | ------------ | ------------------- | ---------------------------------------------- | @@ -286,6 +288,8 @@ The built-in validator functions provided by this package are: | lower_element_bounds<> | [lower] | Lower bound for each element (inclusive) | | upper_element_bounds<> | [upper] | Upper bound for each element (inclusive) | +Note: `element_bounds<>` cannot be mixed with `lower_element_bounds<>`, or `upper_element_bounds<>`. + ### Custom validator functions Validators are functions that return a `tl::expected` type and accept a `rclcpp::Parameter const&` as their first argument and any number of arguments after that can be specified in YAML. Validators are C++ functions defined in a header file similar to the example shown below. From d0a865fd4f1497b7b7ab000cb710fb4ab9de7a22 Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Tue, 7 Apr 2026 20:30:50 +0000 Subject: [PATCH 7/9] Allow one-sided bounds combinations --- example/src/parameters.yaml | 23 ++++++++++ .../parameters.yaml | 23 ++++++++++ .../jinja_templates/cpp/declare_parameter | 44 ++++++++++--------- .../cpp/declare_runtime_parameter | 44 ++++++++++--------- .../jinja_templates/python/declare_parameter | 40 +++++++++-------- .../python/declare_runtime_parameter | 40 +++++++++-------- .../parse_yaml.py | 22 +++------- .../test/YAML_parse_error_test.py | 4 -- .../test/conflicting_element_bounds.yaml | 2 +- .../conflicting_scalar_bounds_exclusive.yaml | 8 ---- .../conflicting_scalar_bounds_inclusive.yaml | 8 ---- .../conflicting_scalar_bounds_mixed_1.yaml | 8 ---- .../conflicting_scalar_bounds_mixed_2.yaml | 8 ---- .../test/valid_parameters.yaml | 21 +++++++++ generate_parameter_library_py/setup.py | 24 ---------- 15 files changed, 165 insertions(+), 154 deletions(-) delete mode 100644 generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_exclusive.yaml delete mode 100644 generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_inclusive.yaml delete mode 100644 generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_mixed_1.yaml delete mode 100644 generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_mixed_2.yaml diff --git a/example/src/parameters.yaml b/example/src/parameters.yaml index 5f46ad8..dc8955b 100644 --- a/example/src/parameters.yaml +++ b/example/src/parameters.yaml @@ -223,6 +223,15 @@ admittance_controller: validation: element_bounds: [ 0.0001, 100000.0 ] + acceleration_limits: + type: double_array + description: "specifies maximum acceleration limits for x, y and z axis" + default_value: [0.0, 0.0, 0.0] + validation: + fixed_size<>: 3 + lower_element_bounds<>: -10.0 + upper_element_bounds<>: 10.0 + # general settings enable_parameter_update_without_reactivation: type: bool @@ -244,6 +253,20 @@ admittance_controller: description: "should be a number greater than 15" validation: gt<>: [ 15 ] + gt_fifteen_lt_eq_twenty: + type: int + default_value: 20 + description: "should be a number greater than 15 and less than or equal to 20" + validation: + gt<>: [ 15 ] + lt_eq<>: [ 20 ] + gt_fifteen_lt_twenty: + type: int + default_value: 16 + description: "should be a number greater than 15 and less than 20" + validation: + gt<>: [ 15 ] + lt<>: [ 20 ] one_number: type: int default_value: 14540 diff --git a/example_python/generate_parameter_module_example/parameters.yaml b/example_python/generate_parameter_module_example/parameters.yaml index acd6463..48dd68d 100644 --- a/example_python/generate_parameter_module_example/parameters.yaml +++ b/example_python/generate_parameter_module_example/parameters.yaml @@ -226,6 +226,15 @@ admittance_controller: validation: element_bounds: [ 0.0001, 100000.0 ] + acceleration_limits: + type: double_array + description: "specifies maximum acceleration limits for x, y and z axis" + default_value: [0.0, 0.0, 0.0] + validation: + fixed_size<>: 3 + lower_element_bounds<>: -10.0 + upper_element_bounds<>: 10.0 + # general settings enable_parameter_update_without_reactivation: type: bool @@ -247,6 +256,20 @@ admittance_controller: description: "should be a number greater than 15" validation: gt<>: [ 15 ] + gt_fifteen_lt_eq_twenty: + type: int + default_value: 20 + description: "should be a number greater than 15 and less than or equal to 20" + validation: + gt<>: [ 15 ] + lt_eq<>: [ 20 ] + gt_fifteen_lt_twenty: + type: int + default_value: 16 + description: "should be a number greater than 15 and less than 20" + validation: + gt<>: [ 15 ] + lt<>: [ 20 ] one_number: type: int default_value: 14540 diff --git a/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/cpp/declare_parameter b/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/cpp/declare_parameter index 51e4fb4..c0896e2 100644 --- a/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/cpp/declare_parameter +++ b/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/cpp/declare_parameter @@ -6,37 +6,41 @@ descriptor.read_only = {{parameter_read_only}}; {%- if parameter_additional_constraints|length %} descriptor.additional_constraints = {{parameter_additional_constraints | valid_string_cpp}}; {% endif -%} -{%- for validation in parameter_validations if ("bounds" in validation.function_name or "lt" in validation.function_name or "gt" in validation.function_name) %} {%- if "DOUBLE" in parameter_type %} +{%- set range = namespace(lower=None, upper=None) %} +{%- for validation in parameter_validations if ("bounds" in validation.function_name or "lt" in validation.function_name or "gt" in validation.function_name) %} {%- if validation.arguments|length == 2 %} -descriptor.floating_point_range.resize({{loop.index}}); -descriptor.floating_point_range.at({{loop.index0}}).from_value = {{validation.arguments[0]}}; -descriptor.floating_point_range.at({{loop.index0}}).to_value = {{validation.arguments[1]}}; +{%- set range.lower = validation.arguments[0] %} +{%- set range.upper = validation.arguments[1] %} {%- elif ("lower" in validation.function_name or "gt" == validation.function_base_name or "gt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} -descriptor.floating_point_range.resize({{loop.index}}); -descriptor.floating_point_range.at({{loop.index0}}).from_value = {{validation.arguments[0]}}; -descriptor.floating_point_range.at({{loop.index0}}).to_value = std::numeric_limits::max(); +{%- set range.lower = validation.arguments[0] %} {%- elif ("upper" in validation.function_name or "lt" == validation.function_base_name or "lt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} -descriptor.floating_point_range.resize({{loop.index}}); -descriptor.floating_point_range.at({{loop.index0}}).from_value = std::numeric_limits::lowest(); -descriptor.floating_point_range.at({{loop.index0}}).to_value = {{validation.arguments[0]}}; +{%- set range.upper = validation.arguments[0] %} +{%- endif %} +{%- endfor %} +{%- if range.lower is not none or range.upper is not none %} +descriptor.floating_point_range.resize(1); +descriptor.floating_point_range.at(0).from_value = {{range.lower if range.lower is not none else "std::numeric_limits::lowest()"}}; +descriptor.floating_point_range.at(0).to_value = {{range.upper if range.upper is not none else "std::numeric_limits::max()"}}; {%- endif %} {%- elif "INTEGER" in parameter_type %} +{%- set range = namespace(lower=None, upper=None) %} +{%- for validation in parameter_validations if ("bounds" in validation.function_name or "lt" in validation.function_name or "gt" in validation.function_name) %} {%- if validation.arguments|length == 2 %} -descriptor.integer_range.resize({{loop.index}}); -descriptor.integer_range.at({{loop.index0}}).from_value = {{validation.arguments[0]}}; -descriptor.integer_range.at({{loop.index0}}).to_value = {{validation.arguments[1]}}; +{%- set range.lower = validation.arguments[0] %} +{%- set range.upper = validation.arguments[1] %} {%- elif ("lower" in validation.function_name or "gt" == validation.function_base_name or "gt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} -descriptor.integer_range.resize({{loop.index}}); -descriptor.integer_range.at({{loop.index0}}).from_value = {{validation.arguments[0]}}; -descriptor.integer_range.at({{loop.index0}}).to_value = std::numeric_limits::max(); +{%- set range.lower = validation.arguments[0] %} {%- elif ("upper" in validation.function_name or "lt" == validation.function_base_name or "lt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} -descriptor.integer_range.resize({{loop.index}}); -descriptor.integer_range.at({{loop.index0}}).from_value = std::numeric_limits::lowest(); -descriptor.integer_range.at({{loop.index0}}).to_value = {{validation.arguments[0]}}; -{%- endif %} +{%- set range.upper = validation.arguments[0] %} {%- endif %} {%- endfor %} +{%- if range.lower is not none or range.upper is not none %} +descriptor.integer_range.resize(1); +descriptor.integer_range.at(0).from_value = {{range.lower if range.lower is not none else "std::numeric_limits::lowest()"}}; +descriptor.integer_range.at(0).to_value = {{range.upper if range.upper is not none else "std::numeric_limits::max()"}}; +{%- endif %} +{%- endif %} {%- if not parameter_value|length %} auto parameter = rclcpp::ParameterType::PARAMETER_{{parameter_type}}; {% endif -%} diff --git a/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/cpp/declare_runtime_parameter b/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/cpp/declare_runtime_parameter index 625a842..935bb17 100644 --- a/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/cpp/declare_runtime_parameter +++ b/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/cpp/declare_runtime_parameter @@ -22,37 +22,41 @@ descriptor.read_only = {{parameter_read_only}}; {%- if parameter_additional_constraints|length %} descriptor.additional_constraints = {{parameter_additional_constraints | valid_string_cpp}}; {% endif -%} -{%- for validation in parameter_validations if ("bounds" in validation.function_name or "lt" in validation.function_name or "gt" in validation.function_name) %} {%- if "DOUBLE" in parameter_type %} +{%- set range = namespace(lower=None, upper=None) %} +{%- for validation in parameter_validations if ("bounds" in validation.function_name or "lt" in validation.function_name or "gt" in validation.function_name) %} {%- if validation.arguments|length == 2 %} -descriptor.floating_point_range.resize({{loop.index}}); -descriptor.floating_point_range.at({{loop.index0}}).from_value = {{validation.arguments[0]}}; -descriptor.floating_point_range.at({{loop.index0}}).to_value = {{validation.arguments[1]}}; +{%- set range.lower = validation.arguments[0] %} +{%- set range.upper = validation.arguments[1] %} {%- elif ("lower" in validation.function_name or "gt" == validation.function_base_name or "gt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} -descriptor.floating_point_range.resize({{loop.index}}); -descriptor.floating_point_range.at({{loop.index0}}).from_value = {{validation.arguments[0]}}; -descriptor.floating_point_range.at({{loop.index0}}).to_value = std::numeric_limits::max(); +{%- set range.lower = validation.arguments[0] %} {%- elif ("upper" in validation.function_name or "lt" == validation.function_base_name or "lt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} -descriptor.floating_point_range.resize({{loop.index}}); -descriptor.floating_point_range.at({{loop.index0}}).from_value = std::numeric_limits::lowest(); -descriptor.floating_point_range.at({{loop.index0}}).to_value = {{validation.arguments[0]}}; +{%- set range.upper = validation.arguments[0] %} +{%- endif %} +{%- endfor %} +{%- if range.lower is not none or range.upper is not none %} +descriptor.floating_point_range.resize(1); +descriptor.floating_point_range.at(0).from_value = {{range.lower if range.lower is not none else "std::numeric_limits::lowest()"}}; +descriptor.floating_point_range.at(0).to_value = {{range.upper if range.upper is not none else "std::numeric_limits::max()"}}; {%- endif %} {%- elif "INTEGER" in parameter_type %} +{%- set range = namespace(lower=None, upper=None) %} +{%- for validation in parameter_validations if ("bounds" in validation.function_name or "lt" in validation.function_name or "gt" in validation.function_name) %} {%- if validation.arguments|length == 2 %} -descriptor.integer_range.resize({{loop.index}}); -descriptor.integer_range.at({{loop.index0}}).from_value = {{validation.arguments[0]}}; -descriptor.integer_range.at({{loop.index0}}).to_value = {{validation.arguments[1]}}; +{%- set range.lower = validation.arguments[0] %} +{%- set range.upper = validation.arguments[1] %} {%- elif ("lower" in validation.function_name or "gt" == validation.function_base_name or "gt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} -descriptor.integer_range.resize({{loop.index}}); -descriptor.integer_range.at({{loop.index0}}).from_value = {{validation.arguments[0]}}; -descriptor.integer_range.at({{loop.index0}}).to_value = std::numeric_limits::max(); +{%- set range.lower = validation.arguments[0] %} {%- elif ("upper" in validation.function_name or "lt" == validation.function_base_name or "lt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} -descriptor.integer_range.resize({{loop.index}}); -descriptor.integer_range.at({{loop.index0}}).from_value = std::numeric_limits::lowest(); -descriptor.integer_range.at({{loop.index0}}).to_value = {{validation.arguments[0]}}; -{%- endif %} +{%- set range.upper = validation.arguments[0] %} {%- endif %} {%- endfor %} +{%- if range.lower is not none or range.upper is not none %} +descriptor.integer_range.resize(1); +descriptor.integer_range.at(0).from_value = {{range.lower if range.lower is not none else "std::numeric_limits::lowest()"}}; +descriptor.integer_range.at(0).to_value = {{range.upper if range.upper is not none else "std::numeric_limits::max()"}}; +{%- endif %} +{%- endif %} {%- if not default_value|length %} auto parameter = rclcpp::ParameterType::PARAMETER_{{parameter_type}}; {% endif -%} diff --git a/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/declare_parameter b/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/declare_parameter index 312a001..bc71a37 100644 --- a/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/declare_parameter +++ b/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/declare_parameter @@ -4,37 +4,41 @@ descriptor = ParameterDescriptor(description=r"{{parameter_description|valid_str {%- if parameter_additional_constraints|length %} descriptor.additional_constraints = "{{parameter_additional_constraints|valid_string_python}}" {% endif -%} -{%- for validation in parameter_validations if ("bounds" in validation.function_name or "lt" in validation.function_name or "gt" in validation.function_name) %} {%- if "DOUBLE" in parameter_type %} +{%- set range = namespace(lower=None, upper=None) %} +{%- for validation in parameter_validations if ("bounds" in validation.function_name or "lt" in validation.function_name or "gt" in validation.function_name) %} {%- if validation.arguments|length == 2 %} -descriptor.floating_point_range.append(FloatingPointRange()) -descriptor.floating_point_range[-1].from_value = {{validation.arguments[0]}} -descriptor.floating_point_range[-1].to_value = {{validation.arguments[1]}} +{%- set range.lower = validation.arguments[0] %} +{%- set range.upper = validation.arguments[1] %} {%- elif ("lower" in validation.function_name or "gt" == validation.function_base_name or "gt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} -descriptor.floating_point_range.append(FloatingPointRange()) -descriptor.floating_point_range[-1].from_value = {{validation.arguments[0]}} -descriptor.floating_point_range[-1].to_value = float('inf') +{%- set range.lower = validation.arguments[0] %} {%- elif ("upper" in validation.function_name or "lt" == validation.function_base_name or "lt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} +{%- set range.upper = validation.arguments[0] %} +{%- endif %} +{%- endfor %} +{%- if range.lower is not none or range.upper is not none %} descriptor.floating_point_range.append(FloatingPointRange()) -descriptor.floating_point_range[-1].from_value = -float('inf') -descriptor.floating_point_range[-1].to_value = {{validation.arguments[0]}} +descriptor.floating_point_range[-1].from_value = {{range.lower if range.lower is not none else "-float('inf')"}} +descriptor.floating_point_range[-1].to_value = {{range.upper if range.upper is not none else "float('inf')"}} {%- endif %} {%- elif "INTEGER" in parameter_type %} +{%- set range = namespace(lower=None, upper=None) %} +{%- for validation in parameter_validations if ("bounds" in validation.function_name or "lt" in validation.function_name or "gt" in validation.function_name) %} {%- if validation.arguments|length == 2 %} -descriptor.integer_range.append(IntegerRange()) -descriptor.integer_range[-1].from_value = {{validation.arguments[0]}} -descriptor.integer_range[-1].to_value = {{validation.arguments[1]}} +{%- set range.lower = validation.arguments[0] %} +{%- set range.upper = validation.arguments[1] %} {%- elif ("lower" in validation.function_name or "gt" == validation.function_base_name or "gt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} -descriptor.integer_range.append(IntegerRange()) -descriptor.integer_range[-1].from_value = {{validation.arguments[0]}} -descriptor.integer_range[-1].to_value = 2**31-1 +{%- set range.lower = validation.arguments[0] %} {%- elif ("upper" in validation.function_name or "lt" == validation.function_base_name or "lt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} +{%- set range.upper = validation.arguments[0] %} +{%- endif %} +{%- endfor %} +{%- if range.lower is not none or range.upper is not none %} descriptor.integer_range.append(IntegerRange()) -descriptor.integer_range[-1].from_value = -2**31-1 -descriptor.integer_range[-1].to_value = {{validation.arguments[0]}} +descriptor.integer_range[-1].from_value = {{range.lower if range.lower is not none else "-2**31-1"}} +descriptor.integer_range[-1].to_value = {{range.upper if range.upper is not none else "2**31-1"}} {%- endif %} {%- endif %} -{%- endfor %} {%- if not parameter_value|length %} parameter = rclpy.Parameter.Type.{{parameter_type}} {% endif -%} diff --git a/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/declare_runtime_parameter b/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/declare_runtime_parameter index 4d98938..a74ca2c 100644 --- a/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/declare_runtime_parameter +++ b/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/python/declare_runtime_parameter @@ -19,37 +19,41 @@ descriptor = ParameterDescriptor(description=r"{{parameter_description|valid_str {%- if parameter_additional_constraints|length %} descriptor.additional_constraints = "{{parameter_additional_constraints|valid_string_python}}" {% endif -%} -{%- for validation in parameter_validations if ("bounds" in validation.function_name or "lt" in validation.function_name or "gt" in validation.function_name) %} {%- if "DOUBLE" in parameter_type %} +{%- set range = namespace(lower=None, upper=None) %} +{%- for validation in parameter_validations if ("bounds" in validation.function_name or "lt" in validation.function_name or "gt" in validation.function_name) %} {%- if validation.arguments|length == 2 %} -descriptor.floating_point_range.append(FloatingPointRange()) -descriptor.floating_point_range[-1].from_value = {{validation.arguments[0]}} -descriptor.floating_point_range[-1].to_value = {{validation.arguments[1]}} +{%- set range.lower = validation.arguments[0] %} +{%- set range.upper = validation.arguments[1] %} {%- elif ("lower" in validation.function_name or "gt" == validation.function_base_name or "gt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} -descriptor.floating_point_range.append(FloatingPointRange()) -descriptor.floating_point_range[-1].from_value = {{validation.arguments[0]}} -descriptor.floating_point_range[-1].to_value = float('inf') +{%- set range.lower = validation.arguments[0] %} {%- elif ("upper" in validation.function_name or "lt" == validation.function_base_name or "lt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} +{%- set range.upper = validation.arguments[0] %} +{%- endif %} +{%- endfor %} +{%- if range.lower is not none or range.upper is not none %} descriptor.floating_point_range.append(FloatingPointRange()) -descriptor.floating_point_range[-1].from_value = -float('inf') -descriptor.floating_point_range[-1].to_value = {{validation.arguments[0]}} +descriptor.floating_point_range[-1].from_value = {{range.lower if range.lower is not none else "-float('inf')"}} +descriptor.floating_point_range[-1].to_value = {{range.upper if range.upper is not none else "float('inf')"}} {%- endif %} {%- elif "INTEGER" in parameter_type %} +{%- set range = namespace(lower=None, upper=None) %} +{%- for validation in parameter_validations if ("bounds" in validation.function_name or "lt" in validation.function_name or "gt" in validation.function_name) %} {%- if validation.arguments|length == 2 %} -descriptor.integer_range.append(IntegerRange()) -descriptor.integer_range[-1].from_value = {{validation.arguments[0]}} -descriptor.integer_range[-1].to_value = {{validation.arguments[1]}} +{%- set range.lower = validation.arguments[0] %} +{%- set range.upper = validation.arguments[1] %} {%- elif ("lower" in validation.function_name or "gt" == validation.function_base_name or "gt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} -descriptor.integer_range.append(IntegerRange()) -descriptor.integer_range[-1].from_value = {{validation.arguments[0]}} -descriptor.integer_range[-1].to_value = 2**31-1 +{%- set range.lower = validation.arguments[0] %} {%- elif ("upper" in validation.function_name or "lt" == validation.function_base_name or "lt_eq" == validation.function_base_name) and validation.arguments|length == 1 %} +{%- set range.upper = validation.arguments[0] %} +{%- endif %} +{%- endfor %} +{%- if range.lower is not none or range.upper is not none %} descriptor.integer_range.append(IntegerRange()) -descriptor.integer_range[-1].from_value = -2**31-1 -descriptor.integer_range[-1].to_value = {{validation.arguments[0]}} +descriptor.integer_range[-1].from_value = {{range.lower if range.lower is not none else "-2**31-1"}} +descriptor.integer_range[-1].to_value = {{range.upper if range.upper is not none else "2**31-1"}} {%- endif %} {%- endif %} -{%- endfor %} {%- if not default_value|length %} parameter = rclpy.Parameter.Type.{{parameter_type}} {% endif -%} diff --git a/generate_parameter_library_py/generate_parameter_library_py/parse_yaml.py b/generate_parameter_library_py/generate_parameter_library_py/parse_yaml.py index e23c1b5..060980f 100644 --- a/generate_parameter_library_py/generate_parameter_library_py/parse_yaml.py +++ b/generate_parameter_library_py/generate_parameter_library_py/parse_yaml.py @@ -107,14 +107,14 @@ def validation_base_name(function_name: str): def validate_validator_combinations(param_name: str, validations_dict: dict): validation_names = {validation_base_name(name) for name in validations_dict} - if { + if 'element_bounds' in validation_names and { 'lower_element_bounds', 'upper_element_bounds', - }.issubset(validation_names): + }.intersection(validation_names): raise compile_error( - "Parameter {} cannot combine 'lower_element_bounds' and " - "'upper_element_bounds'. Use 'element_bounds' instead so the " - 'generator emits a single ROS descriptor range.'.format(param_name) + "Parameter {} cannot combine 'element_bounds' with 'lower_element_bounds/upper_element_bounds'.".format( + param_name + ) ) scalar_bound_validators = {'gt', 'gt_eq', 'lt', 'lt_eq'} @@ -127,18 +127,6 @@ def validate_validator_combinations(param_name: str, validations_dict: dict): 'or only scalar bound validators.'.format(param_name) ) - scalar_lower_bounds = {'gt', 'gt_eq'} - scalar_upper_bounds = {'lt', 'lt_eq'} - if validation_names.intersection( - scalar_lower_bounds - ) and validation_names.intersection(scalar_upper_bounds): - raise compile_error( - 'Parameter {} cannot combine lower and upper scalar bound validators ' - '(gt/gt_eq with lt/lt_eq). ' - "Use 'bounds<>' for inclusive ranges or a custom validator for " - 'exclusive/mixed bounds.'.format(param_name) - ) - def get_dynamic_parameter_field(yaml_parameter_name: str): tmp = yaml_parameter_name.split('.') diff --git a/generate_parameter_library_py/generate_parameter_library_py/test/YAML_parse_error_test.py b/generate_parameter_library_py/generate_parameter_library_py/test/YAML_parse_error_test.py index 3ea6ef7..2800766 100644 --- a/generate_parameter_library_py/generate_parameter_library_py/test/YAML_parse_error_test.py +++ b/generate_parameter_library_py/generate_parameter_library_py/test/YAML_parse_error_test.py @@ -76,10 +76,6 @@ def set_up(yaml_test_file): 'invalid_parameter_type.yaml', 'conflicting_element_bounds.yaml', 'conflicting_scalar_bounds_with_bounds.yaml', - 'conflicting_scalar_bounds_inclusive.yaml', - 'conflicting_scalar_bounds_exclusive.yaml', - 'conflicting_scalar_bounds_mixed_1.yaml', - 'conflicting_scalar_bounds_mixed_2.yaml', ] ], ) diff --git a/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_element_bounds.yaml b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_element_bounds.yaml index 51cbf72..2f56e5e 100644 --- a/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_element_bounds.yaml +++ b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_element_bounds.yaml @@ -4,5 +4,5 @@ admittance_controller: description: "specifies maximum acceleration limits for x, y and z axis" validation: fixed_size<>: 3 - lower_element_bounds<>: -10.0 + element_bounds<>: [-10.0, 20.0] upper_element_bounds<>: 10.0 diff --git a/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_exclusive.yaml b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_exclusive.yaml deleted file mode 100644 index ea09f4a..0000000 --- a/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_exclusive.yaml +++ /dev/null @@ -1,8 +0,0 @@ -admittance_controller: - conflicting_exclusive_bounds: - type: int - default_value: 16 - description: "should be a number between 10 and 20" - validation: - gt<>: [ 10 ] - lt<>: [ 20 ] diff --git a/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_inclusive.yaml b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_inclusive.yaml deleted file mode 100644 index d4f3ba2..0000000 --- a/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_inclusive.yaml +++ /dev/null @@ -1,8 +0,0 @@ -admittance_controller: - conflicting_inclusive_bounds: - type: int - default_value: 16 - description: "should be a number between 10 and 20" - validation: - gt_eq<>: [ 10 ] - lt_eq<>: [ 20 ] diff --git a/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_mixed_1.yaml b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_mixed_1.yaml deleted file mode 100644 index c9270e8..0000000 --- a/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_mixed_1.yaml +++ /dev/null @@ -1,8 +0,0 @@ -admittance_controller: - conflicting_mixed_bounds_1: - type: int - default_value: 16 - description: "should be a number between 10 and 20" - validation: - gt_eq<>: [ 10 ] - lt<>: [ 20 ] diff --git a/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_mixed_2.yaml b/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_mixed_2.yaml deleted file mode 100644 index b0cdcef..0000000 --- a/generate_parameter_library_py/generate_parameter_library_py/test/conflicting_scalar_bounds_mixed_2.yaml +++ /dev/null @@ -1,8 +0,0 @@ -admittance_controller: - conflicting_mixed_bounds_2: - type: int - default_value: 16 - description: "should be a number between 10 and 20" - validation: - gt<>: [ 10 ] - lt_eq<>: [ 20 ] diff --git a/generate_parameter_library_py/generate_parameter_library_py/test/valid_parameters.yaml b/generate_parameter_library_py/generate_parameter_library_py/test/valid_parameters.yaml index 6142a45..fbbbb8d 100644 --- a/generate_parameter_library_py/generate_parameter_library_py/test/valid_parameters.yaml +++ b/generate_parameter_library_py/generate_parameter_library_py/test/valid_parameters.yaml @@ -34,6 +34,27 @@ admittance_controller: type: double default_value: 1.0 description: "derivative gain term" + gt_fifteen_lt_eq_twenty: + type: int + default_value: 20 + description: "should be a number greater than 15 and less than or equal to 20" + validation: + gt<>: [ 15 ] + lt_eq<>: [ 20 ] + gt_fifteen_lt_twenty: + type: int + default_value: 16 + description: "should be a number greater than 15 and less than 20" + validation: + gt<>: [ 15 ] + lt<>: [ 20 ] + acceleration_limits: + type: double_array + description: "specifies maximum acceleration limits for x, y and z axis" + validation: + fixed_size<>: 3 + lower_element_bounds<>: -10.0 + upper_element_bounds<>: 10.0 fixed_string: type: string_fixed_25 default_value: "string_value" diff --git a/generate_parameter_library_py/setup.py b/generate_parameter_library_py/setup.py index 3cd7e96..92ec109 100644 --- a/generate_parameter_library_py/setup.py +++ b/generate_parameter_library_py/setup.py @@ -65,30 +65,6 @@ 'generate_parameter_library_py/test/conflicting_scalar_bounds_with_bounds.yaml' ], ), - ( - 'share/' + package_name + '/test', - [ - 'generate_parameter_library_py/test/conflicting_scalar_bounds_inclusive.yaml' - ], - ), - ( - 'share/' + package_name + '/test', - [ - 'generate_parameter_library_py/test/conflicting_scalar_bounds_exclusive.yaml' - ], - ), - ( - 'share/' + package_name + '/test', - [ - 'generate_parameter_library_py/test/conflicting_scalar_bounds_mixed_1.yaml' - ], - ), - ( - 'share/' + package_name + '/test', - [ - 'generate_parameter_library_py/test/conflicting_scalar_bounds_mixed_2.yaml' - ], - ), ( 'share/' + package_name + '/test', ['generate_parameter_library_py/test/valid_parameters.yaml'], From 95243e47c045e2a8dd20ab93156c316fe55e21e3 Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Tue, 7 Apr 2026 20:48:26 +0000 Subject: [PATCH 8/9] Fix readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5c0b353..40fcd6b 100644 --- a/README.md +++ b/README.md @@ -264,7 +264,7 @@ The built-in validator functions provided by this package are: | gt_eq<> | [value] | parameter >= value | | one_of<> | [[val1, val2, ...]] | Value is one of the specified values | -Note: `lt<>`, `gt<>`, `lt_eq<>`, `gt_eq<>`, or `bounds<>` cannot be used together. +Note: `lt<>`, `gt<>`, `lt_eq<>`, or `gt_eq<>` cannot be used together with `bounds<>`. **String validators** | Function | Arguments | Description | @@ -288,7 +288,7 @@ Note: `lt<>`, `gt<>`, `lt_eq<>`, `gt_eq<>`, or `bounds<>` cannot be used togethe | lower_element_bounds<> | [lower] | Lower bound for each element (inclusive) | | upper_element_bounds<> | [upper] | Upper bound for each element (inclusive) | -Note: `element_bounds<>` cannot be mixed with `lower_element_bounds<>`, or `upper_element_bounds<>`. +Note: `element_bounds<>` cannot be mixed with `lower_element_bounds<>` or `upper_element_bounds<>`. ### Custom validator functions Validators are functions that return a `tl::expected` type and accept a `rclcpp::Parameter const&` as their first argument and any number of arguments after that can be specified in YAML. From 935ebfefec25618480da0e3e012693c25db82cea Mon Sep 17 00:00:00 2001 From: Christoph Froehlich Date: Tue, 7 Apr 2026 20:58:24 +0000 Subject: [PATCH 9/9] Add also a test field for runtime(mapped) parameters --- example/src/parameters.yaml | 9 +++++++++ .../generate_parameter_module_example/parameters.yaml | 9 +++++++++ .../test/valid_parameters.yaml | 10 ++++++++++ 3 files changed, 28 insertions(+) diff --git a/example/src/parameters.yaml b/example/src/parameters.yaml index dc8955b..4eb222e 100644 --- a/example/src/parameters.yaml +++ b/example/src/parameters.yaml @@ -29,6 +29,15 @@ admittance_controller: validation: gt<>: [0.0] + limit: + type: double_array + description: "specifies limit for x, y and z axis" + default_value: [0.0, 0.0, 0.0] + validation: + fixed_size<>: 3 + lower_element_bounds<>: -10.0 + upper_element_bounds<>: 10.0 + nested_dynamic: __map_joints: __map_dof_names: diff --git a/example_python/generate_parameter_module_example/parameters.yaml b/example_python/generate_parameter_module_example/parameters.yaml index 48dd68d..df33939 100644 --- a/example_python/generate_parameter_module_example/parameters.yaml +++ b/example_python/generate_parameter_module_example/parameters.yaml @@ -35,6 +35,15 @@ admittance_controller: validation: gt<>: [0.0] + limit: + type: double_array + description: "specifies limit for x, y and z axis" + default_value: [0.0, 0.0, 0.0] + validation: + fixed_size<>: 3 + lower_element_bounds<>: -10.0 + upper_element_bounds<>: 10.0 + nested_dynamic: __map_joints: __map_dof_names: diff --git a/generate_parameter_library_py/generate_parameter_library_py/test/valid_parameters.yaml b/generate_parameter_library_py/generate_parameter_library_py/test/valid_parameters.yaml index fbbbb8d..571c09d 100644 --- a/generate_parameter_library_py/generate_parameter_library_py/test/valid_parameters.yaml +++ b/generate_parameter_library_py/generate_parameter_library_py/test/valid_parameters.yaml @@ -34,6 +34,16 @@ admittance_controller: type: double default_value: 1.0 description: "derivative gain term" + + limit: + type: double_array + description: "specifies limit for x, y and z axis" + default_value: [0.0, 0.0, 0.0] + validation: + fixed_size<>: 3 + lower_element_bounds<>: -10.0 + upper_element_bounds<>: 10.0 + gt_fifteen_lt_eq_twenty: type: int default_value: 20