Package rosmaster :: Module paramserver

Source Code for Module rosmaster.paramserver

  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  from threading import RLock 
 34   
 35  from rosgraph.names import ns_join, GLOBALNS, SEP, is_global, is_private, canonicalize_name 
 36   
37 -def _get_param_names(names, key, d):
38 """ 39 helper recursive routine for getParamNames() 40 @param names: list of param names to append to 41 @type names: [str] 42 @param d: parameter tree node 43 @type d: dict 44 @param key: parameter key for tree node d 45 @type key: str 46 """ 47 48 #TODOXXX 49 for k,v in d.iteritems(): 50 if type(v) == dict: 51 _get_param_names(names, ns_join(key, k), v) 52 else: 53 names.append(ns_join(key, k))
54
55 -class ParamDictionary(object):
56
57 - def __init__(self, reg_manager):
58 """ 59 ctor. 60 @param subscribers: parameter subscribers 61 @type subscribers: Registrations 62 """ 63 self.lock = RLock() 64 self.parameters = {} 65 self.reg_manager = reg_manager
66
67 - def get_param_names(self):
68 """ 69 Get list of all parameter names stored on this server. 70 71 @return: [code, statusMessage, parameterNameList] 72 @rtype: [int, str, [str]] 73 """ 74 try: 75 self.lock.acquire() 76 param_names = [] 77 _get_param_names(param_names, '/', self.parameters) 78 finally: 79 self.lock.release() 80 return param_names
81
82 - def search_param(self, ns, key):
83 """ 84 Search for matching parameter key for search param 85 key. Search for key starts at ns and proceeds upwards to 86 the root. As such, search_param should only be called with a 87 relative parameter name. 88 89 search_param's behavior is to search for the first partial match. 90 For example, imagine that there are two 'robot_description' parameters: 91 92 - /robot_description 93 - /robot_description/arm 94 - /robot_description/base 95 96 - /pr2/robot_description 97 - /pr2/robot_description/base 98 99 If I start in the namespace /pr2/foo and search for 100 'robot_description', search_param will match 101 /pr2/robot_description. If I search for 'robot_description/arm' 102 it will return /pr2/robot_description/arm, even though that 103 parameter does not exist (yet). 104 105 @param ns: namespace to begin search from. 106 @type ns: str 107 @param key: Parameter key. 108 @type key: str 109 @return: key of matching parameter or None if no matching 110 parameter. 111 @rtype: str 112 """ 113 if not key or is_private(key): 114 raise ValueError("invalid key") 115 if not is_global(ns): 116 raise ValueError("namespace must be global") 117 if is_global(key): 118 if self.has_param(key): 119 return key 120 else: 121 return None 122 123 # there are more efficient implementations, but our hiearchy 124 # is not very deep and this is fairly clean code to read. 125 126 # - we only search for the first namespace in the key to check for a match 127 key_namespaces = [x for x in key.split(SEP) if x] 128 key_ns = key_namespaces[0] 129 130 # - corner case: have to test initial namespace first as 131 # negative indices won't work with 0 132 search_key = ns_join(ns, key_ns) 133 if self.has_param(search_key): 134 # resolve to full key 135 return ns_join(ns, key) 136 137 namespaces = [x for x in ns.split(SEP) if x] 138 for i in xrange(1, len(namespaces)+1): 139 search_key = SEP + SEP.join(namespaces[0:-i] + [key_ns]) 140 if self.has_param(search_key): 141 # we have a match on the namespace of the key, so 142 # compose the full key and return it 143 full_key = SEP + SEP.join(namespaces[0:-i] + [key]) 144 return full_key 145 return None
146
147 - def get_param(self, key):
148 """ 149 Get the parameter in the parameter dictionary. 150 151 @param key: parameter key 152 @type key: str 153 @return: parameter value 154 """ 155 try: 156 self.lock.acquire() 157 val = self.parameters 158 if key != GLOBALNS: 159 # split by the namespace separator, ignoring empty splits 160 namespaces = [x for x in key.split(SEP)[1:] if x] 161 for ns in namespaces: 162 if not type(val) == dict: 163 raise KeyError(val) 164 val = val[ns] 165 return val 166 finally: 167 self.lock.release()
168
169 - def set_param(self, key, value, notify_task=None):
170 """ 171 Set the parameter in the parameter dictionary. 172 173 @param key: parameter key 174 @type key: str 175 @param value: parameter value 176 @param notify_task: function to call with 177 subscriber updates. updates is of the form 178 [(subscribers, param_key, param_value)*]. The empty dictionary 179 represents an unset parameter. 180 @type notify_task: fn(updates) 181 """ 182 try: 183 self.lock.acquire() 184 if key == GLOBALNS: 185 if type(value) != dict: 186 raise TypeError("cannot set root of parameter tree to non-dictionary") 187 self.parameters = value 188 else: 189 namespaces = [x for x in key.split(SEP) if x] 190 # - last namespace is the actual key we're storing in 191 value_key = namespaces[-1] 192 namespaces = namespaces[:-1] 193 d = self.parameters 194 # - descend tree to the node we're setting 195 for ns in namespaces: 196 if not ns in d: 197 new_d = {} 198 d[ns] = new_d 199 d = new_d 200 else: 201 val = d[ns] 202 # implicit type conversion of value to namespace 203 if type(val) != dict: 204 d[ns] = val = {} 205 d = val 206 207 d[value_key] = value 208 209 # ParamDictionary needs to queue updates so that the updates are thread-safe 210 if notify_task: 211 updates = compute_param_updates(self.reg_manager.param_subscribers, key, value) 212 if updates: 213 notify_task(updates) 214 finally: 215 self.lock.release()
216 217
218 - def subscribe_param(self, key, registration_args):
219 """ 220 @param key: parameter key 221 @type key: str 222 @param registration_args: additional args to pass to 223 subscribers.register. First parameter is always the parameter 224 key. 225 @type registration_args: tuple 226 """ 227 if key != SEP: 228 key = canonicalize_name(key) + SEP 229 try: 230 self.lock.acquire() 231 # fetch parameter value 232 try: 233 val = self.get_param(key) 234 except KeyError: 235 # parameter not set yet 236 val = {} 237 self.reg_manager.register_param_subscriber(key, *registration_args) 238 return val 239 finally: 240 self.lock.release()
241 242
243 - def unsubscribe_param(self, key, unregistration_args):
244 """ 245 @param key str: parameter key 246 @type key: str 247 @param unregistration_args: additional args to pass to 248 subscribers.unregister. i.e. unregister will be called with 249 (key, *unregistration_args) 250 @type unregistration_args: tuple 251 @return: return value of subscribers.unregister() 252 """ 253 if key != SEP: 254 key = canonicalize_name(key) + SEP 255 return self.reg_manager.unregister_param_subscriber(key, *unregistration_args)
256
257 - def delete_param(self, key, notify_task=None):
258 """ 259 Delete the parameter in the parameter dictionary. 260 @param key str: parameter key 261 @param notify_task fn(updates): function to call with 262 subscriber updates. updates is of the form 263 [(subscribers, param_key, param_value)*]. The empty dictionary 264 represents an unset parameter. 265 """ 266 try: 267 self.lock.acquire() 268 if key == GLOBALNS: 269 raise KeyError("cannot delete root of parameter tree") 270 else: 271 # key is global, so first split is empty 272 namespaces = [x for x in key.split(SEP) if x] 273 # - last namespace is the actual key we're deleting 274 value_key = namespaces[-1] 275 namespaces = namespaces[:-1] 276 d = self.parameters 277 # - descend tree to the node we're setting 278 for ns in namespaces: 279 if type(d) != dict or not ns in d: 280 raise KeyError(key) 281 else: 282 d = d[ns] 283 284 if not value_key in d: 285 raise KeyError(key) 286 else: 287 del d[value_key] 288 289 # ParamDictionary needs to queue updates so that the updates are thread-safe 290 if notify_task: 291 updates = compute_param_updates(self.reg_manager.param_subscribers, key, {}) 292 if updates: 293 notify_task(updates) 294 finally: 295 self.lock.release()
296
297 - def has_param(self, key):
298 """ 299 Test for parameter existence 300 301 @param key: parameter key 302 @type key: str 303 @return: True if parameter set, False otherwise 304 @rtype: bool 305 """ 306 try: 307 # more efficient implementations are certainly possible, 308 # but this guarantees correctness for now 309 self.get_param(key) 310 return True 311 except KeyError: 312 return False
313
314 -def _compute_all_keys(param_key, param_value, all_keys=None):
315 """ 316 Compute which subscribers should be notified based on the parameter update 317 @param param_key: key of updated parameter 318 @type param_key: str 319 @param param_value: value of updated parameter 320 @param all_keys: (internal use only) list of parameter keys 321 to append to for recursive calls. 322 @type all_keys: [str] 323 @return: list of parameter keys. All keys will be canonicalized with trailing slash. 324 @rtype: [str] 325 """ 326 if all_keys is None: 327 all_keys = [] 328 for k, v in param_value.iteritems(): 329 new_k = ns_join(param_key, k) + SEP 330 all_keys.append(new_k) 331 if type(v) == dict: 332 _compute_all_keys(new_k, v, all_keys) 333 return all_keys
334
335 -def compute_param_updates(subscribers, param_key, param_value):
336 """ 337 Compute subscribers that should be notified based on the parameter update 338 @param subscribers: parameter subscribers 339 @type subscribers: Registrations 340 @param param_key: parameter key 341 @type param_key: str 342 @param param_value: parameter value 343 @type param_value: str 344 """ 345 346 # logic correct for both updates and deletions 347 348 if not subscribers: 349 return [] 350 351 # end with a trailing slash to optimize startswith check from 352 # needing an extra equals check 353 if param_key != SEP: 354 param_key = canonicalize_name(param_key) + SEP 355 356 # compute all the updated keys 357 if type(param_value) == dict: 358 all_keys = _compute_all_keys(param_key, param_value) 359 else: 360 all_keys = None 361 362 updates = [] 363 364 # subscriber gets update if anything in the subscribed namespace is updated or if its deleted 365 for sub_key in subscribers.iterkeys(): 366 ns_key = sub_key 367 if ns_key[-1] != SEP: 368 ns_key = sub_key + SEP 369 if param_key.startswith(ns_key): 370 node_apis = subscribers[sub_key] 371 updates.append((node_apis, param_key, param_value)) 372 elif all_keys is not None and ns_key.startswith(param_key) \ 373 and not sub_key in all_keys: 374 # parameter was deleted 375 node_apis = subscribers[sub_key] 376 updates.append((node_apis, sub_key, {})) 377 378 # add updates for exact matches within tree 379 if all_keys is not None: 380 # #586: iterate over parameter tree for notification 381 for key in all_keys: 382 if key in subscribers: 383 # compute actual update value 384 sub_key = key[len(param_key):] 385 namespaces = [x for x in sub_key.split(SEP) if x] 386 val = param_value 387 for ns in namespaces: 388 val = val[ns] 389 390 updates.append((subscribers[key], key, val)) 391 392 return updates
393