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  ROS Master API. 
 
 36  
 
 37  L{ROSMasterHandler} provides the API implementation of the
 
 38  Master. Python allows an API to be introspected from a Python class,
 
 39  so the handler has a 1-to-1 mapping with the actual XMLRPC API.
 
 40  
 
 41  API return convention: (statusCode, statusMessage, returnValue)
 
 42  
 
 43   - statusCode: an integer indicating the completion condition of the method. 
 
 44   - statusMessage: a human-readable string message for debugging
 
 45   - returnValue: the return value of the method; method-specific.
 
 46  
 
 47  Current status codes: 
 
 48  
 
 49   - -1: ERROR: Error on the part of the caller, e.g. an invalid parameter
 
 50   - 0: FAILURE: Method was attempted but failed to complete correctly.
 
 51   - 1: SUCCESS: Method completed successfully.
 
 52  
 
 53  Individual methods may assign additional meaning/semantics to statusCode.
 
 54  """ 
 55  
 
 56  from __future__ import print_function 
 57  
 
 58  import os 
 59  import sys 
 60  import logging 
 61  import threading 
 62  import time 
 63  import traceback 
 64  
 
 65  from rosgraph.xmlrpc import XmlRpcHandler 
 66  
 
 67  import rosgraph.names 
 68  from rosgraph.names import resolve_name 
 69  import rosmaster.paramserver 
 70  import rosmaster.threadpool 
 71  
 
 72  from rosmaster.util import xmlrpcapi 
 73  from rosmaster.registrations import RegistrationManager 
 74  from rosmaster.validators import non_empty, non_empty_str, not_none, is_api, is_topic, is_service, valid_type_name, valid_name, empty_or_valid_name, ParameterInvalid 
 75  
 
 76  NUM_WORKERS = 3  
 77  
 
 78  
 
 79  STATUS = 0 
 80  MSG = 1 
 81  VAL = 2 
 82  
 
 83  _logger = logging.getLogger("rosmaster.master") 
 84  
 
 85  LOG_API = False 
 88      """
 
 89      Info-level master log statements. These statements may be printed
 
 90      to screen so they should be user-readable.
 
 91      @param msg: Message string
 
 92      @type  msg: str
 
 93      @param args: arguments for msg if msg is a format string
 
 94      """ 
 95      
 
 96      _logger.info(msg, *args) 
  97  
 
 99      """
 
100      Warn-level master log statements. These statements may be printed
 
101      to screen so they should be user-readable.
 
102      @param msg: Message string
 
103      @type  msg: str    
 
104      @param args: arguments for msg if msg is a format string
 
105      """ 
106      
 
107      _logger.warn(msg, *args) 
108      if args: 
109          print("WARN: " + msg % args) 
110      else: 
111          print("WARN: " + str(msg)) 
 112  
 
113  
 
114 -def apivalidate(error_return_value, validators=()): 
 115      """
 
116      ROS master/slave arg-checking decorator. Applies the specified
 
117      validator to the corresponding argument and also remaps each
 
118      argument to be the value returned by the validator.  Thus,
 
119      arguments can be simultaneously validated and canonicalized prior
 
120      to actual function call.
 
121      @param error_return_value: API value to return if call unexpectedly fails
 
122      @param validators: sequence of validators to apply to each
 
123        arg. None means no validation for the parameter is required. As all
 
124        api methods take caller_id as the first parameter, the validators
 
125        start with the second param.
 
126      @type  validators: sequence
 
127      """ 
128      def check_validates(f): 
129          try: 
130              func_code = f.__code__ 
131              func_name = f.__name__ 
132          except AttributeError: 
133              func_code = f.func_code 
134              func_name = f.func_name 
135          assert len(validators) == func_code.co_argcount - 2, "%s failed arg check"%f  
136          def validated_f(*args, **kwds): 
137              if LOG_API: 
138                  _logger.debug("%s%s", func_name, str(args[1:])) 
139                  
 
140              if len(args) == 1: 
141                  _logger.error("%s invoked without caller_id paramter" % func_name) 
142                  return -1, "missing required caller_id parameter", error_return_value 
143              elif len(args) != func_code.co_argcount: 
144                  return -1, "Error: bad call arity", error_return_value 
145  
 
146              instance = args[0] 
147              caller_id = args[1] 
148              def isstring(s): 
149                  """Small helper version to check an object is a string in
 
150                  a way that works for both Python 2 and 3
 
151                  """ 
152                  try: 
153                      return isinstance(s, basestring) 
154                  except NameError: 
155                      return isinstance(s, str) 
 156              if not isstring(caller_id): 
157                  _logger.error("%s: invalid caller_id param type", func_name) 
158                  return -1, "caller_id must be a string", error_return_value 
159              
 
160              newArgs = [instance, caller_id]  
161              try: 
162                  for (v, a) in zip(validators, args[2:]): 
163                      if v: 
164                          try: 
165                              newArgs.append(v(a, caller_id))  
166                          except ParameterInvalid as e: 
167                              _logger.error("%s: invalid parameter: %s", func_name, str(e) or 'error') 
168                              return -1, str(e) or 'error', error_return_value 
169                      else: 
170                          newArgs.append(a) 
171  
 
172                  if LOG_API: 
173                      retval = f(*newArgs, **kwds) 
174                      _logger.debug("%s%s returns %s", func_name, args[1:], retval) 
175                      return retval 
176                  else: 
177                      code, msg, val = f(*newArgs, **kwds) 
178                      if val is None: 
179                          return -1, "Internal error (None value returned)", error_return_value 
180                      return code, msg, val 
181              except TypeError as te:  
182                  _logger.error(traceback.format_exc()) 
183                  return -1, "Error: invalid arguments: %s"%te, error_return_value 
184              except Exception as e:  
185                  _logger.error(traceback.format_exc()) 
186                  return 0, "Internal failure: %s"%e, error_return_value 
187          try: 
188              validated_f.__name__ = func_name 
189          except AttributeError: 
190              validated_f.func_name = func_name 
191          validated_f.__doc__ = f.__doc__  
192          return validated_f 
193      return check_validates 
194  
 
196      """
 
197      Contact api.publisherUpdate with specified parameters
 
198      @param api: XML-RPC URI of node to contact
 
199      @type  api: str
 
200      @param topic: Topic name to send to node
 
201      @type  topic: str
 
202      @param pub_uris: list of publisher APIs to send to node
 
203      @type  pub_uris: [str]
 
204      """ 
205      
 
206      mloginfo("publisherUpdate[%s] -> %s", topic, api) 
207      
 
208      xmlrpcapi(api).publisherUpdate('/master', topic, pub_uris) 
 209  
 
211      """
 
212      Contact api.serviceUpdate with specified parameters
 
213      @param api: XML-RPC URI of node to contact
 
214      @type  api: str
 
215      @param service: Service name to send to node
 
216      @type  service: str
 
217      @param uri: URI to send to node        
 
218      @type  uri: str
 
219      """ 
220      mloginfo("serviceUpdate[%s, %s] -> %s",service, uri, api) 
221      xmlrpcapi(api).serviceUpdate('/master', service, uri) 
 222  
 
227      """
 
228      XML-RPC handler for ROS master APIs.
 
229      API routines for the ROS Master Node. The Master Node is a
 
230      superset of the Slave Node and contains additional API methods for
 
231      creating and monitoring a graph of slave nodes.
 
232  
 
233      By convention, ROS nodes take in caller_id as the first parameter
 
234      of any API call.  The setting of this parameter is rarely done by
 
235      client code as ros::msproxy::MasterProxy automatically inserts
 
236      this parameter (see ros::client::getMaster()).
 
237      """ 
238      
 
240          """ctor.""" 
241  
 
242          self.uri = None 
243          self.done = False 
244  
 
245          self.thread_pool = rosmaster.threadpool.MarkedThreadPool(num_workers) 
246          
 
247          self.ps_lock = threading.Condition(threading.Lock()) 
248  
 
249          self.reg_manager = RegistrationManager(self.thread_pool) 
250  
 
251          
 
252          self.publishers  = self.reg_manager.publishers 
253          self.subscribers = self.reg_manager.subscribers 
254          self.services = self.reg_manager.services 
255          self.param_subscribers = self.reg_manager.param_subscribers 
256          
 
257          self.topics_types = {}  
258  
 
259          
 
260          self.param_server = rosmaster.paramserver.ParamDictionary(self.reg_manager) 
 261  
 
263          if self.thread_pool is not None: 
264              self.thread_pool.join_all(wait_for_tasks=False, wait_for_threads=False) 
265              self.thread_pool = None 
266          self.done = True 
 267          
 
269          """
 
270          Initialize the handler with the XMLRPC URI. This is a standard callback from the XmlRpcNode API.
 
271  
 
272          @param uri: XML-RPC URI
 
273          @type  uri: str
 
274          """ 
275          self.uri = uri 
 276  
 
279      
 
280      
 
281      
 
282  
 
283      @apivalidate(0, (None, )) 
285          """
 
286          Stop this server
 
287          @param caller_id: ROS caller id
 
288          @type  caller_id: str
 
289          @param msg: a message describing why the node is being shutdown.
 
290          @type  msg: str
 
291          @return: [code, msg, 0]
 
292          @rtype: [int, str, int]
 
293          """ 
294          if msg: 
295              print("shutdown request: %s" % msg, file=sys.stdout) 
296          else: 
297              print("shutdown requst", file=sys.stdout) 
298          self._shutdown('external shutdown request from [%s]: %s'%(caller_id, msg)) 
299          return 1, "shutdown", 0 
 300          
 
301      @apivalidate('') 
303          """
 
304          Get the XML-RPC URI of this server.
 
305          @param caller_id str: ROS caller id    
 
306          @return [int, str, str]: [1, "", xmlRpcUri]
 
307          """ 
308          return 1, "", self.uri 
 309  
 
310          
 
311      @apivalidate(-1) 
313          """
 
314          Get the PID of this server
 
315          @param caller_id: ROS caller id
 
316          @type  caller_id: str
 
317          @return: [1, "", serverProcessPID]
 
318          @rtype: [int, str, int]
 
319          """ 
320          return 1, "", os.getpid() 
 321  
 
322      
 
323      
 
324      
 
325      
 
326      @apivalidate(0, (non_empty_str('key'),)) 
328          """
 
329          Parameter Server: delete parameter
 
330          @param caller_id: ROS caller id
 
331          @type  caller_id: str
 
332          @param key: parameter name
 
333          @type  key: str
 
334          @return: [code, msg, 0]
 
335          @rtype: [int, str, int]
 
336          """ 
337          try: 
338              key = resolve_name(key, caller_id) 
339              self.param_server.delete_param(key, self._notify_param_subscribers) 
340              mloginfo("-PARAM [%s] by %s",key, caller_id)             
341              return  1, "parameter %s deleted"%key, 0                 
342          except KeyError as e: 
343              return -1, "parameter [%s] is not set"%key, 0 
 344          
 
345      @apivalidate(0, (non_empty_str('key'), not_none('value'))) 
346 -    def setParam(self, caller_id, key, value): 
 347          """
 
348          Parameter Server: set parameter.  NOTE: if value is a
 
349          dictionary it will be treated as a parameter tree, where key
 
350          is the parameter namespace. For example:::
 
351            {'x':1,'y':2,'sub':{'z':3}}
 
352  
 
353          will set key/x=1, key/y=2, and key/sub/z=3. Furthermore, it
 
354          will replace all existing parameters in the key parameter
 
355          namespace with the parameters in value. You must set
 
356          parameters individually if you wish to perform a union update.
 
357          
 
358          @param caller_id: ROS caller id
 
359          @type  caller_id: str
 
360          @param key: parameter name
 
361          @type  key: str
 
362          @param value: parameter value.
 
363          @type  value: XMLRPCLegalValue
 
364          @return: [code, msg, 0]
 
365          @rtype: [int, str, int]
 
366          """ 
367          key = resolve_name(key, caller_id) 
368          self.param_server.set_param(key, value, self._notify_param_subscribers) 
369          mloginfo("+PARAM [%s] by %s",key, caller_id) 
370          return 1, "parameter %s set"%key, 0 
 371  
 
372      @apivalidate(0, (non_empty_str('key'),)) 
374          """
 
375          Retrieve parameter value from server.
 
376          @param caller_id: ROS caller id
 
377          @type  caller_id: str
 
378          @param key: parameter to lookup. If key is a namespace,
 
379          getParam() will return a parameter tree.
 
380          @type  key: str
 
381          getParam() will return a parameter tree.
 
382  
 
383          @return: [code, statusMessage, parameterValue]. If code is not
 
384              1, parameterValue should be ignored. If key is a namespace,
 
385              the return value will be a dictionary, where each key is a
 
386              parameter in that namespace. Sub-namespaces are also
 
387              represented as dictionaries.
 
388          @rtype: [int, str, XMLRPCLegalValue]
 
389          """ 
390          try: 
391              key = resolve_name(key, caller_id) 
392              return 1, "Parameter [%s]"%key, self.param_server.get_param(key) 
393          except KeyError as e:  
394              return -1, "Parameter [%s] is not set"%key, 0 
 395  
 
396      @apivalidate(0, (non_empty_str('key'),)) 
398          """
 
399          Search for parameter key on parameter server. Search starts in caller's namespace and proceeds
 
400          upwards through parent namespaces until Parameter Server finds a matching key.
 
401  
 
402          searchParam's behavior is to search for the first partial match.
 
403          For example, imagine that there are two 'robot_description' parameters::
 
404            
 
405             /robot_description
 
406               /robot_description/arm
 
407               /robot_description/base
 
408             /pr2/robot_description
 
409               /pr2/robot_description/base
 
410  
 
411          If I start in the namespace /pr2/foo and search for
 
412          'robot_description', searchParam will match
 
413          /pr2/robot_description. If I search for 'robot_description/arm'
 
414          it will return /pr2/robot_description/arm, even though that
 
415          parameter does not exist (yet).
 
416  
 
417          @param caller_id str: ROS caller id
 
418          @type  caller_id: str
 
419          @param key: parameter key to search for.
 
420          @type  key: str
 
421          @return: [code, statusMessage, foundKey]. If code is not 1, foundKey should be
 
422              ignored. 
 
423          @rtype: [int, str, str]
 
424          """ 
425          search_key = self.param_server.search_param(caller_id, key) 
426          if search_key: 
427              return 1, "Found [%s]"%search_key, search_key 
428          else: 
429              return -1, "Cannot find parameter [%s] in an upwards search"%key, '' 
 430  
 
431      @apivalidate(0, (is_api('caller_api'), non_empty_str('key'),)) 
433          """
 
434          Retrieve parameter value from server and subscribe to updates to that param. See
 
435          paramUpdate() in the Node API. 
 
436          @param caller_id str: ROS caller id
 
437          @type  caller_id: str
 
438          @param key: parameter to lookup.
 
439          @type  key: str
 
440          @param caller_api: API URI for paramUpdate callbacks.
 
441          @type  caller_api: str
 
442          @return: [code, statusMessage, parameterValue]. If code is not
 
443             1, parameterValue should be ignored. parameterValue is an empty dictionary if the parameter
 
444             has not been set yet.
 
445          @rtype: [int, str, XMLRPCLegalValue]
 
446          """ 
447          key = resolve_name(key, caller_id)         
448          try: 
449              
 
450              
 
451              self.ps_lock.acquire() 
452              val = self.param_server.subscribe_param(key, (caller_id, caller_api)) 
453          finally: 
454              self.ps_lock.release() 
455          return 1, "Subscribed to parameter [%s]"%key, val 
 456  
 
457      @apivalidate(0, (is_api('caller_api'), non_empty_str('key'),)) 
459          """
 
460          Retrieve parameter value from server and subscribe to updates to that param. See
 
461          paramUpdate() in the Node API. 
 
462          @param caller_id str: ROS caller id
 
463          @type  caller_id: str
 
464          @param key: parameter to lookup.
 
465          @type  key: str
 
466          @param caller_api: API URI for paramUpdate callbacks.
 
467          @type  caller_api: str
 
468          @return: [code, statusMessage, numUnsubscribed]. 
 
469             If numUnsubscribed is zero it means that the caller was not subscribed to the parameter.
 
470          @rtype: [int, str, int]
 
471          """         
472          key = resolve_name(key, caller_id)         
473          try: 
474              
 
475              self.ps_lock.acquire() 
476              retval = self.param_server.unsubscribe_param(key, (caller_id, caller_api)) 
477          finally: 
478              self.ps_lock.release() 
479          return 1, "Unsubscribe to parameter [%s]"%key, 1 
 480  
 
481  
 
482      @apivalidate(False, (non_empty_str('key'),)) 
484          """
 
485          Check if parameter is stored on server. 
 
486          @param caller_id str: ROS caller id
 
487          @type  caller_id: str
 
488          @param key: parameter to check
 
489          @type  key: str
 
490          @return: [code, statusMessage, hasParam]
 
491          @rtype: [int, str, bool]
 
492          """ 
493          key = resolve_name(key, caller_id) 
494          if self.param_server.has_param(key): 
495              return 1, key, True 
496          else: 
497              return 1, key, False             
 498  
 
499      @apivalidate([]) 
501          """
 
502          Get list of all parameter names stored on this server.
 
503          This does not adjust parameter names for caller's scope.
 
504          
 
505          @param caller_id: ROS caller id    
 
506          @type  caller_id: str
 
507          @return: [code, statusMessage, parameterNameList]
 
508          @rtype: [int, str, [str]]
 
509          """ 
510          return 1, "Parameter names", self.param_server.get_param_names() 
 511              
 
512      
 
513      
 
514  
 
515 -    def _notify(self, registrations, task, key, value, node_apis): 
 516          """
 
517          Generic implementation of callback notification
 
518          @param registrations: Registrations
 
519          @type  registrations: L{Registrations}
 
520          @param task: task to queue
 
521          @type  task: fn
 
522          @param key: registration key
 
523          @type  key: str
 
524          @param value: value to pass to task
 
525          @type  value: Any
 
526          """ 
527          
 
528          thread_pool = self.thread_pool 
529          if not thread_pool: 
530              return 
531          
 
532          try:             
533              for node_api in node_apis: 
534                  
 
535                  thread_pool.queue_task(node_api, task, (node_api, key, value)) 
536          except KeyError: 
537              _logger.warn('subscriber data stale (key [%s], listener [%s]): node API unknown'%(key, s)) 
 538          
 
540          """
 
541          Notify parameter subscribers of new parameter value
 
542          @param updates [([str], str, any)*]: [(subscribers, param_key, param_value)*]
 
543          @param param_value str: parameter value
 
544          """ 
545          
 
546          thread_pool = self.thread_pool 
547          if not thread_pool: 
548              return 
549  
 
550          for subscribers, key, value in updates: 
551              
 
552              for caller_id, caller_api in subscribers: 
553                  self.thread_pool.queue_task(caller_api, self.param_update_task, (caller_id, caller_api, key, value)) 
 554  
 
556          """
 
557          Contact api.paramUpdate with specified parameters
 
558          @param caller_id: caller ID
 
559          @type  caller_id: str
 
560          @param caller_api: XML-RPC URI of node to contact
 
561          @type  caller_api: str
 
562          @param param_key: parameter key to pass to node
 
563          @type  param_key: str
 
564          @param param_value: parameter value to pass to node
 
565          @type  param_value: str
 
566          """ 
567          mloginfo("paramUpdate[%s]", param_key) 
568          code, _, _ = xmlrpcapi(caller_api).paramUpdate('/master', param_key, param_value) 
569          if code == -1: 
570              try: 
571                  
 
572                  self.ps_lock.acquire() 
573                  
 
574                  matches = self.reg_manager.reverse_lookup(caller_api) 
575                  for m in matches: 
576                      retval = self.param_server.unsubscribe_param(param_key, (m.id, caller_api)) 
577              finally: 
578                  self.ps_lock.release() 
 579  
 
581          """
 
582          Notify subscribers with new publisher list
 
583          @param topic: name of topic
 
584          @type  topic: str
 
585          @param pub_uris: list of URIs of publishers.
 
586          @type  pub_uris: [str]
 
587          """ 
588          self._notify(self.subscribers, publisher_update_task, topic, pub_uris, sub_uris) 
 589  
 
590      
 
591      
 
592  
 
593      @apivalidate(0, ( is_service('service'), is_api('service_api'), is_api('caller_api'))) 
595          """
 
596          Register the caller as a provider of the specified service.
 
597          @param caller_id str: ROS caller id
 
598          @type  caller_id: str
 
599          @param service: Fully-qualified name of service 
 
600          @type  service: str
 
601          @param service_api: Service URI 
 
602          @type  service_api: str
 
603          @param caller_api: XML-RPC URI of caller node 
 
604          @type  caller_api: str
 
605          @return: (code, message, ignore)
 
606          @rtype: (int, str, int)
 
607          """         
608          try: 
609              self.ps_lock.acquire() 
610              self.reg_manager.register_service(service, caller_id, caller_api, service_api) 
611              mloginfo("+SERVICE [%s] %s %s", service, caller_id, caller_api) 
612          finally: 
613              self.ps_lock.release() 
614          return 1, "Registered [%s] as provider of [%s]"%(caller_id, service), 1 
 615  
 
616      @apivalidate(0, (is_service('service'),)) 
618          """
 
619          Lookup all provider of a particular service.
 
620          @param caller_id str: ROS caller id
 
621          @type  caller_id: str
 
622          @param service: fully-qualified name of service to lookup.
 
623          @type: service: str
 
624          @return: (code, message, serviceUrl). service URL is provider's
 
625             ROSRPC URI with address and port.  Fails if there is no provider.
 
626          @rtype: (int, str, str)
 
627          """ 
628          try: 
629              self.ps_lock.acquire() 
630              service_url = self.services.get_service_api(service) 
631          finally: 
632              self.ps_lock.release() 
633          if service_url: 
634              return 1, "rosrpc URI: [%s]"%service_url, service_url 
635          else: 
636              return -1, "no provider", '' 
 637  
 
638      @apivalidate(0, ( is_service('service'), is_api('service_api'))) 
640          """
 
641          Unregister the caller as a provider of the specified service.
 
642          @param caller_id str: ROS caller id
 
643          @type  caller_id: str
 
644          @param service: Fully-qualified name of service
 
645          @type  service: str
 
646          @param service_api: API URI of service to unregister. Unregistration will only occur if current
 
647             registration matches.
 
648          @type  service_api: str
 
649          @return: (code, message, numUnregistered). Number of unregistrations (either 0 or 1).
 
650             If this is zero it means that the caller was not registered as a service provider.
 
651             The call still succeeds as the intended final state is reached.
 
652          @rtype: (int, str, int)
 
653          """ 
654          try: 
655              self.ps_lock.acquire() 
656              retval = self.reg_manager.unregister_service(service, caller_id, service_api) 
657              mloginfo("-SERVICE [%s] %s %s", service, caller_id, service_api) 
658              return retval 
659          finally: 
660              self.ps_lock.release() 
 661  
 
662      
 
663      
 
664  
 
665      @apivalidate(0, ( is_topic('topic'), valid_type_name('topic_type'), is_api('caller_api'))) 
667          """
 
668          Subscribe the caller to the specified topic. In addition to receiving
 
669          a list of current publishers, the subscriber will also receive notifications
 
670          of new publishers via the publisherUpdate API.        
 
671          @param caller_id: ROS caller id
 
672          @type  caller_id: str
 
673          @param topic str: Fully-qualified name of topic to subscribe to. 
 
674          @param topic_type: Datatype for topic. Must be a package-resource name, i.e. the .msg name.
 
675          @type  topic_type: str
 
676          @param caller_api: XML-RPC URI of caller node for new publisher notifications
 
677          @type  caller_api: str
 
678          @return: (code, message, publishers). Publishers is a list of XMLRPC API URIs
 
679             for nodes currently publishing the specified topic.
 
680          @rtype: (int, str, [str])
 
681          """ 
682          
 
683          try: 
684              self.ps_lock.acquire() 
685              self.reg_manager.register_subscriber(topic, caller_id, caller_api) 
686  
 
687              
 
688              
 
689              if not topic in self.topics_types and topic_type != rosgraph.names.ANYTYPE: 
690                  self.topics_types[topic] = topic_type 
691  
 
692              mloginfo("+SUB [%s] %s %s",topic, caller_id, caller_api) 
693              pub_uris = self.publishers.get_apis(topic) 
694          finally: 
695              self.ps_lock.release() 
696          return 1, "Subscribed to [%s]"%topic, pub_uris 
 697  
 
698      @apivalidate(0, (is_topic('topic'), is_api('caller_api'))) 
700          """
 
701          Unregister the caller as a publisher of the topic.
 
702          @param caller_id: ROS caller id
 
703          @type  caller_id: str
 
704          @param topic: Fully-qualified name of topic to unregister.
 
705          @type  topic: str
 
706          @param caller_api: API URI of service to unregister. Unregistration will only occur if current
 
707             registration matches.    
 
708          @type  caller_api: str
 
709          @return: (code, statusMessage, numUnsubscribed). 
 
710            If numUnsubscribed is zero it means that the caller was not registered as a subscriber.
 
711            The call still succeeds as the intended final state is reached.
 
712          @rtype: (int, str, int)
 
713          """ 
714          try: 
715              self.ps_lock.acquire() 
716              retval = self.reg_manager.unregister_subscriber(topic, caller_id, caller_api) 
717              mloginfo("-SUB [%s] %s %s",topic, caller_id, caller_api) 
718              return retval 
719          finally: 
720              self.ps_lock.release() 
 721  
 
722      @apivalidate(0, ( is_topic('topic'), valid_type_name('topic_type'), is_api('caller_api'))) 
724          """
 
725          Register the caller as a publisher the topic.
 
726          @param caller_id: ROS caller id
 
727          @type  caller_id: str
 
728          @param topic: Fully-qualified name of topic to register.
 
729          @type  topic: str
 
730          @param topic_type: Datatype for topic. Must be a
 
731          package-resource name, i.e. the .msg name.
 
732          @type  topic_type: str
 
733          @param caller_api str: ROS caller XML-RPC API URI
 
734          @type  caller_api: str
 
735          @return: (code, statusMessage, subscriberApis).
 
736          List of current subscribers of topic in the form of XMLRPC URIs.
 
737          @rtype: (int, str, [str])
 
738          """ 
739          
 
740          try: 
741              self.ps_lock.acquire() 
742              self.reg_manager.register_publisher(topic, caller_id, caller_api) 
743              
 
744              if topic_type != rosgraph.names.ANYTYPE or not topic in self.topics_types: 
745                  self.topics_types[topic] = topic_type 
746              pub_uris = self.publishers.get_apis(topic) 
747              sub_uris = self.subscribers.get_apis(topic) 
748              self._notify_topic_subscribers(topic, pub_uris, sub_uris) 
749              mloginfo("+PUB [%s] %s %s",topic, caller_id, caller_api) 
750              sub_uris = self.subscribers.get_apis(topic)             
751          finally: 
752              self.ps_lock.release() 
753          return 1, "Registered [%s] as publisher of [%s]"%(caller_id, topic), sub_uris 
 754  
 
755  
 
756      @apivalidate(0, (is_topic('topic'), is_api('caller_api'))) 
758          """
 
759          Unregister the caller as a publisher of the topic.
 
760          @param caller_id: ROS caller id
 
761          @type  caller_id: str
 
762          @param topic: Fully-qualified name of topic to unregister.
 
763          @type  topic: str
 
764          @param caller_api str: API URI of service to
 
765             unregister. Unregistration will only occur if current
 
766             registration matches.
 
767          @type  caller_api: str
 
768          @return: (code, statusMessage, numUnregistered). 
 
769             If numUnregistered is zero it means that the caller was not registered as a publisher.
 
770             The call still succeeds as the intended final state is reached.
 
771          @rtype: (int, str, int)
 
772          """             
773          try: 
774              self.ps_lock.acquire() 
775              retval = self.reg_manager.unregister_publisher(topic, caller_id, caller_api) 
776              if retval[VAL]: 
777                  self._notify_topic_subscribers(topic, self.publishers.get_apis(topic), self.subscribers.get_apis(topic)) 
778              mloginfo("-PUB [%s] %s %s",topic, caller_id, caller_api) 
779          finally: 
780              self.ps_lock.release() 
781          return retval 
 782  
 
783      
 
784      
 
785  
 
786      @apivalidate('', (valid_name('node'),)) 
788          """
 
789          Get the XML-RPC URI of the node with the associated
 
790          name/caller_id.  This API is for looking information about
 
791          publishers and subscribers. Use lookupService instead to lookup
 
792          ROS-RPC URIs.
 
793          @param caller_id: ROS caller id
 
794          @type  caller_id: str
 
795          @param node: name of node to lookup
 
796          @type  node: str
 
797          @return: (code, msg, URI)
 
798          @rtype: (int, str, str)
 
799          """ 
800          try: 
801              self.ps_lock.acquire() 
802              node = self.reg_manager.get_node(node_name) 
803              if node is not None: 
804                  retval = 1, "node api", node.api 
805              else: 
806                  retval = -1, "unknown node [%s]"%node_name, '' 
807          finally: 
808              self.ps_lock.release() 
809          return retval 
 810          
 
811      @apivalidate(0, (empty_or_valid_name('subgraph'),)) 
813          """
 
814          Get list of topics that can be subscribed to. This does not return topics that have no publishers.
 
815          See L{getSystemState()} to get more comprehensive list.
 
816          @param caller_id: ROS caller id
 
817          @type  caller_id: str
 
818          @param subgraph: Restrict topic names to match within the specified subgraph. Subgraph namespace
 
819             is resolved relative to the caller's namespace. Use '' to specify all names.
 
820          @type  subgraph: str
 
821          @return: (code, msg, [[topic1, type1]...[topicN, typeN]])
 
822          @rtype: (int, str, [[str, str],])
 
823          """ 
824          try: 
825              self.ps_lock.acquire() 
826              
 
827              if subgraph and subgraph[-1] != rosgraph.names.SEP: 
828                  subgraph = subgraph + rosgraph.names.SEP 
829              
 
830              
 
831              retval = [[t, self.topics_types[t]] for t in self.publishers.iterkeys() if t.startswith(subgraph)] 
832          finally: 
833              self.ps_lock.release() 
834          return 1, "current topics", retval 
 835      
 
836      @apivalidate([]) 
838          """
 
839          Retrieve list topic names and their types.
 
840          @param caller_id: ROS caller id    
 
841          @type  caller_id: str
 
842          @rtype: (int, str, [[str,str]] )
 
843          @return: (code, statusMessage, topicTypes). topicTypes is a list of [topicName, topicType] pairs.
 
844          """ 
845          try:  
846              self.ps_lock.acquire() 
847              retval = list(self.topics_types.items()) 
848          finally: 
849              self.ps_lock.release() 
850          return 1, "current system state", retval 
 851  
 
852      @apivalidate([[],[], []]) 
854          """
 
855          Retrieve list representation of system state (i.e. publishers, subscribers, and services).
 
856          @param caller_id: ROS caller id    
 
857          @type  caller_id: str
 
858          @rtype: (int, str, [[str,[str]], [str,[str]], [str,[str]]])
 
859          @return: (code, statusMessage, systemState).
 
860  
 
861             System state is in list representation::
 
862               [publishers, subscribers, services].
 
863          
 
864             publishers is of the form::
 
865               [ [topic1, [topic1Publisher1...topic1PublisherN]] ... ]
 
866          
 
867             subscribers is of the form::
 
868               [ [topic1, [topic1Subscriber1...topic1SubscriberN]] ... ]
 
869          
 
870             services is of the form::
 
871               [ [service1, [service1Provider1...service1ProviderN]] ... ]
 
872          """ 
873          edges = [] 
874          try:  
875              self.ps_lock.acquire() 
876              retval = [r.get_state() for r in (self.publishers, self.subscribers, self.services)] 
877          finally: 
878              self.ps_lock.release() 
879          return 1, "current system state", retval 
880