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.items(): 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 range(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, caller_id=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 @param caller_id: the caller id 182 @type caller_id: str 183 """ 184 try: 185 self.lock.acquire() 186 if key == GLOBALNS: 187 if type(value) != dict: 188 raise TypeError("cannot set root of parameter tree to non-dictionary") 189 self.parameters = value 190 else: 191 namespaces = [x for x in key.split(SEP) if x] 192 # - last namespace is the actual key we're storing in 193 value_key = namespaces[-1] 194 namespaces = namespaces[:-1] 195 d = self.parameters 196 # - descend tree to the node we're setting 197 for ns in namespaces: 198 if not ns in d: 199 new_d = {} 200 d[ns] = new_d 201 d = new_d 202 else: 203 val = d[ns] 204 # implicit type conversion of value to namespace 205 if type(val) != dict: 206 d[ns] = val = {} 207 d = val 208 209 d[value_key] = value 210 211 # ParamDictionary needs to queue updates so that the updates are thread-safe 212 if notify_task: 213 updates = compute_param_updates(self.reg_manager.param_subscribers, key, value, caller_id) 214 if updates: 215 notify_task(updates) 216 finally: 217 self.lock.release()
218 219
220 - def subscribe_param(self, key, registration_args):
221 """ 222 @param key: parameter key 223 @type key: str 224 @param registration_args: additional args to pass to 225 subscribers.register. First parameter is always the parameter 226 key. 227 @type registration_args: tuple 228 """ 229 if key != SEP: 230 key = canonicalize_name(key) + SEP 231 try: 232 self.lock.acquire() 233 # fetch parameter value 234 try: 235 val = self.get_param(key) 236 except KeyError: 237 # parameter not set yet 238 val = {} 239 self.reg_manager.register_param_subscriber(key, *registration_args) 240 return val 241 finally: 242 self.lock.release()
243 244
245 - def unsubscribe_param(self, key, unregistration_args):
246 """ 247 @param key str: parameter key 248 @type key: str 249 @param unregistration_args: additional args to pass to 250 subscribers.unregister. i.e. unregister will be called with 251 (key, *unregistration_args) 252 @type unregistration_args: tuple 253 @return: return value of subscribers.unregister() 254 """ 255 if key != SEP: 256 key = canonicalize_name(key) + SEP 257 return self.reg_manager.unregister_param_subscriber(key, *unregistration_args)
258
259 - def delete_param(self, key, notify_task=None):
260 """ 261 Delete the parameter in the parameter dictionary. 262 @param key str: parameter key 263 @param notify_task fn(updates): function to call with 264 subscriber updates. updates is of the form 265 [(subscribers, param_key, param_value)*]. The empty dictionary 266 represents an unset parameter. 267 """ 268 try: 269 self.lock.acquire() 270 if key == GLOBALNS: 271 raise KeyError("cannot delete root of parameter tree") 272 else: 273 # key is global, so first split is empty 274 namespaces = [x for x in key.split(SEP) if x] 275 # - last namespace is the actual key we're deleting 276 value_key = namespaces[-1] 277 namespaces = namespaces[:-1] 278 d = self.parameters 279 # - descend tree to the node we're setting 280 for ns in namespaces: 281 if type(d) != dict or not ns in d: 282 raise KeyError(key) 283 else: 284 d = d[ns] 285 286 if not value_key in d: 287 raise KeyError(key) 288 else: 289 del d[value_key] 290 291 # ParamDictionary needs to queue updates so that the updates are thread-safe 292 if notify_task: 293 updates = compute_param_updates(self.reg_manager.param_subscribers, key, {}) 294 if updates: 295 notify_task(updates) 296 finally: 297 self.lock.release()
298
299 - def has_param(self, key):
300 """ 301 Test for parameter existence 302 303 @param key: parameter key 304 @type key: str 305 @return: True if parameter set, False otherwise 306 @rtype: bool 307 """ 308 try: 309 # more efficient implementations are certainly possible, 310 # but this guarantees correctness for now 311 self.get_param(key) 312 return True 313 except KeyError: 314 return False
315
316 -def _compute_all_keys(param_key, param_value, all_keys=None):
317 """ 318 Compute which subscribers should be notified based on the parameter update 319 @param param_key: key of updated parameter 320 @type param_key: str 321 @param param_value: value of updated parameter 322 @param all_keys: (internal use only) list of parameter keys 323 to append to for recursive calls. 324 @type all_keys: [str] 325 @return: list of parameter keys. All keys will be canonicalized with trailing slash. 326 @rtype: [str] 327 """ 328 if all_keys is None: 329 all_keys = [] 330 for k, v in param_value.items(): 331 new_k = ns_join(param_key, k) + SEP 332 all_keys.append(new_k) 333 if type(v) == dict: 334 _compute_all_keys(new_k, v, all_keys) 335 return all_keys
336
337 -def compute_param_updates(subscribers, param_key, param_value, caller_id_to_ignore=None):
338 """ 339 Compute subscribers that should be notified based on the parameter update 340 @param subscribers: parameter subscribers 341 @type subscribers: Registrations 342 @param param_key: parameter key 343 @type param_key: str 344 @param param_value: parameter value 345 @type param_value: str 346 @param caller_id_to_ignore: the caller to ignore 347 @type caller_id_to_ignore: str 348 """ 349 350 # logic correct for both updates and deletions 351 352 if not subscribers: 353 return [] 354 355 # end with a trailing slash to optimize startswith check from 356 # needing an extra equals check 357 if param_key != SEP: 358 param_key = canonicalize_name(param_key) + SEP 359 360 # compute all the updated keys 361 if type(param_value) == dict: 362 all_keys = _compute_all_keys(param_key, param_value) 363 else: 364 all_keys = None 365 366 updates = [] 367 368 # subscriber gets update if anything in the subscribed namespace is updated or if its deleted 369 for sub_key in subscribers.iterkeys(): 370 ns_key = sub_key 371 if ns_key[-1] != SEP: 372 ns_key = sub_key + SEP 373 if param_key.startswith(ns_key): 374 node_apis = subscribers[sub_key] 375 if caller_id_to_ignore is not None: 376 node_apis = [ 377 (caller_id, caller_api) 378 for (caller_id, caller_api) in node_apis 379 if caller_id != caller_id_to_ignore] 380 updates.append((node_apis, param_key, param_value)) 381 elif all_keys is not None and ns_key.startswith(param_key) \ 382 and not sub_key in all_keys: 383 # parameter was deleted 384 node_apis = subscribers[sub_key] 385 updates.append((node_apis, sub_key, {})) 386 387 # add updates for exact matches within tree 388 if all_keys is not None: 389 # #586: iterate over parameter tree for notification 390 for key in all_keys: 391 if key in subscribers: 392 # compute actual update value 393 sub_key = key[len(param_key):] 394 namespaces = [x for x in sub_key.split(SEP) if x] 395 val = param_value 396 for ns in namespaces: 397 val = val[ns] 398 399 updates.append((subscribers[key], key, val)) 400 401 return updates
402