Skip to content

Commit dfcf70f

Browse files
author
Juan Ignacio Ubeira
authored
Porting rosapi package. (#388)
Porting rosapi package. Signed-off-by: Juan Ignacio Ubeira <[email protected]>
1 parent 7467a03 commit dfcf70f

File tree

12 files changed

+490
-342
lines changed

12 files changed

+490
-342
lines changed

rosapi/package.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@
2222
<buildtool_depend>rosidl_default_generators</buildtool_depend>
2323

2424
<exec_depend>rclpy</exec_depend>
25+
<exec_depend>rcl_interfaces</exec_depend>
2526
<exec_depend>rosbridge_library</exec_depend>
27+
<exec_depend>ros2node</exec_depend>
28+
<exec_depend>ros2param</exec_depend>
29+
<exec_depend>ros2pkg</exec_depend>
30+
<exec_depend>ros2service</exec_depend>
31+
<exec_depend>ros2topic</exec_depend>
2632
<!--
2733
<exec_depend>rosnode</exec_depend>
2834
<exec_depend>rosgraph</exec_depend>

rosapi/scripts/rosapi_node

Lines changed: 187 additions & 141 deletions
Large diffs are not rendered by default.

rosapi/src/rosapi/glob_helper.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,36 @@
11
#!/usr/bin/env python
22

3-
import rospy
3+
from collections import namedtuple
44
import fnmatch
5+
from rcl_interfaces.msg import ParameterType
56

6-
topics_glob = []
7-
services_glob = []
8-
params_glob = []
7+
Globs = namedtuple('Globs', ['topics', 'services', 'params'])
98

109

11-
def get_globs():
10+
def get_globs(node):
1211

13-
global topics_glob
14-
global services_glob
15-
global params_glob
16-
17-
def get_param(param_name):
18-
param = rospy.get_param(param_name, '')
12+
def get_param(parameter_name):
13+
parameter_value = node.get_parameter(parameter_name).get_parameter_value()
14+
if parameter_value.type == ParameterType.PARAMETER_STRING:
15+
parameter_value = parameter_value.string_value
16+
else:
17+
parameter_value = ''
1918
# strips array delimiters in case of an array style value
2019
return [
2120
element.strip().strip("'")
22-
for element in param.strip('[').strip(']').split(',')
21+
for element in parameter_value.strip('[').strip(']').split(',')
2322
if len(element.strip().strip("'")) > 0]
2423

25-
topics_glob = get_param('~topics_glob')
26-
services_glob = get_param('~services_glob')
27-
params_glob = get_param('~params_glob')
24+
topics_glob = get_param('/topics_glob')
25+
services_glob = get_param('/services_glob')
26+
params_glob = get_param('/params_glob')
27+
return Globs(topics_glob, services_glob, params_glob)
2828

2929

3030
def filter_globs(globs, full_list):
3131
# If the globs are empty (weren't defined in the params), return the full list
3232
if globs is not None and len(globs) > 0:
33-
return filter(lambda x: any_match(x, globs), full_list)
33+
return list(filter(lambda x: any_match(x, globs), full_list))
3434
else:
3535
return full_list
3636

rosapi/src/rosapi/objectutils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def get_service_response_typedef_recursive(servicetype):
9595

9696
def _get_typedef(instance):
9797
""" Gets a typedef dict for the specified instance """
98-
if instance is None or not hasattr(instance, "__slots__") or not hasattr(instance, "_slot_types"):
98+
if instance is None or not hasattr(instance, "__slots__") or not hasattr(instance, "_fields_and_field_types"):
9999
return None
100100

101101
fieldnames = []
@@ -108,7 +108,7 @@ def _get_typedef(instance):
108108
fieldnames.append(name)
109109

110110
# Pull out the type and determine whether it's an array
111-
field_type = instance._slot_types[i]
111+
field_type = instance._fields_and_field_types[name[1:]] # Remove trailing underscore.
112112
arraylen = -1
113113
if field_type[-1:]==']':
114114
if field_type[-2:-1]=='[':

rosapi/src/rosapi/params.py

Lines changed: 130 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,40 @@
3131
# POSSIBILITY OF SUCH DAMAGE.
3232

3333
import fnmatch
34-
import rospy
35-
import threading
36-
3734
from json import loads, dumps
35+
import threading
3836

37+
from rcl_interfaces.msg import Parameter, ParameterType, ParameterValue
38+
from rcl_interfaces.srv import ListParameters
39+
import rclpy
40+
from ros2node.api import get_absolute_node_name
41+
from ros2param.api import call_get_parameters, call_set_parameters, get_parameter_value
3942

4043
""" Methods to interact with the param server. Values have to be passed
4144
as JSON in order to facilitate dynamically typed SRV messages """
4245

43-
# rospy parameter server isn't thread-safe
46+
# Ensure thread safety for setting / getting parameters.
4447
param_server_lock = threading.RLock()
48+
_node = None
49+
50+
_parameter_type_mapping = ['', 'bool_value', 'integer_value', 'double_value', 'string_value', 'byte_array_value'
51+
'bool_array_value', 'integer_array_value', 'double_array_value', 'string_array_value']
52+
53+
54+
def init():
55+
"""
56+
Initializes params module with a rclpy.node.Node for further use.
57+
This function has to be called before any other for the module to work.
58+
"""
59+
global _node
60+
# TODO(@jubeira): remove this node; use rosapi node with MultiThreadedExecutor or
61+
# async / await to prevent the service calls from blocking.
62+
_node = rclpy.create_node('rosapi_params')
63+
64+
65+
def set_param(node_name, name, value, params_glob):
66+
""" Sets a parameter in a given node """
4567

46-
def set_param(name, value, params_glob):
4768
if params_glob and not any(fnmatch.fnmatch(str(name), glob) for glob in params_glob):
4869
# If the glob list is not empty and there are no glob matches,
4970
# stop the attempt to set the parameter.
@@ -55,48 +76,139 @@ def set_param(name, value, params_glob):
5576
d = loads(value)
5677
except ValueError:
5778
raise Exception("Due to the type flexibility of the ROS parameter server, the value argument to set_param must be a JSON-formatted string.")
79+
80+
node_name = get_absolute_node_name(node_name)
5881
with param_server_lock:
59-
rospy.set_param(name, d)
60-
61-
62-
def get_param(name, default, params_glob):
82+
_set_param(node_name, name, value)
83+
84+
85+
def _set_param(node_name, name, value, parameter_type=None):
86+
"""
87+
Internal helper function for set_param.
88+
Attempts to set the given parameter in the target node with the desired value,
89+
deducing the parameter type if it's not specified.
90+
parameter_type allows forcing a type for the given value; this is useful to delete parameters.
91+
"""
92+
parameter = Parameter()
93+
parameter.name = name
94+
if parameter_type is None:
95+
parameter.value = get_parameter_value(string_value=value)
96+
else:
97+
parameter.value = ParameterValue()
98+
parameter.value.type = parameter_type
99+
if parameter_type != ParameterType.PARAMETER_NOT_SET:
100+
setattr(parameter.value, _parameter_type_mapping[parameter_type])
101+
102+
try:
103+
# call_get_parameters will fail if node does not exist.
104+
call_set_parameters(node=_node, node_name=node_name, parameters=[parameter])
105+
except:
106+
pass
107+
108+
109+
def get_param(node_name, name, default, params_glob):
110+
""" Gets a parameter from a given node """
111+
63112
if params_glob and not any(fnmatch.fnmatch(str(name), glob) for glob in params_glob):
64113
# If the glob list is not empty and there are no glob matches,
65114
# stop the attempt to get the parameter.
66115
return
67116
# If the glob list is empty (i.e. false) or the parameter matches
68117
# one of the glob strings, continue to get the parameter.
69-
d = None
70118
if default is not "":
71119
try:
72-
d = loads(default)
120+
default = loads(default)
73121
except ValueError:
74-
d = default
122+
pass # Keep default without modifications.
123+
124+
node_name = get_absolute_node_name(node_name)
75125
with param_server_lock:
76-
value = rospy.get_param(name, d)
126+
try:
127+
# call_get_parameters will fail if node does not exist.
128+
response = call_get_parameters(
129+
node=_node, node_name=node_name,
130+
parameter_names=[name])
131+
pvalue = response.values[0]
132+
# if type is 0 (parameter not set), the next line will raise an exception
133+
# and return value shall go to default.
134+
value = getattr(pvalue, _parameter_type_mapping[pvalue.type])
135+
except:
136+
# If either the node or the parameter does not exist, return default.
137+
value = default
138+
77139
return dumps(value)
78140

79-
def has_param(name, params_glob):
141+
142+
def has_param(node_name, name, params_glob):
143+
""" Checks whether a given node has a parameter or not """
144+
80145
if params_glob and not any(fnmatch.fnmatch(str(name), glob) for glob in params_glob):
81146
# If the glob list is not empty and there are no glob matches,
82147
# stop the attempt to set the parameter.
83148
return False
84149
# If the glob list is empty (i.e. false) or the parameter matches
85150
# one of the glob strings, check whether the parameter exists.
151+
node_name = get_absolute_node_name(node_name)
86152
with param_server_lock:
87-
return rospy.has_param(name)
153+
try:
154+
response = call_get_parameters(
155+
node=_node, node_name=node_name,
156+
parameter_names=[name])
157+
except:
158+
return False
159+
160+
return response.values[0].type > 0 and response.values[0].type < len(_parameter_type_mapping)
161+
162+
163+
def delete_param(node_name, name, params_glob):
164+
""" Deletes a parameter in a given node """
88165

89-
def delete_param(name, params_glob):
90166
if params_glob and not any(fnmatch.fnmatch(str(name), glob) for glob in params_glob):
91167
# If the glob list is not empty and there are no glob matches,
92168
# stop the attempt to delete the parameter.
93169
return
94170
# If the glob list is empty (i.e. false) or the parameter matches
95171
# one of the glob strings, continue to delete the parameter.
96-
if has_param(name, params_glob):
172+
node_name = get_absolute_node_name(node_name)
173+
if has_param(node_name, name, params_glob):
97174
with param_server_lock:
98-
rospy.delete_param(name)
175+
_set_param(node_name, name, None, ParameterType.PARAMETER_NOT_SET)
176+
177+
178+
def get_param_names(node_name, params_glob):
179+
""" Gets list of parameter names for a given node """
180+
181+
node_name = get_absolute_node_name(node_name)
182+
183+
with param_server_lock:
184+
if params_glob:
185+
# If there is a parameter glob, filter by it.
186+
return list(filter(
187+
lambda x: any(fnmatch.fnmatch(str(x), glob) for glob in params_glob),
188+
_get_param_names(node_name)))
189+
else:
190+
# If there is no parameter glob, don't filter.
191+
return _get_param_names(node_name)
192+
99193

194+
def _get_param_names(node_name):
195+
client = _node.create_client(ListParameters, '{}/list_parameters'.format(node_name))
196+
197+
ready = client.wait_for_service(timeout_sec=5.0)
198+
if not ready:
199+
raise RuntimeError('Wait for list_parameters service timed out')
200+
201+
request = ListParameters.Request()
202+
future = client.call_async(request)
203+
rclpy.spin_until_future_complete(_node, future)
204+
response = future.result()
205+
if response is not None:
206+
return response.result.names
207+
else:
208+
return []
209+
210+
211+
# TODO(@jubeira): functions to be ported below.
100212
def search_param(name, params_glob):
101213
if params_glob and not any(fnmatch.fnmatch(str(name), glob) for glob in params_glob):
102214
# If the glob list is not empty and there are no glob matches,
@@ -105,13 +217,3 @@ def search_param(name, params_glob):
105217
# If the glob list is empty (i.e. false) or the parameter matches
106218
# one of the glob strings, continue to find the parameter.
107219
return rospy.search_param(name)
108-
109-
def get_param_names(params_glob):
110-
with param_server_lock:
111-
if params_glob:
112-
# If there is a parameter glob, filter by it.
113-
return filter(lambda x: any(fnmatch.fnmatch(str(x), glob) for glob in params_glob), rospy.get_param_names())
114-
else:
115-
# If there is no parameter glob, don't filter.
116-
return rospy.get_param_names()
117-

0 commit comments

Comments
 (0)