Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "additional_constraints" support #221

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading