Skip to content
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
25 changes: 25 additions & 0 deletions rclpy/rclpy/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,21 @@ def __init__(self, parameter, *args):
Exception.__init__(self, 'Invalid parameter name', parameter, *args)


class InvalidParameterTypeException(ParameterException):
"""Raised when a parameter is rejected for having an invalid type."""

def __init__(self, desired_parameter, expected_type, *args):
from rclpy.parameter import Parameter
Exception.__init__(
self,
f"Trying to set parameter '{desired_parameter._name}' to '{desired_parameter._value}'"
f" of type '{Parameter.Type.from_parameter_value(desired_parameter._value).name}'"
f", expecting type '{expected_type}'",
*args)
self._actual_type = desired_parameter.type_
self._param_name = desired_parameter.name


class InvalidParameterValueException(ParameterException):
"""Raised when a parameter is rejected by a user callback or when applying a descriptor."""

Expand All @@ -112,6 +127,16 @@ def __init__(self, parameter, *args):
Exception.__init__(self, 'Attempted to modify read-only parameter', parameter, *args)


class NoParameterOverrideProvidedException(ParameterException):
"""Raised when no override is provided for a statically typed parameter with no default."""

def __init__(self, parameter_name, *args):
Exception.__init__(
self,
f"No parameter override provided for '{parameter_name}' when one was expected",
*args)


class ROSInterruptException(Exception):
"""Raised when an operation is canceled by rclpy shutting down."""

Expand Down
118 changes: 82 additions & 36 deletions rclpy/rclpy/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@
from typing import TypeVar
from typing import Union

import warnings
import weakref

from rcl_interfaces.msg import FloatingPointRange
from rcl_interfaces.msg import IntegerRange
from rcl_interfaces.msg import Parameter as ParameterMsg
from rcl_interfaces.msg import ParameterDescriptor
from rcl_interfaces.msg import ParameterEvent
from rcl_interfaces.msg import ParameterType
from rcl_interfaces.msg import ParameterValue
from rcl_interfaces.msg import SetParametersResult

Expand All @@ -43,8 +45,10 @@
from rclpy.clock import ROSClock
from rclpy.constants import S_TO_NS
from rclpy.context import Context
from rclpy.exceptions import InvalidParameterTypeException
from rclpy.exceptions import InvalidParameterValueException
from rclpy.exceptions import InvalidTopicNameException
from rclpy.exceptions import NoParameterOverrideProvidedException
from rclpy.exceptions import NotInitializedException
from rclpy.exceptions import ParameterAlreadyDeclaredException
from rclpy.exceptions import ParameterImmutableException
Expand Down Expand Up @@ -328,7 +332,7 @@ def declare_parameter(
self,
name: str,
value: Any = None,
descriptor: ParameterDescriptor = ParameterDescriptor(),
descriptor: Optional[ParameterDescriptor] = None,
ignore_override: bool = False
) -> Parameter:
"""
Expand All @@ -346,13 +350,20 @@ def declare_parameter(
:raises: InvalidParameterException if the parameter name is invalid.
:raises: InvalidParameterValueException if the registered callback rejects the parameter.
"""
return self.declare_parameters('', [(name, value, descriptor)], ignore_override)[0]
if value is None and descriptor is None:
# Temporal patch so we get deprecation warning if only a name is provided.
args = (name, )
else:
descriptor = ParameterDescriptor() if descriptor is None else descriptor
args = (name, value, descriptor)
return self.declare_parameters('', [args], ignore_override)[0]

def declare_parameters(
self,
namespace: str,
parameters: List[Union[
Tuple[str],
Tuple[str, Parameter.Type],
Tuple[str, Any],
Tuple[str, Any, ParameterDescriptor],
]],
Expand Down Expand Up @@ -401,42 +412,66 @@ def declare_parameters(
)

value = None
descriptor = ParameterDescriptor()
param_type = None

# Get the values from the tuple, checking its types.
# Use defaults if the tuple doesn't contain value and / or descriptor.
try:
name = parameter_tuple[0]
assert \
isinstance(name, str), \
(
'First element {name} at index {index} in parameters list '
'is not a str.'.format_map(locals())
)

# Get value from parameter overrides, of from tuple if it doesn't exist.
if not ignore_override and name in self._parameter_overrides:
value = self._parameter_overrides[name].value
else:
# This raises a TypeError if it's not possible to get a type from the tuple.
value = parameter_tuple[1]

# Get descriptor from tuple.
descriptor = parameter_tuple[2]
assert \
isinstance(descriptor, ParameterDescriptor), \
(
'Third element {descriptor} at index {index} in parameters list '
'is not a ParameterDescriptor.'.format_map(locals())
)
except AssertionError as assertion_error:
raise TypeError(assertion_error)
except IndexError:
# This means either value or descriptor were not defined which is fine.
pass
name = parameter_tuple[0]
second_arg = parameter_tuple[1] if 1 < len(parameter_tuple) else None
descriptor = parameter_tuple[2] if 2 < len(parameter_tuple) else ParameterDescriptor()

if not isinstance(name, str):
raise TypeError(
f'First element {name} at index {index} in parameters list '
'is not a str.')
if not isinstance(descriptor, ParameterDescriptor):
raise TypeError(
f'Third element {descriptor} at index {index} in parameters list '
'is not a ParameterDescriptor.'
)

if len(parameter_tuple) == 1:
warnings.warn(
f"when declaring parmater named '{name}', "
'declaring a parameter only providing its name is deprecated. '
'You have to either:\n'
'\t- Pass a name and a default value different to "PARAMETER NOT SET"'
' (and optionally a descriptor).\n'
'\t- Pass a name and a parameter type.\n'
'\t- Pass a name and a descriptor with `dynamic_typing=True')
descriptor.dynamic_typing = True

if isinstance(second_arg, Parameter.Type):
if second_arg.value == Parameter.Type.NOT_SET:
raise ValueError(
f'Cannot declare parameter {{{name}}} as statically typed of type NOT_SET')
if descriptor.dynamic_typing is True:
raise ValueError(
f'When declaring parameter {{{name}}} passing a descriptor with'
'`dynamic_typing=True` is not allowed when the parameter type is provided')
descriptor.type = second_arg.value
else:
value = second_arg
if not descriptor.dynamic_typing and value is not None:
# infer type from default value
if not isinstance(value, ParameterValue):
descriptor.type = Parameter.Type.from_parameter_value(value).value
else:
if value.type == ParameterType.PARAMETER_NOT_SET:
raise ValueError(
'Cannot declare a statically typed parameter with default value '
'of type PARAMETER_NOT_SET')
descriptor.type = value.type

# Get value from parameter overrides, of from tuple if it doesn't exist.
if not ignore_override and name in self._parameter_overrides:
value = self._parameter_overrides[name].value

if value is None and not descriptor.dynamic_typing:
raise NoParameterOverrideProvidedException(name)

if namespace:
name = '{namespace}.{name}'.format_map(locals())
name = f'{namespace}.{name}'

# Note(jubeira): declare_parameters verifies the name, but set_parameters doesn't.
validate_parameter_name(name)
Expand Down Expand Up @@ -643,6 +678,9 @@ def _set_parameters(
allow_not_set_type=allow_undeclared_parameters
)
if raise_on_failure and not result.successful:
if result.reason.startswith('Wrong parameter type'):
raise InvalidParameterTypeException(
param, Parameter.Type(descriptors[param._name].type).name)
raise InvalidParameterValueException(param.name, param.value, result.reason)
results.append(result)
return results
Expand Down Expand Up @@ -858,14 +896,22 @@ def _apply_descriptor(
else:
descriptor.name = parameter.name

# The type in the descriptor has to match the type of the parameter.
descriptor.type = parameter.type_.value

if check_read_only and descriptor.read_only:
return SetParametersResult(
successful=False,
reason='Trying to set a read-only parameter: {}.'.format(parameter.name))

if descriptor.dynamic_typing or self._allow_undeclared_parameters:
descriptor.type = parameter.type_.value
elif descriptor.type != parameter.type_.value:
return SetParametersResult(
successful=False,
reason=(
'Wrong parameter type, expected '
f"'{Parameter.Type(descriptor.type)}'"
f" got '{parameter.type_}'")
)

if parameter.type_ == Parameter.Type.INTEGER and descriptor.integer_range:
return self._apply_integer_range(parameter, descriptor.integer_range[0])

Expand Down
2 changes: 1 addition & 1 deletion rclpy/test/resources/test_node/test_parameters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
initial_foobar: False
/my_ns/my_node:
ros__parameters:
initial_fizz: 23
initial_fizz: param_file_override
Loading