diff --git a/.gitignore b/.gitignore index eeb8a6e..6ee9dbf 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ **/__pycache__ +build +install +log +*.egg-info diff --git a/README.md b/README.md index fff15ce..5d75d46 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,7 @@ cpp_namespace: type: int default_value: 3 read_only: true + additional_constraints: "{ type: 'number', multipleOf: 3 }" description: "A read-only integer parameter with a default value of 3" validation: # validation functions ... @@ -182,28 +183,29 @@ cpp_namespace: A parameter is a YAML dictionary with the only required key being `type`. -| Field | Description | -|---------------|---------------------------------------------------------------| -| type | The type (string, double, etc) of the parameter. | -| default_value | Value for the parameter if the user does not specify a value. | -| read_only | Can only be set at launch and are not dynamic. | -| description | Displayed by `ros2 param describe`. | -| validation | Dictionary of validation functions and their parameters. | +| Field | Description | +| ---------------------- | -------------------------------------------------------------------------------------------------------------- | +| type | The type (string, double, etc) of the parameter. | +| default_value | Value for the parameter if the user does not specify a value. | +| read_only | Can only be set at launch and are not dynamic. | +| description | Displayed by `ros2 param describe`. | +| validation | Dictionary of validation functions and their parameters. | +| additional_constraints | Additional constraints that end up on the ParameterDescriptor but are not used for validation by this package. | The types of parameters in ros2 map to C++ types. -| Parameter Type | C++ Type | -|-----------------|-----------------------------| -| string | `std::string` | -| double | `double` | -| int | `int` | -| bool | `bool` | -| string_array | `std::vector` | -| double_array | `std::vector` | -| int_array | `std::vector` | -| bool_array | `std::vector` | -| string_fixed_XX | `FixedSizeString` | -| none | NO CODE GENERATED | +| Parameter Type | C++ Type | +| --------------- | -------------------------- | +| string | `std::string` | +| double | `double` | +| int | `int` | +| bool | `bool` | +| string_array | `std::vector` | +| double_array | `std::vector` | +| int_array | `std::vector` | +| bool_array | `std::vector` | +| string_fixed_XX | `FixedSizeString` | +| none | NO CODE GENERATED | Fixed-size types are denoted with a suffix `_fixed_XX`, where `XX` is the desired size. The corresponding C++ type is a data wrapper class for conveniently accessing the data. @@ -240,36 +242,36 @@ Some of these validators work only on value types, some on string types, and oth The built-in validator functions provided by this package are: **Value validators** -| Function | Arguments | Description | -|------------------------|---------------------|---------------------------------------| -| bounds<> | [lower, upper] | Bounds checking (inclusive) | -| lt<> | [value] | parameter < value | -| gt<> | [value] | parameter > value | -| lt_eq<> | [value] | parameter <= value | -| gt_eq<> | [value] | parameter >= value | -| one_of<> | [[val1, val2, ...]] | Value is one of the specified values | +| Function | Arguments | Description | +| -------- | ------------------- | ------------------------------------ | +| bounds<> | [lower, upper] | Bounds checking (inclusive) | +| lt<> | [value] | parameter < value | +| gt<> | [value] | parameter > value | +| lt_eq<> | [value] | parameter <= value | +| gt_eq<> | [value] | parameter >= value | +| one_of<> | [[val1, val2, ...]] | Value is one of the specified values | **String validators** -| Function | Arguments | Description | -|------------------------|---------------------|-------------------------------------------------| -| fixed_size<> | [length] | Length string is specified length | -| size_gt<> | [length] | Length string is greater than specified length | -| size_lt<> | [length] | Length string is less less specified length | -| not_empty<> | [] | String parameter is not empty | -| one_of<> | [[val1, val2, ...]] | String is one of the specified values | +| Function | Arguments | Description | +| ------------ | ------------------- | ---------------------------------------------- | +| fixed_size<> | [length] | Length string is specified length | +| size_gt<> | [length] | Length string is greater than specified length | +| size_lt<> | [length] | Length string is less less specified length | +| not_empty<> | [] | String parameter is not empty | +| one_of<> | [[val1, val2, ...]] | String is one of the specified values | **Array validators** -| Function | Arguments | Description | -|------------------------|---------------------|------------------------------------------------------| -| unique<> | [] | Contains no duplicates | -| subset_of<> | [[val1, val2, ...]] | Every element is one of the list | -| fixed_size<> | [length] | Number of elements is specified length | -| size_gt<> | [length] | Number of elements is greater than specified length | -| size_lt<> | [length] | Number of elements is less less specified length | -| not_empty<> | [] | Has at-least one element | -| element_bounds<> | [lower, upper] | Bounds checking each element (inclusive) | -| lower_element_bounds<> | [lower] | Lower bound for each element (inclusive) | -| upper_element_bounds<> | [upper] | Upper bound for each element (inclusive) | +| Function | Arguments | Description | +| ---------------------- | ------------------- | --------------------------------------------------- | +| unique<> | [] | Contains no duplicates | +| subset_of<> | [[val1, val2, ...]] | Every element is one of the list | +| fixed_size<> | [length] | Number of elements is specified length | +| size_gt<> | [length] | Number of elements is greater than specified length | +| size_lt<> | [length] | Number of elements is less less specified length | +| not_empty<> | [] | Has at-least one element | +| element_bounds<> | [lower, upper] | Bounds checking each element (inclusive) | +| lower_element_bounds<> | [lower] | Lower bound for each element (inclusive) | +| upper_element_bounds<> | [upper] | Upper bound for each element (inclusive) | ### 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. diff --git a/example/src/parameters.yaml b/example/src/parameters.yaml index c42f5eb..7836c8e 100644 --- a/example/src/parameters.yaml +++ b/example/src/parameters.yaml @@ -3,6 +3,7 @@ admittance_controller: type: double default_value: 0.00000000001 description: "Test scientific notation" + additional_constraints: "Any string can be here. For example, you might want to embed JSON schema" interpolation_mode: type: string default_value: "spline" @@ -89,6 +90,7 @@ admittance_controller: command_interfaces: type: string_array description: "specifies which command interfaces to claim" + additional_constraints: "some additional constraints" read_only: true state_interfaces: diff --git a/example_python/generate_parameter_module_example/parameters.yaml b/example_python/generate_parameter_module_example/parameters.yaml index 0a6fded..d6d8f1b 100644 --- a/example_python/generate_parameter_module_example/parameters.yaml +++ b/example_python/generate_parameter_module_example/parameters.yaml @@ -24,6 +24,7 @@ admittance_controller: type: string_array default_value: ["x", "y", "rz"] description: "specifies which joints will be used by the controller" + additional_constraints: "Any string can be here. For example, you might want to embed JSON schema" __map_joints: __map_dof_names: diff --git a/generate_parameter_library_py/generate_parameter_library_py/generate_markdown.py b/generate_parameter_library_py/generate_parameter_library_py/generate_markdown.py index fcfef4b..084b3ae 100644 --- a/generate_parameter_library_py/generate_parameter_library_py/generate_markdown.py +++ b/generate_parameter_library_py/generate_parameter_library_py/generate_markdown.py @@ -103,6 +103,7 @@ def __str__(self): 'type': self.declare_parameters.code_gen_variable.defined_type, 'default_value': self.declare_parameters.code_gen_variable.lang_str_value, 'constraints': constraints, + 'additional_constraints': self.declare_parameters.parameter_additional_constraints, # remove leading whitespace from description, this is necessary for correct indentation of multi-line descriptions 'description': re.sub( r'(?m)^(?!$)\s*', @@ -139,6 +140,7 @@ def __str__(self): 'type': self.declare_parameters.code_gen_variable.defined_type, 'default_value': self.declare_parameters.code_gen_variable.lang_str_value, 'constraints': constraints, + 'additional_constraints': self.declare_parameters.parameter_additional_constraints, 'description': self.declare_parameters.parameter_description, } 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 ec57f65..51e4fb4 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 @@ -3,6 +3,9 @@ if (!parameters_interface_->has_parameter(prefix_ + "{{parameter_name}}")) { rcl_interfaces::msg::ParameterDescriptor descriptor; descriptor.description = {{parameter_description | valid_string_cpp}}; 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 %} {%- if validation.arguments|length == 2 %} 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 cc8246e..625a842 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 @@ -19,6 +19,9 @@ if (!parameters_interface_->has_parameter(param_name)) { rcl_interfaces::msg::ParameterDescriptor descriptor; descriptor.description = {{parameter_description | valid_string_cpp}}; 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 %} {%- if validation.arguments|length == 2 %} diff --git a/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/markdown/parameter_detail b/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/markdown/parameter_detail index 1f4a544..04bcf3c 100644 --- a/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/markdown/parameter_detail +++ b/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/markdown/parameter_detail @@ -9,5 +9,9 @@ *Constraints:* {{constraints}} + +*Additional Constraints:* +{{additional_constraints}} + {% else %} {% 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 47179f2..8ae0e2d 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 @@ -1,6 +1,9 @@ if not self.node_.has_parameter(self.prefix_ + "{{parameter_name}}"): {%- filter indent(width=4) %} descriptor = ParameterDescriptor(description="{{parameter_description|valid_string_python}}", read_only = {{parameter_read_only}}) +{%- 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 %} {%- if validation.arguments|length == 2 %} 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 96c48f7..dbd9d4b 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 @@ -16,6 +16,9 @@ param_name = f"{self.prefix_}{% for map in parameter_map%}{value_{{loop.index}}} if not self.node_.has_parameter(self.prefix_ + param_name): {%- filter indent(width=4) %} descriptor = ParameterDescriptor(description="{{parameter_description|valid_string_python}}", read_only = {{parameter_read_only}}) +{%- 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 %} {%- if validation.arguments|length == 2 %} diff --git a/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/rst/parameter_detail b/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/rst/parameter_detail index cc87455..626b1e3 100644 --- a/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/rst/parameter_detail +++ b/generate_parameter_library_py/generate_parameter_library_py/jinja_templates/rst/parameter_detail @@ -17,3 +17,8 @@ Constraints: {% endif %} {% endfilter -%} + +{%- if additional_constraints|length %} +Additional Constraints: +{{additional_constraints}} +{% 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 7bb1fec..bc7b020 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 @@ -495,12 +495,14 @@ def __init__( parameter_description: str, parameter_read_only: bool, parameter_validations: list, + parameter_additional_constraints: str, ): self.parameter_name = code_gen_variable.param_name self.parameter_description = parameter_description self.parameter_read_only = parameter_read_only self.parameter_validations = parameter_validations self.code_gen_variable = code_gen_variable + self.parameter_additional_constraints = parameter_additional_constraints class DeclareParameter(DeclareParameterBase): @@ -519,6 +521,7 @@ def __str__(self): 'parameter_type': self.code_gen_variable.get_parameter_type(), 'parameter_description': self.parameter_description, 'parameter_read_only': bool_to_str(self.parameter_read_only), + 'parameter_additional_constraints': self.parameter_additional_constraints, 'parameter_validations': parameter_validations, } @@ -538,12 +541,14 @@ def __init__( parameter_description: str, parameter_read_only: bool, parameter_validations: list, + parameter_additional_constraints: str, ): super().__init__( code_gen_variable, parameter_description, parameter_read_only, parameter_validations, + parameter_additional_constraints, ) self.set_runtime_parameter = None self.param_struct_instance = 'updated_params' @@ -576,6 +581,7 @@ def __str__(self): 'parameter_description': self.parameter_description, 'parameter_read_only': bool_to_str(self.parameter_read_only), 'parameter_as_function': self.code_gen_variable.parameter_as_function_str(), + 'parameter_additional_constraints': self.parameter_additional_constraints, 'mapped_params': mapped_params, 'mapped_param_underscore': [val.replace('.', '_') for val in mapped_params], 'set_runtime_parameter': self.set_runtime_parameter, @@ -671,7 +677,14 @@ def preprocess_inputs(language, name, value, nested_name_list): raise compile_error('No type defined for parameter %s' % param_name) # check for invalid syntax - valid_keys = {'default_value', 'description', 'read_only', 'validation', 'type'} + valid_keys = { + 'default_value', + 'description', + 'read_only', + 'additional_constraints', + 'validation', + 'type', + } invalid_keys = value.keys() - valid_keys if len(invalid_keys) > 0: raise compile_error( @@ -693,6 +706,7 @@ def preprocess_inputs(language, name, value, nested_name_list): description = value.get('description', '') read_only = bool(value.get('read_only', False)) validations = [] + additional_constraints = value.get('additional_constraints', '') validations_dict = value.get('validation', {}) if is_fixed_type(defined_type): validations_dict['size_lt<>'] = fixed_type_size(defined_type) + 1 @@ -708,6 +722,7 @@ def preprocess_inputs(language, name, value, nested_name_list): description, read_only, validations, + additional_constraints, ) @@ -767,6 +782,7 @@ def parse_params(self, name, value, nested_name_list): description, read_only, validations, + additional_constraints, ) = preprocess_inputs(self.language, name, value, nested_name_list) # skip accepted params that do not generate code if code_gen_variable.lang_type is None: @@ -795,13 +811,21 @@ def parse_params(self, name, value, nested_name_list): if is_runtime_parameter: declare_parameter_set = SetRuntimeParameter(param_name, code_gen_variable) declare_parameter = DeclareRuntimeParameter( - code_gen_variable, description, read_only, validations + code_gen_variable, + description, + read_only, + validations, + additional_constraints, ) declare_parameter.add_set_runtime_parameter(declare_parameter_set) update_parameter = UpdateRuntimeParameter(param_name, code_gen_variable) else: declare_parameter = DeclareParameter( - code_gen_variable, description, read_only, validations + code_gen_variable, + description, + read_only, + validations, + additional_constraints, ) declare_parameter_set = SetParameter(param_name, code_gen_variable) update_parameter = UpdateParameter(param_name, code_gen_variable) 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 9046d8c..6142a45 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 @@ -48,6 +48,7 @@ admittance_controller: command_interfaces: type: string_array description: "specifies which command interfaces to claim" + additional_constraints: "cmd1 | cmd2 | cmd3" read_only: true state_interfaces: