Package dynamic_reconfigure :: Module client

Source Code for Module dynamic_reconfigure.client

  1  # Software License Agreement (BSD License) 
  2  # 
  3  # Copyright (c) 2009, Willow Garage, Inc. 
  4  # All rights reserved. 
  5  # 
  6  # Redistribution and use in source and binary forms, with or without 
  7  # modification, are permitted provided that the following conditions 
  8  # are met: 
  9  # 
 10  #  * Redistributions of source code must retain the above copyright 
 11  #    notice, this list of conditions and the following disclaimer. 
 12  #  * Redistributions in binary form must reproduce the above 
 13  #    copyright notice, this list of conditions and the following 
 14  #    disclaimer in the documentation and/or other materials provided 
 15  #    with the distribution. 
 16  #  * Neither the name of Willow Garage, Inc. nor the names of its 
 17  #    contributors may be used to endorse or promote products derived 
 18  #    from this software without specific prior written permission. 
 19  # 
 20  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 21  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 22  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 23  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 24  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 25  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 26  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 27  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 28  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 29  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 30  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 31  # POSSIBILITY OF SUCH DAMAGE. 
 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: 
 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   
58 -class Client(object):
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
89 - def get_configuration(self, timeout=None):
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
121 - def get_parameter_descriptions(self, timeout=None):
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
148 - def get_group_descriptions(self, timeout=None):
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
168 - def update_configuration(self, changes):
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 # Retrieve the parameter descriptions 176 if self.param_description is None: 177 self.get_parameter_descriptions() 178 179 # Cast the parameters to the appropriate types 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 # Fix not converting bools properly 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 # Handle enums 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
232 - def update_groups(self, changes):
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
255 - def close(self):
256 """ 257 Close connections to the server 258 """ 259 self._descriptions_sub.unregister() 260 self._updates_sub.unregister()
261 262 ## config_callback 263
264 - def get_config_callback(self):
265 """ 266 Retrieve the config_callback 267 """ 268 return self._config_callback
269
270 - def set_config_callback(self, value):
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 ## description_callback 281
282 - def get_description_callback(self):
283 """ 284 Get the current description_callback 285 """ 286 return self._description_callback
287
288 - def set_description_callback(self, value):
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 # Implementation 300
301 - def _get_service_proxy(self, suffix, timeout):
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
314 - def _get_subscriber(self, suffix, type, callback):
315 topic_name = rospy.resolve_name(self.name + '/' + suffix) 316 317 return rospy.Subscriber(topic_name, type, callback=callback)
318
319 - def _updates_msg(self, msg):
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
329 - def _descriptions_msg(self, msg):
330 self.group_description = decode_description(msg) 331 self.param_description = extract_params(self.group_description) 332 333 # Build map from parameter name to type 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
345 - def _param_type_from_string(self, type_str):
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