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