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 node parameters and parameter services #214
Changes from all commits
0f7c170
9dc54a8
1a2a4aa
8adc3d6
7350e26
869db6c
a8231e0
e6c7125
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
# Copyright 2018 Open Source Robotics Foundation, Inc. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
from enum import Enum | ||
|
||
from rcl_interfaces.msg import ParameterDescriptor, ParameterType, ParameterValue | ||
|
||
PARAMETER_SEPARATOR_STRING = '.' | ||
|
||
|
||
class Parameter: | ||
|
||
class Type(Enum): | ||
NOT_SET = ParameterType.PARAMETER_NOT_SET | ||
BOOL = ParameterType.PARAMETER_BOOL | ||
INTEGER = ParameterType.PARAMETER_INTEGER | ||
DOUBLE = ParameterType.PARAMETER_DOUBLE | ||
STRING = ParameterType.PARAMETER_STRING | ||
BYTE_ARRAY = ParameterType.PARAMETER_BYTE_ARRAY | ||
BOOL_ARRAY = ParameterType.PARAMETER_BOOL_ARRAY | ||
INTEGER_ARRAY = ParameterType.PARAMETER_INTEGER_ARRAY | ||
DOUBLE_ARRAY = ParameterType.PARAMETER_DOUBLE_ARRAY | ||
STRING_ARRAY = ParameterType.PARAMETER_STRING_ARRAY | ||
|
||
def check(self, parameter_value): | ||
if Parameter.Type.NOT_SET == self: | ||
return parameter_value is None | ||
if Parameter.Type.BOOL == self: | ||
return isinstance(parameter_value, bool) | ||
if Parameter.Type.INTEGER == self: | ||
return isinstance(parameter_value, int) | ||
if Parameter.Type.DOUBLE == self: | ||
return isinstance(parameter_value, float) | ||
if Parameter.Type.STRING == self: | ||
return isinstance(parameter_value, str) | ||
if Parameter.Type.BYTE_ARRAY == self: | ||
return isinstance(parameter_value, list) and \ | ||
all(isinstance(v, bytes) and len(v) == 1 for v in parameter_value) | ||
if Parameter.Type.BOOL_ARRAY == self: | ||
return isinstance(parameter_value, list) and \ | ||
all(isinstance(v, bool) for v in parameter_value) | ||
if Parameter.Type.INTEGER_ARRAY == self: | ||
return isinstance(parameter_value, list) and \ | ||
all(isinstance(v, int) for v in parameter_value) | ||
if Parameter.Type.DOUBLE_ARRAY == self: | ||
return isinstance(parameter_value, list) and \ | ||
all(isinstance(v, float) for v in parameter_value) | ||
if Parameter.Type.STRING_ARRAY == self: | ||
return isinstance(parameter_value, list) and \ | ||
all(isinstance(v, str) for v in parameter_value) | ||
return False | ||
|
||
@classmethod | ||
def from_rcl_interface_parameter(cls, rcl_param): | ||
value = None | ||
type_ = Parameter.Type(value=rcl_param.value.type) | ||
if Parameter.Type.BOOL == type_: | ||
value = rcl_param.value.bool_value | ||
elif Parameter.Type.INTEGER == type_: | ||
value = rcl_param.value.integer_value | ||
elif Parameter.Type.DOUBLE == type_: | ||
value = rcl_param.value.double_value | ||
elif Parameter.Type.STRING == type_: | ||
value = rcl_param.value.string_value | ||
elif Parameter.Type.BYTE_ARRAY == type_: | ||
value = rcl_param.value.byte_array_value | ||
elif Parameter.Type.BOOL_ARRAY == type_: | ||
value = rcl_param.value.bool_array_value | ||
elif Parameter.Type.INTEGER_ARRAY == type_: | ||
value = rcl_param.value.integer_array_value | ||
elif Parameter.Type.DOUBLE_ARRAY == type_: | ||
value = rcl_param.value.double_array_value | ||
elif Parameter.Type.STRING_ARRAY == type_: | ||
value = rcl_param.value.string_array_value | ||
return cls(rcl_param.name, type_, value) | ||
|
||
def __init__(self, name, type_, value=None): | ||
if not isinstance(type_, Parameter.Type): | ||
raise TypeError("type must be an instance of '{}'".format(repr(Parameter.Type))) | ||
|
||
if not type_.check(value): | ||
raise ValueError("Type '{}' and value '{}' do not agree".format(type_, value)) | ||
|
||
self._type_ = type_ | ||
self._name = name | ||
self._value = value | ||
|
||
@property | ||
def name(self): | ||
return self._name | ||
|
||
@property | ||
def type_(self): | ||
return self._type_ | ||
|
||
@property | ||
def value(self): | ||
return self._value | ||
|
||
def get_descriptor(self): | ||
return ParameterDescriptor(name=self.name, type=self.type_.value) | ||
|
||
def get_parameter_value(self): | ||
parameter_value = ParameterValue(type=self.type_.value) | ||
if Parameter.Type.BOOL == self.type_: | ||
parameter_value.bool_value = self.value | ||
elif Parameter.Type.INTEGER == self.type_: | ||
parameter_value.integer_value = self.value | ||
elif Parameter.Type.DOUBLE == self.type_: | ||
parameter_value.double_value = self.value | ||
elif Parameter.Type.STRING == self.type_: | ||
parameter_value.string_value = self.value | ||
elif Parameter.Type.BYTE_ARRAY == self.type_: | ||
parameter_value.byte_array_value = self.value | ||
elif Parameter.Type.BOOL_ARRAY == self.type_: | ||
parameter_value.bool_array_value = self.value | ||
elif Parameter.Type.INTEGER_ARRAY == self.type_: | ||
parameter_value.integer_array_value = self.value | ||
elif Parameter.Type.DOUBLE_ARRAY == self.type_: | ||
parameter_value.double_array_value = self.value | ||
elif Parameter.Type.STRING_ARRAY == self.type_: | ||
parameter_value.string_array_value = self.value | ||
return parameter_value |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
# Copyright 2018 Open Source Robotics Foundation, Inc. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
from rcl_interfaces.srv import DescribeParameters, GetParameters, GetParameterTypes | ||
from rcl_interfaces.srv import ListParameters, SetParameters, SetParametersAtomically | ||
from rclpy.parameter import Parameter, PARAMETER_SEPARATOR_STRING | ||
from rclpy.qos import qos_profile_parameters | ||
from rclpy.validate_topic_name import TOPIC_SEPARATOR_STRING | ||
|
||
|
||
class ParameterService: | ||
|
||
def __init__(self, node): | ||
self._node = node | ||
nodename = node.get_name() | ||
|
||
describe_parameters_service_name = \ | ||
TOPIC_SEPARATOR_STRING.join((nodename, 'describe_parameters')) | ||
node.create_service( | ||
DescribeParameters, describe_parameters_service_name, | ||
self._describe_parameters_callback, qos_profile=qos_profile_parameters | ||
) | ||
get_parameters_service_name = TOPIC_SEPARATOR_STRING.join((nodename, 'get_parameters')) | ||
node.create_service( | ||
GetParameters, get_parameters_service_name, self._get_parameters_callback, | ||
qos_profile=qos_profile_parameters | ||
) | ||
get_parameter_types_service_name = \ | ||
TOPIC_SEPARATOR_STRING.join((nodename, 'get_parameter_types')) | ||
node.create_service( | ||
GetParameterTypes, get_parameter_types_service_name, | ||
self._get_parameter_types_callback, qos_profile=qos_profile_parameters | ||
) | ||
list_parameters_service_name = TOPIC_SEPARATOR_STRING.join((nodename, 'list_parameters')) | ||
node.create_service( | ||
ListParameters, list_parameters_service_name, self._list_parameters_callback, | ||
qos_profile=qos_profile_parameters | ||
) | ||
set_parameters_service_name = TOPIC_SEPARATOR_STRING.join((nodename, 'set_parameters')) | ||
node.create_service( | ||
SetParameters, set_parameters_service_name, self._set_parameters_callback, | ||
qos_profile=qos_profile_parameters | ||
) | ||
set_parameters_atomically_service_name = \ | ||
TOPIC_SEPARATOR_STRING.join((nodename, 'set_parameters_atomically')) | ||
node.create_service( | ||
SetParametersAtomically, set_parameters_atomically_service_name, | ||
self._set_parameters_atomically_callback, | ||
qos_profile=qos_profile_parameters | ||
) | ||
|
||
def _describe_parameters_callback(self, request, response): | ||
for name in request.names: | ||
p = self._node.get_parameter(name) | ||
response.descriptors.append(p.get_descriptor()) | ||
return response | ||
|
||
def _get_parameters_callback(self, request, response): | ||
for name in request.names: | ||
p = self._node.get_parameter(name) | ||
response.values.append(p.get_parameter_value()) | ||
return response | ||
|
||
def _get_parameter_types_callback(self, request, response): | ||
for name in request.names: | ||
response.types.append(self._node.get_parameter(name).get_parameter_type()) | ||
return response | ||
|
||
def _list_parameters_callback(self, request, response): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question: do you have examples of parameters that prompted the logic in this function ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See #214 (comment) for test cases used. |
||
names_with_prefixes = [] | ||
for name in self._node._parameters.keys(): | ||
if PARAMETER_SEPARATOR_STRING in name: | ||
names_with_prefixes.append(name) | ||
continue | ||
elif request.prefixes: | ||
continue | ||
else: | ||
response.result.names.append(name) | ||
if 1 == request.depth: | ||
return response | ||
|
||
if not request.DEPTH_RECURSIVE == request.depth: | ||
names_with_prefixes = filter( | ||
lambda name: | ||
name.count(PARAMETER_SEPARATOR_STRING) < request.depth, names_with_prefixes | ||
) | ||
for name in names_with_prefixes: | ||
if request.prefixes: | ||
for prefix in request.prefixes: | ||
if name.startswith(prefix + PARAMETER_SEPARATOR_STRING): | ||
response.result.names.append(name) | ||
full_prefix = PARAMETER_SEPARATOR_STRING.join( | ||
name.split(PARAMETER_SEPARATOR_STRING)[0:-1]) | ||
if full_prefix not in response.result.prefixes: | ||
response.result.prefixes.append(full_prefix) | ||
if prefix not in response.result.prefixes: | ||
response.result.prefixes.append(prefix) | ||
else: | ||
prefix = PARAMETER_SEPARATOR_STRING.join( | ||
name.split(PARAMETER_SEPARATOR_STRING)[0:-1]) | ||
if prefix not in response.result.prefixes: | ||
response.result.prefixes.append(prefix) | ||
response.result.names.append(name) | ||
|
||
return response | ||
|
||
def _set_parameters_callback(self, request, response): | ||
for p in request.parameters: | ||
param = Parameter.from_rcl_interface_parameter(p) | ||
response.results.append(self._node.set_parameters_atomically([param])) | ||
return response | ||
|
||
def _set_parameters_atomically_callback(self, request, response): | ||
response.results = self._node.set_parameters_atomically([ | ||
Parameter.from_rcl_interface_parameter(p) for p in request.parameters]) | ||
return response |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,8 @@ | |
from rclpy.exceptions import InvalidTopicNameException | ||
from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy | ||
|
||
TOPIC_SEPARATOR_STRING = '/' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to find a "sensible" place for the topic separator constant because it seems like it could be used for more than just parameter services and this was the best I could come up with based on module names. But I can either put it in parameter_services for now or if there's a place more "sensible" than this I can move it to I'm happy to field suggestions. |
||
|
||
|
||
def validate_topic_name(name, *, is_service=False): | ||
""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mikaelarguedas @sloretz the property is now accessed as
type_
. I had initially let this slide since the generated interface code usestype
in python currently and I know that surfaced in discussion recently but can't find it to figure out where we came down.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My recollection is that we cannot really make changes to ROS1 messages that use builtins.
But that anything not being legacy ROS1 code (and that will result in disruptive changes) should avoid using builtins names.
Part of the discussion is at ros2/build_farmer#104, bu I don't think it discuss anything outside of generated messages
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. And the change is in so all's well.