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  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   
57 -class Client(object):
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
88 - def get_configuration(self, timeout=None):
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
120 - def get_parameter_descriptions(self, timeout=None):
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
147 - def get_group_descriptions(self, timeout=None):
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
167 - def update_configuration(self, changes):
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 # Retrieve the parameter descriptions 175 if self.param_description is None: 176 self.get_parameter_descriptions() 177 178 # Cast the parameters to the appropriate types 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 # Fix not converting bools properly 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 # Handle enums 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
226 - def update_groups(self, changes):
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
250 - def close(self):
251 """ 252 Close connections to the server 253 """ 254 self._descriptions_sub.unregister() 255 self._updates_sub.unregister()
256 257 ## config_callback 258
259 - def get_config_callback(self):
260 """ 261 Retrieve the config_callback 262 """ 263 return self._config_callback
264
265 - def set_config_callback(self, value):
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 ## description_callback 276
277 - def get_description_callback(self):
278 """ 279 Get the current description_callback 280 """ 281 return self._config_callback
282
283 - def set_description_callback(self, value):
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 # Implementation 295
296 - def _get_service_proxy(self, suffix, timeout):
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
309 - def _get_subscriber(self, suffix, type, callback):
310 topic_name = rospy.resolve_name(self.name + '/' + suffix) 311 312 return rospy.Subscriber(topic_name, type, callback=callback)
313
314 - def _updates_msg(self, msg):
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
324 - def _descriptions_msg(self, msg):
325 self.group_description = decode_description(msg) 326 self.param_description = extract_params(self.group_description) 327 328 # Build map from parameter name to type 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
340 - def _param_type_from_string(self, type_str):
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