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