1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 """
34 Python client API for dynamic_reconfigure (L{DynamicReconfigureClient}) as well as
35 example server implementation (L{DynamicReconfigureServer}).
36 """
37
38 from __future__ import print_function, with_statement
39
40 try:
41 import roslib; roslib.load_manifest('dynamic_reconfigure')
42 except Exception:
43 pass
44 import rospy
45 import sys
46 import threading
47 import time
48
49 from dynamic_reconfigure import DynamicReconfigureCallbackException
50 from dynamic_reconfigure import DynamicReconfigureParameterException
51 from dynamic_reconfigure.encoding import decode_config, decode_description, encode_config, extract_params
52 from dynamic_reconfigure.msg import Config as ConfigMsg
53 from dynamic_reconfigure.msg import ConfigDescription as ConfigDescrMsg
54 from dynamic_reconfigure.srv import Reconfigure as ReconfigureSrv
55 from rospy.service import ServiceException
56
57
59 """
60 Python dynamic_reconfigure client API
61 """
62 - def __init__(self, name, timeout=None, config_callback=None, description_callback=None):
63 """
64 Connect to dynamic_reconfigure server and return a client object
65
66 @param name: name of the server to connect to (usually the node name)
67 @type name: str
68 @param timeout: time to wait before giving up
69 @type timeout: float
70 @param config_callback: callback for server parameter changes
71 @param description_callback: internal use only as the API has not stabilized
72 """
73 self.name = name
74 self.config = None
75 self.param_description = None
76 self.group_description = None
77
78 self._param_types = None
79
80 self._cv = threading.Condition()
81
82 self._config_callback = config_callback
83 self._description_callback = description_callback
84
85 self._set_service = self._get_service_proxy('set_parameters', timeout)
86 self._descriptions_sub = self._get_subscriber('parameter_descriptions', ConfigDescrMsg, self._descriptions_msg)
87 self._updates_sub = self._get_subscriber('parameter_updates', ConfigMsg, self._updates_msg)
88
90 """
91 Return the latest received server configuration (wait to receive
92 one if none have been received)
93
94 @param timeout: time to wait before giving up
95 @type timeout: float
96 @return: dictionary mapping parameter names to values or None if unable to retrieve config.
97 @rtype: {str: value}
98 """
99 if timeout is None or timeout == 0.0:
100 if self.get_configuration(timeout=1.0) is None:
101 print('Waiting for configuration...', file=sys.stderr)
102
103 with self._cv:
104 while self.config is None:
105 if rospy.is_shutdown():
106 return None
107 self._cv.wait()
108 else:
109 start_time = time.time()
110 with self._cv:
111 while self.config is None:
112 if rospy.is_shutdown():
113 return None
114 secs_left = timeout - (time.time() - start_time)
115 if secs_left <= 0.0:
116 break
117 self._cv.wait(secs_left)
118
119 return self.config
120
122 """
123 UNSTABLE. Return a description of the parameters for the server.
124 Do not use this method as the type that is returned may change.
125
126 @param timeout: time to wait before giving up
127 @type timeout: float
128 """
129 if timeout is None or timeout == 0.0:
130 with self._cv:
131 while self.param_description is None:
132 if rospy.is_shutdown():
133 return None
134 self._cv.wait()
135 else:
136 start_time = time.time()
137 with self._cv:
138 while self.param_description is None:
139 if rospy.is_shutdown():
140 return None
141 secs_left = timeout - (time.time() - start_time)
142 if secs_left <= 0.0:
143 break
144 self._cv.wait(secs_left)
145
146 return self.param_description
147
149 if timeout is None or timeout == 0.0:
150 with self._cv:
151 while self.group_description is None:
152 if rospy.is_shutdown():
153 return None
154 self._cv.wait()
155 else:
156 start_time = time.time()
157 with self._cv:
158 while self.group_description is None:
159 if rospy.is_shutdown():
160 return None
161 secs_left = timeout - (time.time() - start_time)
162 if secs_left <= 0.0:
163 break
164 self._cv.wait(secs_left)
165
166 return self.group_description
167
169 """
170 Change the server's configuration
171
172 @param changes: dictionary of key value pairs for the parameters that are changing
173 @type changes: {str: value}
174 """
175
176 if self.param_description is None:
177 self.get_parameter_descriptions()
178
179
180 if self.param_description is not None:
181 for name, value in list(changes.items())[:]:
182 if name != 'groups':
183 dest_type = self._param_types.get(name)
184 if dest_type is None:
185 raise DynamicReconfigureParameterException('don\'t know parameter: %s' % name)
186
187 try:
188 found = False
189 descr = [x for x in self.param_description if x['name'].lower() == name.lower()][0]
190
191
192 if dest_type is bool and type(value) is str:
193 changes[name] = value.lower() in ("yes", "true", "t", "1")
194 found = True
195
196 elif type(value) is str and not descr['edit_method'] == '':
197 enum_descr = eval(descr['edit_method'])
198 found = False
199 for const in enum_descr['enum']:
200 if value.lower() == const['name'].lower():
201 val_type = self._param_type_from_string(const['type'])
202 changes[name] = val_type(const['value'])
203 found = True
204 if not found:
205 if sys.version_info.major < 3:
206 if type(value) is unicode:
207 changes[name] = unicode(value)
208 else:
209 changes[name] = dest_type(value)
210 else:
211 changes[name] = dest_type(value)
212
213 except ValueError as e:
214 raise DynamicReconfigureParameterException('can\'t set parameter \'%s\' of %s: %s' % (name, str(dest_type), e))
215
216 if 'groups' in changes.keys():
217 changes['groups'] = self.update_groups(changes['groups'])
218
219 config = encode_config(changes)
220
221 try:
222 msg = self._set_service(config).config
223 except ServiceException as e:
224 raise DynamicReconfigureCallbackException('service call failed')
225
226 if self.group_description is None:
227 self.get_group_descriptions()
228 resp = decode_config(msg, self.group_description)
229
230 return resp
231
233 """
234 Changes the servers group configuration
235
236 @param changes: dictionary of key value pairs for the parameters that are changing
237 @type changes: {str: value}
238 """
239
240 descr = self.get_group_descriptions()
241
242 def update_state(group, description):
243 for p, g in description['groups'].items():
244 if g['name'] == group:
245 description['groups'][p]['state'] = changes[group]
246 else:
247 update_state(group, g)
248 return description
249
250 for change in changes:
251 descr = update_state(change, descr)
252
253 return descr
254
256 """
257 Close connections to the server
258 """
259 self._descriptions_sub.unregister()
260 self._updates_sub.unregister()
261
262
263
265 """
266 Retrieve the config_callback
267 """
268 return self._config_callback
269
271 """
272 Set the config_callback
273 """
274 self._config_callback = value
275 if self._config_callback is not None:
276 self._config_callback(self.config)
277
278 config_callback = property(get_config_callback, set_config_callback)
279
280
281
283 """
284 Get the current description_callback
285 """
286 return self._description_callback
287
289 """
290 UNSTABLE. Set the description callback. Do not use as the type of the
291 description callback may change.
292 """
293 self._description_callback = value
294 if self._description_callback is not None:
295 self._description_callback(self.param_description)
296
297 description_callback = property(get_description_callback, set_description_callback)
298
299
300
302 service_name = rospy.resolve_name(self.name + '/' + suffix)
303 if timeout is None or timeout == 0.0:
304 try:
305 rospy.wait_for_service(service_name, 1.0)
306 except rospy.exceptions.ROSException:
307 print('Waiting for service %s...' % service_name, file=sys.stderr)
308 rospy.wait_for_service(service_name, timeout)
309 else:
310 rospy.wait_for_service(service_name, timeout)
311
312 return rospy.ServiceProxy(service_name, ReconfigureSrv)
313
315 topic_name = rospy.resolve_name(self.name + '/' + suffix)
316
317 return rospy.Subscriber(topic_name, type, callback=callback)
318
320 if self.group_description is None:
321 self.get_group_descriptions()
322 self.config = decode_config(msg, self.group_description)
323
324 with self._cv:
325 self._cv.notifyAll()
326 if self._config_callback is not None:
327 self._config_callback(self.config)
328
330 self.group_description = decode_description(msg)
331 self.param_description = extract_params(self.group_description)
332
333
334 self._param_types = {}
335 for p in self.param_description:
336 n, t = p.get('name'), p.get('type')
337 if n is not None and t is not None:
338 self._param_types[n] = self._param_type_from_string(t)
339
340 with self._cv:
341 self._cv.notifyAll()
342 if self._description_callback is not None:
343 self._description_callback(self.param_description)
344
346 if type_str == 'int':
347 return int
348 elif type_str == 'double':
349 return float
350 elif type_str == 'str':
351 return str
352 elif type_str == 'bool':
353 return bool
354 else:
355 raise DynamicReconfigureParameterException('parameter has unknown type: %s. This is a bug in dynamic_reconfigure.' % type_str)
356