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

refactor: make ros2param use rclpy.parameter_client #716

Merged
merged 17 commits into from
Jul 22, 2022
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
225 changes: 55 additions & 170 deletions ros2param/ros2param/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,113 +13,44 @@
# limitations under the License.

import sys
import warnings

from rcl_interfaces.msg import Parameter
from rcl_interfaces.msg import ParameterType
from rcl_interfaces.msg import ParameterValue
from rcl_interfaces.srv import DescribeParameters
from rcl_interfaces.srv import GetParameters
from rcl_interfaces.srv import ListParameters
from rcl_interfaces.srv import SetParameters
import rclpy
from rclpy.parameter import PARAMETER_SEPARATOR_STRING
from rclpy.parameter import get_parameter_value as rclpy_get_parameter_value
from rclpy.parameter import parameter_dict_from_yaml_file
from rclpy.parameter import parameter_value_to_python
from rclpy.parameter_client import AsyncParameterClient
from ros2cli.node.direct import DirectNode

import yaml

def get_parameter_value(*, string_value):
ihasdapie marked this conversation as resolved.
Show resolved Hide resolved
warnings.warn('get_parameter_value() is deprecated. '
'Use rclpy.parameter.get_parameter_value instead')
return rclpy_get_parameter_value(string_value)


def get_value(*, parameter_value):
"""Get the value from a ParameterValue."""
if parameter_value.type == ParameterType.PARAMETER_BOOL:
value = parameter_value.bool_value
elif parameter_value.type == ParameterType.PARAMETER_INTEGER:
value = parameter_value.integer_value
elif parameter_value.type == ParameterType.PARAMETER_DOUBLE:
value = parameter_value.double_value
elif parameter_value.type == ParameterType.PARAMETER_STRING:
value = parameter_value.string_value
elif parameter_value.type == ParameterType.PARAMETER_BYTE_ARRAY:
value = list(parameter_value.byte_array_value)
elif parameter_value.type == ParameterType.PARAMETER_BOOL_ARRAY:
value = list(parameter_value.bool_array_value)
elif parameter_value.type == ParameterType.PARAMETER_INTEGER_ARRAY:
value = list(parameter_value.integer_array_value)
elif parameter_value.type == ParameterType.PARAMETER_DOUBLE_ARRAY:
value = list(parameter_value.double_array_value)
elif parameter_value.type == ParameterType.PARAMETER_STRING_ARRAY:
value = list(parameter_value.string_array_value)
elif parameter_value.type == ParameterType.PARAMETER_NOT_SET:
value = None
else:
value = None

return value


def get_parameter_value(*, string_value):
"""Guess the desired type of the parameter based on the string value."""
value = ParameterValue()
value = None
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
value = parameter_value_to_python(parameter_value)
except RuntimeError as e:
print(f'Runtime error {e} raised')
jacobperron marked this conversation as resolved.
Show resolved Hide resolved
return value


def parse_parameter_dict(*, namespace, parameter_dict):
parameters = []
for param_name, param_value in parameter_dict.items():
full_param_name = namespace + param_name
# Unroll nested parameters
if type(param_value) == dict:
parameters += parse_parameter_dict(
namespace=full_param_name + PARAMETER_SEPARATOR_STRING,
parameter_dict=param_value)
else:
parameter = Parameter()
parameter.name = full_param_name
parameter.value = get_parameter_value(string_value=str(param_value))
parameters.append(parameter)
return parameters


def load_parameter_dict(*, node, node_name, parameter_dict):

parameters = parse_parameter_dict(namespace='', parameter_dict=parameter_dict)
response = call_set_parameters(
node=node, node_name=node_name, parameters=parameters)

# output response
assert len(response.results) == len(parameters)
def load_parameter_file(*, node, node_name, parameter_file, use_wildcard):
client = AsyncParameterClient(node, node_name)
ready = client.wait_for_services(timeout_sec=5.0)
if not ready:
raise RuntimeError('Wait for service timed out waiting for '
f'parameter services for node {node_name}')
future = client.load_parameter_file(parameter_file, use_wildcard)
parameters = list(parameter_dict_from_yaml_file(parameter_file, use_wildcard).values())
rclpy.spin_until_future_complete(node, future)
response = future.result()
assert len(response.results) == len(parameters), 'Not all parameters set'
for i in range(0, len(response.results)):
result = response.results[i]
param_name = parameters[i].name
Expand All @@ -135,105 +66,59 @@ def load_parameter_dict(*, node, node_name, parameter_dict):
print(msg, file=sys.stderr)


def load_parameter_file(*, node, node_name, parameter_file, use_wildcard):
# Remove leading slash and namespaces
with open(parameter_file, 'r') as f:
param_file = yaml.safe_load(f)
param_keys = []
if use_wildcard and '/**' in param_file:
param_keys.append('/**')
if node_name in param_file:
param_keys.append(node_name)

if param_keys == []:
raise RuntimeError('Param file does not contain parameters for {}, '
' only for nodes: {}' .format(node_name, param_file.keys()))
param_dict = {}
for k in param_keys:
value = param_file[k]
if type(value) != dict or 'ros__parameters' not in value:
raise RuntimeError('Invalid structure of parameter file for node {}'
'expected same format as provided by ros2 param dump'
.format(k))
param_dict.update(value['ros__parameters'])
load_parameter_dict(node=node, node_name=node_name, parameter_dict=param_dict)


def call_describe_parameters(*, node, node_name, parameter_names=None):
# create client
client = node.create_client(
DescribeParameters, f'{node_name}/describe_parameters')

# call as soon as ready
ready = client.wait_for_service(timeout_sec=5.0)
client = AsyncParameterClient(node, node_name)
ready = client.wait_for_services(timeout_sec=5.0)
if not ready:
raise RuntimeError('Wait for service timed out')

request = DescribeParameters.Request()
if parameter_names:
request.names = parameter_names
future = client.call_async(request)
raise RuntimeError('Wait for service timed out waiting for '
f'parameter services for node {node_name}')
future = client.describe_parameters(parameter_names)
rclpy.spin_until_future_complete(node, future)

# handle response
response = future.result()
if response is None:
raise RuntimeError('Exception while calling service of node '
f'{node_name}: {future.exception()}')
return response


def call_get_parameters(*, node, node_name, parameter_names):
# create client
client = node.create_client(GetParameters, f'{node_name}/get_parameters')

# call as soon as ready
ready = client.wait_for_service(timeout_sec=5.0)
client = AsyncParameterClient(node, node_name)
ready = client.wait_for_services(timeout_sec=5.0)
if not ready:
raise RuntimeError('Wait for service timed out')

request = GetParameters.Request()
request.names = parameter_names
future = client.call_async(request)
raise RuntimeError('Wait for service timed out waiting for '
f'parameter services for node {node_name}')
future = client.get_parameters(parameter_names)
rclpy.spin_until_future_complete(node, future)

# handle response
response = future.result()
if response is None:
raise RuntimeError('Exception while calling service of node '
f'{node_name}: {future.exception()}')
return response


def call_set_parameters(*, node, node_name, parameters):
# create client
client = node.create_client(SetParameters, f'{node_name}/set_parameters')

# call as soon as ready
ready = client.wait_for_service(timeout_sec=5.0)
client = AsyncParameterClient(node, node_name)
ready = client.wait_for_services(timeout_sec=5.0)
if not ready:
raise RuntimeError('Wait for service timed out')

request = SetParameters.Request()
request.parameters = parameters
future = client.call_async(request)
raise RuntimeError('Wait for service timed out waiting for '
f'parameter services for node {node_name}')
future = client.set_parameters(parameters)
rclpy.spin_until_future_complete(node, future)

# handle response
response = future.result()
if response is None:
raise RuntimeError('Exception while calling service of node '
f'{node_name}: {future.exception()}')
return response


def call_list_parameters(*, node, node_name, prefix=None):
# create client
client = node.create_client(ListParameters, f'{node_name}/list_parameters')

# call as soon as ready
ready = client.wait_for_service(timeout_sec=5.0)
def call_list_parameters(*, node, node_name, prefixes=None):
client = AsyncParameterClient(node, node_name)
ready = client.wait_for_services(timeout_sec=5.0)
if not ready:
raise RuntimeError('Wait for service timed out')

request = ListParameters.Request()
future = client.call_async(request)
return None
future = client.list_parameters(prefixes=prefixes)
rclpy.spin_until_future_complete(node, future)

# handle response
response = future.result()
return response.result.names
return future


def get_parameter_type_string(parameter_type):
Expand Down
51 changes: 19 additions & 32 deletions ros2param/ros2param/verb/dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,19 @@
import os
import sys

from rcl_interfaces.srv import ListParameters

import rclpy
from rclpy.parameter import PARAMETER_SEPARATOR_STRING

from ros2cli.node.direct import DirectNode
from ros2cli.node.strategy import add_arguments
from ros2cli.node.strategy import NodeStrategy

from ros2node.api import get_absolute_node_name
from ros2node.api import get_node_names
from ros2node.api import NodeNameCompleter
from ros2node.api import parse_node_name

from ros2param.api import call_get_parameters
from ros2param.api import call_list_parameters
from ros2param.api import get_value
from ros2param.verb import VerbExtension

import yaml


Expand All @@ -57,17 +52,17 @@ def add_arguments(self, parser, cli_name): # noqa: D102
help='DEPRECATED: Does nothing.')

@staticmethod
def get_parameter_value(node, node_name, param):
def get_parameter_values(node, node_name, params):
response = call_get_parameters(
node=node, node_name=node_name,
parameter_names=[param])
parameter_names=params)

# requested parameter not set
if not response.values:
return '# Parameter not set'

# extract type specific value
return get_value(parameter_value=response.values[0])
return [get_value(parameter_value=i) for i in response.values]

def insert_dict(self, dictionary, key, value):
split = key.split(PARAMETER_SEPARATOR_STRING, 1)
Expand All @@ -94,36 +89,28 @@ def main(self, *, args): # noqa: D102
f"'{args.output_dir}' is not a valid directory.")

with DirectNode(args) as node:
# create client
service_name = f'{absolute_node_name}/list_parameters'
client = node.create_client(ListParameters, service_name)

client.wait_for_service()

if not client.service_is_ready():
raise RuntimeError(f"Could not reach service '{service_name}'")

request = ListParameters.Request()
future = client.call_async(request)

# wait for response
rclpy.spin_until_future_complete(node, future)

yaml_output = {node_name.full_name: {'ros__parameters': {}}}

# retrieve values
if future.result() is not None:
response = future.result()
for param_name in sorted(response.result.names):
pval = self.get_parameter_value(node, absolute_node_name, param_name)
self.insert_dict(
yaml_output[node_name.full_name]['ros__parameters'], param_name, pval)
else:
e = future.exception()
response = call_list_parameters(node=node, node_name=absolute_node_name)
if response is None:
raise RuntimeError(
'Wait for service timed out waiting for '
f'parameter services for node {node_name.full_name}')
elif response.result() is None:
e = response.exception()
raise RuntimeError(
'Exception while calling service of node '
f"'{node_name.full_name}': {e}")

response = response.result().result.names
response = sorted(response)
parameter_values = self.get_parameter_values(node, absolute_node_name, response)

for param_name, pval in zip(response, parameter_values):
self.insert_dict(
yaml_output[node_name.full_name]['ros__parameters'], param_name, pval)

if args.print:
print(
"WARNING: '--print' is deprecated; this utility prints to stdout by default",
Expand Down
Loading