1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 from rosmaster.util import remove_server_proxy
36 from rosmaster.util import xmlrpcapi
37 import rosmaster.exceptions
38
39 """Data structures for representing registration data in the Master"""
40
42 """
43 Container for node registration information. Used in master's
44 self.nodes data structure. This is effectively a reference
45 counter for the node registration information: when the
46 subscriptions and publications are empty the node registration can
47 be deleted.
48 """
50 """
51 ctor
52 @param api str: node XML-RPC API
53 """
54 self.id = id
55 self.api = api
56 self.param_subscriptions = []
57 self.topic_subscriptions = []
58 self.topic_publications = []
59 self.services = []
60
62 """
63 Delete all state from this NodeRef except for the api location
64 """
65 self.param_subscriptions = []
66 self.topic_subscriptions = []
67 self.topic_publications = []
68 self.services = []
69
71 """
72 @return: True if node has no active registrations
73 """
74 return sum((len(x) for x in
75 [self.param_subscriptions,
76 self.topic_subscriptions,
77 self.topic_publications,
78 self.services,])) == 0
79
80 - def add(self, type_, key):
95
111
112
113
114
116 """
117 Method to shutdown another ROS node. Generally invoked within a
118 separate thread as this is used to cleanup hung nodes.
119
120 @param api: XML-RPC API of node to shutdown
121 @type api: str
122 @param caller_id: name of node being shutdown
123 @type caller_id: str
124 @param reason: human-readable reason why node is being shutdown
125 @type reason: str
126 """
127 try:
128 xmlrpcapi(api).shutdown('/master', reason)
129 except:
130 pass
131 remove_server_proxy(api)
132
134 """
135 All calls may result in access/modifications to node registrations
136 dictionary, so be careful to guarantee appropriate thread-safeness.
137
138 Data structure for storing a set of registrations (e.g. publications, services).
139 The underlying data storage is the same except for services, which have the
140 constraint that only one registration may be active for a given key.
141 """
142
143 TOPIC_SUBSCRIPTIONS = 1
144 TOPIC_PUBLICATIONS = 2
145 SERVICE = 3
146 PARAM_SUBSCRIPTIONS = 4
147
165
167 """
168 @return: True if there are no registrations
169 """
170 return len(self.map) != 0
171
173 """
174 Iterate over registration keys
175 @return: iterator for registration keys
176 """
177 return self.map.iterkeys()
178
180 """
181 Lookup service API URI. NOTE: this should only be valid if type==SERVICE as
182 service Registrations instances are the only ones that track service API URIs.
183 @param service: service name
184 @type service: str
185 @return str: service_api for registered key or None if
186 registration is no longer valid.
187 @type: str
188 """
189 if self.service_api_map and service in self.service_api_map:
190 caller_id, service_api = self.service_api_map[service]
191 return service_api
192 return None
193
195 """
196 Only valid if self.type != SERVICE.
197 @param key: registration key (e.g. topic/service/param name)
198 @type key: str
199 @return: caller_apis for registered key, empty list if registration is not valid
200 @rtype: [str]
201 """
202 return [api for _, api in self.map.get(key, [])]
203
205 """
206 Emulate mapping type for has_key()
207 """
208 return key in self.map
209
211 """
212 @param key: registration key (e.g. topic/service/param name)
213 @type key: str
214 @return: (caller_id, caller_api) for registered
215 key, empty list if registration is not valid
216 @rtype: [(str, str),]
217 """
218
219
220
221 return self.map.get(key, [])
222
224 """
225 @param key: registration key (e.g. topic/service/param name)
226 @type key: str
227 @return: True if key is registered
228 @rtype: bool
229 """
230 return key in self.map
231
233 """
234 @return: state in getSystemState()-friendly format [ [key, [callerId1...callerIdN]] ... ]
235 @rtype: [str, [str]...]
236 """
237 retval = []
238 for k in self.map.iterkeys():
239 retval.append([k, [id for id, _ in self.map[k]]])
240 return retval
241
242 - def register(self, key, caller_id, caller_api, service_api=None):
243 """
244 Add caller_id into the map as a provider of the specified
245 service (key). caller_id must not have been previously
246 registered with a different caller_api.
247
248 Subroutine for managing provider map data structure (essentially a multimap).
249 @param key: registration key (e.g. topic/service/param name)
250 @type key: str
251 @param caller_id: caller_id of provider
252 @type caller_id: str
253 @param caller_api: API URI of provider
254 @type caller_api: str
255 @param service_api: (keyword) ROS service API URI if registering a service
256 @type service_api: str
257 """
258 map = self.map
259 if key in map and not service_api:
260 providers = map[key]
261 if not (caller_id, caller_api) in providers:
262 providers.append((caller_id, caller_api))
263 else:
264 map[key] = providers = [(caller_id, caller_api)]
265
266 if service_api:
267 if self.service_api_map is None:
268 self.service_api_map = {}
269 self.service_api_map[key] = (caller_id, service_api)
270 elif self.type == Registrations.SERVICE:
271 raise rosmaster.exceptions.InternalException("service_api must be specified for Registrations.SERVICE")
272
274 """
275 Remove all registrations associated with caller_id
276 @param caller_id: caller_id of provider
277 @type caller_id: str
278 """
279 map = self.map
280
281 dead_keys = []
282 for key in map:
283 providers = map[key]
284
285 to_remove = [(id, api) for id, api in providers if id == caller_id]
286
287 for r in to_remove:
288 providers.remove(r)
289 if not providers:
290 dead_keys.append(key)
291 for k in dead_keys:
292 del self.map[k]
293 if self.type == Registrations.SERVICE and self.service_api_map:
294 del dead_keys[:]
295 for key, val in self.service_api_map.iteritems():
296 if val[0] == caller_id:
297 dead_keys.append(key)
298 for k in dead_keys:
299 del self.service_api_map[k]
300
301 - def unregister(self, key, caller_id, caller_api, service_api=None):
302 """
303 Remove caller_id from the map as a provider of the specified service (key).
304 Subroutine for managing provider map data structure, essentially a multimap
305 @param key: registration key (e.g. topic/service/param name)
306 @type key: str
307 @param caller_id: caller_id of provider
308 @type caller_id: str
309 @param caller_api: API URI of provider
310 @type caller_api: str
311 @param service_api: (keyword) ROS service API URI if registering a service
312 @type service_api: str
313 @return: for ease of master integration, directly returns unregister value for
314 higher-level XMLRPC API. val is the number of APIs unregistered (0 or 1)
315 @rtype: code, msg, val
316 """
317
318 if service_api:
319
320 if self.service_api_map is None:
321 return 1, "[%s] is not a provider of [%s]"%(caller_id, key), 0
322 if self.service_api_map.get(key, None) != (caller_id, service_api):
323 return 1, "[%s] is no longer the current service api handle for [%s]"%(service_api, key), 0
324 else:
325 del self.service_api_map[key]
326 del self.map[key]
327
328 return 1, "Unregistered [%s] as provider of [%s]"%(caller_id, key), 1
329 elif self.type == Registrations.SERVICE:
330 raise rosmaster.exceptions.InternalException("service_api must be specified for Registrations.SERVICE")
331 else:
332 providers = self.map.get(key, [])
333 if (caller_id, caller_api) in providers:
334 providers.remove((caller_id, caller_api))
335 if not providers:
336 del self.map[key]
337 return 1, "Unregistered [%s] as provider of [%s]"%(caller_id, key), 1
338 else:
339 return 1, "[%s] is not a known provider of [%s]"%(caller_id, key), 0
340
342 """
343 Stores registrations for Master.
344
345 RegistrationManager is not threadsafe, so access must be externally locked as appropriate
346 """
347
361
362
364 """
365 Get a NodeRef by caller_api
366 @param caller_api: caller XML RPC URI
367 @type caller_api: str
368 @return: nodes that declare caller_api as their
369 API. 99.9% of the time this should only be one node, but we
370 allow for multiple matches as the master API does not restrict
371 this.
372 @rtype: [NodeRef]
373 """
374 matches = [n for n in self.nodes.iteritems() if n.api == caller_api]
375 if matches:
376 return matches
377
379 return self.nodes.get(caller_id, None)
380
381 - def _register(self, r, key, caller_id, caller_api, service_api=None):
382
383 node_ref, changed = self._register_node_api(caller_id, caller_api)
384 node_ref.add(r.type, key)
385
386 if changed:
387 self.publishers.unregister_all(caller_id)
388 self.subscribers.unregister_all(caller_id)
389 self.services.unregister_all(caller_id)
390 self.param_subscribers.unregister_all(caller_id)
391 r.register(key, caller_id, caller_api, service_api)
392
393 - def _unregister(self, r, key, caller_id, caller_api, service_api=None):
394 node_ref = self.nodes.get(caller_id, None)
395 if node_ref != None:
396 retval = r.unregister(key, caller_id, caller_api, service_api)
397
398 if retval[2] == 1:
399 node_ref.remove(r.type, key)
400 if node_ref.is_empty():
401 del self.nodes[caller_id]
402 else:
403 retval = 1, "[%s] is not a registered node"%caller_id, 0
404 return retval
405
407 """
408 Register service provider
409 @return: None
410 """
411 self._register(self.services, service, caller_id, caller_api, service_api)
413 """
414 Register topic publisher
415 @return: None
416 """
417 self._register(self.publishers, topic, caller_id, caller_api)
419 """
420 Register topic subscriber
421 @return: None
422 """
423 self._register(self.subscribers, topic, caller_id, caller_api)
425 """
426 Register param subscriber
427 @return: None
428 """
429 self._register(self.param_subscribers, param, caller_id, caller_api)
430
432 caller_api = None
433 return self._unregister(self.services, service, caller_id, caller_api, service_api)
434
436 return self._unregister(self.subscribers, topic, caller_id, caller_api)
438 return self._unregister(self.publishers, topic, caller_id, caller_api)
440 return self._unregister(self.param_subscribers, param, caller_id, caller_api)
441
443 """
444 @param caller_id: caller_id of provider
445 @type caller_id: str
446 @param caller_api: caller_api of provider
447 @type caller_api: str
448 @return: (registration_information, changed_registration). changed_registration is true if
449 caller_api is differet than the one registered with caller_id
450 @rtype: (NodeRef, bool)
451 """
452 node_ref = self.nodes.get(caller_id, None)
453
454 bumped_api = None
455 if node_ref is not None:
456 if node_ref.api == caller_api:
457 return node_ref, False
458 else:
459 bumped_api = node_ref.api
460 self.thread_pool.queue_task(bumped_api, shutdown_node_task,
461 (bumped_api, caller_id, "new node registered with same name"))
462
463 node_ref = NodeRef(caller_id, caller_api)
464 self.nodes[caller_id] = node_ref
465 return (node_ref, bumped_api != None)
466