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