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

implement parameter_client #959

Merged
merged 34 commits into from
Jun 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
abb82d7
Misc. typing fixes
ihasdapie May 20, 2022
6e435e6
Start AsyncParameterClient class
ihasdapie May 20, 2022
8b5d2d9
core parameter_client functionality ported
ihasdapie May 20, 2022
4d0b44d
add service_is_ready method
ihasdapie May 24, 2022
39dd54d
allow for listing all parameters
ihasdapie May 24, 2022
ebc4481
synchronus client set/get/list
ihasdapie May 24, 2022
aec10c5
use Parameter message type instead of rclpy.Parameter to align with r…
ihasdapie May 24, 2022
e79afbb
move get_parameter_value from ros2param to rclpy
ihasdapie May 25, 2022
852e3a8
load parameters from file
ihasdapie May 25, 2022
f1731e3
add parameter_client to docs
ihasdapie May 27, 2022
5d56b1f
address pr comments, docs, lint
ihasdapie May 27, 2022
591cffa
add tests for parameter client
ihasdapie Jun 1, 2022
6fab6bf
prepend underscore for private members
ihasdapie Jun 1, 2022
79ac7fe
squash linting complaints
ihasdapie Jun 1, 2022
be6e43d
revert typing changes
ihasdapie Jun 3, 2022
40acf32
address review comments
ihasdapie Jun 3, 2022
83be7d3
add test to parameter_dict_from_yaml for nonexistent file
ihasdapie Jun 3, 2022
4870ade
allow for set_parameter to take both rclpy Parameter and rcl_interfac…
ihasdapie Jun 3, 2022
ff8a56e
more consistent naming for functions
ihasdapie Jun 3, 2022
b7556f6
nicer list comprehension
ihasdapie Jun 3, 2022
b90edf0
name and type in descriptor(s) is ignored via declare_parameter(s). (…
fujitatomoya Jun 15, 2022
f815c6c
Revert "implement parameter_client" (#958)
ihasdapie Jun 15, 2022
cf72595
Revert "Revert "implement parameter_client" (#958)"
ihasdapie Jun 15, 2022
9e2856b
fix load_parameter_dict_from_yaml test failure on windows
ihasdapie Jun 15, 2022
fa20da3
make list_parameter default depth unlimited
ihasdapie Jun 15, 2022
00c5f4a
fix: namespaced parameters take precedence over wildcard
ihasdapie Jun 15, 2022
c883a07
leaner implementation of wait_for_services
ihasdapie Jun 16, 2022
0c32ba1
fix linting
ihasdapie Jun 17, 2022
fb21b66
address pr comments
ihasdapie Jun 17, 2022
214ab92
don't reference ros2 param dump
ihasdapie Jun 17, 2022
d6dd191
Update rclpy/rclpy/parameter.py
ihasdapie Jun 20, 2022
bbf43c4
flake8
ihasdapie Jun 21, 2022
fbf5959
add load_parameter_file_atomically
ihasdapie Jun 23, 2022
ddd40f3
address PR comments
ihasdapie Jun 27, 2022
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
5 changes: 5 additions & 0 deletions rclpy/docs/source/api/parameters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@ Parameter Service
-----------------

.. automodule:: rclpy.parameter_service

Parameter Client
-----------------

.. automodule:: rclpy.parameter_client
1 change: 1 addition & 0 deletions rclpy/rclpy/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ def wait_for_service(self, timeout_sec: float = None) -> bool:
"""
# TODO(sloretz) Return as soon as the service is available
# This is a temporary implementation. The sleep time is arbitrary.
# https://github.com/ros2/rclpy/issues/58
sleep_time = 0.25
if timeout_sec is None:
timeout_sec = float('inf')
Expand Down
5 changes: 5 additions & 0 deletions rclpy/rclpy/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,9 @@ def declare_parameter(
This method, if successful, will result in any callback registered with
:func:`add_on_set_parameters_callback` to be called.

The name and type in the given descriptor is ignored, and should be specified using
the name argument to this function and the default value's type instead.
jacobperron marked this conversation as resolved.
Show resolved Hide resolved

:param name: Fully-qualified name of the parameter, including its namespace.
:param value: Value of the parameter to declare.
:param descriptor: Descriptor for the parameter to declare.
Expand Down Expand Up @@ -381,6 +384,8 @@ def declare_parameters(

The tuples in the given parameter list shall contain the name for each parameter,
optionally providing a value and a descriptor.
The name and type in the given descriptors are ignored, and should be specified using
the name argument to this function and the default value's type instead.
For each entry in the list, a parameter with a name of "namespace.name"
will be declared.
The resulting value for each declared parameter will be returned, considering
Expand Down
127 changes: 126 additions & 1 deletion rclpy/rclpy/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@

import array
from enum import Enum
from typing import Dict
from typing import List
from typing import Optional

from rcl_interfaces.msg import Parameter as ParameterMsg
from rcl_interfaces.msg import ParameterType, ParameterValue
from rcl_interfaces.msg import ParameterType
from rcl_interfaces.msg import ParameterValue
import yaml

PARAMETER_SEPARATOR_STRING = '.'

Expand Down Expand Up @@ -177,6 +182,50 @@ def to_parameter_msg(self):
return ParameterMsg(name=self.name, value=self.get_parameter_value())


def get_parameter_value(string_value: str) -> ParameterValue:
"""
Guess the desired type of the parameter based on the string value.

:param string_value: The string value to be converted to a ParameterValue.
:return: The ParameterValue.
"""
value = ParameterValue()
try:
yaml_value = yaml.safe_load(string_value)
except yaml.parser.ParserError:
yaml_value = string_value

if isinstance(yaml_value, bool):
value.type = ParameterType.PARAMETER_BOOL
value.bool_value = yaml_value
elif isinstance(yaml_value, int):
value.type = ParameterType.PARAMETER_INTEGER
value.integer_value = yaml_value
elif isinstance(yaml_value, float):
value.type = ParameterType.PARAMETER_DOUBLE
value.double_value = yaml_value
elif isinstance(yaml_value, list):
if all((isinstance(v, bool) for v in yaml_value)):
value.type = ParameterType.PARAMETER_BOOL_ARRAY
value.bool_array_value = yaml_value
elif all((isinstance(v, int) for v in yaml_value)):
value.type = ParameterType.PARAMETER_INTEGER_ARRAY
value.integer_array_value = yaml_value
elif all((isinstance(v, float) for v in yaml_value)):
value.type = ParameterType.PARAMETER_DOUBLE_ARRAY
value.double_array_value = yaml_value
elif all((isinstance(v, str) for v in yaml_value)):
value.type = ParameterType.PARAMETER_STRING_ARRAY
value.string_array_value = yaml_value
else:
value.type = ParameterType.PARAMETER_STRING
value.string_value = string_value
else:
value.type = ParameterType.PARAMETER_STRING
value.string_value = yaml_value
return value


def parameter_value_to_python(parameter_value: ParameterValue):
"""
Get the value for the Python builtin type from a rcl_interfaces/msg/ParameterValue object.
Expand Down Expand Up @@ -211,3 +260,79 @@ def parameter_value_to_python(parameter_value: ParameterValue):
raise RuntimeError(f'unexpected parameter type {parameter_value.type}')

return value


def parameter_dict_from_yaml_file(
parameter_file: str,
use_wildcard: bool = False,
target_nodes: Optional[List[str]] = None,
namespace: str = ''
) -> Dict[str, ParameterMsg]:
"""
Build a dict of parameters from a YAML file.

Will load all parameters if ``target_nodes`` is None or empty.

:raises RuntimeError: if a target node is not in the file
:raises RuntimeError: if the is not a valid ROS parameter file

:param parameter_file: Path to the YAML file to load parameters from.
:param use_wildcard: Use wildcard matching for the target nodes.
:param target_nodes: List of nodes in the YAML file to load parameters from.
:param namespace: Namespace to prepend to all parameters.
:return: A dict of Parameter messages keyed by the parameter names
"""
ihasdapie marked this conversation as resolved.
Show resolved Hide resolved
with open(parameter_file, 'r') as f:
param_file = yaml.safe_load(f)
param_keys = []
param_dict = {}

if use_wildcard and '/**' in param_file:
param_keys.append('/**')

if target_nodes:
for n in target_nodes:
if n not in param_file.keys():
raise RuntimeError(f'Param file does not contain parameters for {n},'
f'only for nodes: {list(param_file.keys())} ')
param_keys.append(n)
else:
# wildcard key must go to the front of param_keys so that
# node-namespaced parameters will override the wildcard parameters
keys = set(param_file.keys())
keys.discard('/**')
param_keys.extend(keys)

if len(param_keys) == 0:
raise RuntimeError('Param file does not contain selected parameters')

for n in param_keys:
value = param_file[n]
if type(value) != dict or 'ros__parameters' not in value:
raise RuntimeError(f'YAML file is not a valid ROS parameter file for node {n}')
param_dict.update(value['ros__parameters'])
return _unpack_parameter_dict(namespace, param_dict)


def _unpack_parameter_dict(namespace, parameter_dict):
"""
Flatten a parameter dictionary recursively.

:param namespace: The namespace to prepend to the parameter names.
:param parameter_dict: A dictionary of parameters keyed by the parameter names
:return: A dict of Parameter objects keyed by the parameter names
"""
parameters: Dict[str, ParameterMsg] = {}
for param_name, param_value in parameter_dict.items():
full_param_name = namespace + param_name
# Unroll nested parameters
if type(param_value) == dict:
parameters.update(_unpack_parameter_dict(
namespace=full_param_name + PARAMETER_SEPARATOR_STRING,
parameter_dict=param_value))
else:
parameter = ParameterMsg()
parameter.name = full_param_name
parameter.value = get_parameter_value(str(param_value))
parameters[full_param_name] = parameter
return parameters
Loading