Skip to content

Commit

Permalink
Add "additional_constraints" support (#221)
Browse files Browse the repository at this point in the history
  • Loading branch information
MrBlenny authored Oct 11, 2024
1 parent cbf4971 commit 4ad0857
Show file tree
Hide file tree
Showing 13 changed files with 105 additions and 48 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
**/__pycache__
build
install
log
*.egg-info
92 changes: 47 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,35 +175,37 @@ 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 ...
```

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<std::string>` |
| double_array | `std::vector<double>` |
| int_array | `std::vector<int>` |
| bool_array | `std::vector<bool>` |
| string_fixed_XX | `FixedSizeString<XX>` |
| none | NO CODE GENERATED |
| Parameter Type | C++ Type |
| --------------- | -------------------------- |
| string | `std::string` |
| double | `double` |
| int | `int` |
| bool | `bool` |
| string_array | `std::vector<std::string>` |
| double_array | `std::vector<double>` |
| int_array | `std::vector<int>` |
| bool_array | `std::vector<bool>` |
| string_fixed_XX | `FixedSizeString<XX>` |
| 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.
Expand Down Expand Up @@ -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<void, std::string>` type and accept a `rclcpp::Parameter const&` as their first argument and any number of arguments after that can be specified in YAML.
Expand Down
2 changes: 2 additions & 0 deletions example/src/parameters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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*',
Expand Down Expand Up @@ -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,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,9 @@

*Constraints:*
{{constraints}}

*Additional Constraints:*
{{additional_constraints}}

{% else %}
{% endif %}
Original file line number Diff line number Diff line change
@@ -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 %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ Constraints:

{% endif %}
{% endfilter -%}

{%- if additional_constraints|length %}
Additional Constraints:
{{additional_constraints}}
{% endif %}
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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,
}

Expand All @@ -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'
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand All @@ -708,6 +722,7 @@ def preprocess_inputs(language, name, value, nested_name_list):
description,
read_only,
validations,
additional_constraints,
)


Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 4ad0857

Please # to comment.