Package rospy :: Module client

Source Code for Module rospy.client

  1  # Software License Agreement (BSD License) 
  2  # 
  3  # Copyright (c) 2008, 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  # Revision $Id$ 
 34   
 35  """ 
 36  Additional ROS client API methods. 
 37  """ 
 38   
 39  import logging 
 40  import os 
 41  import socket 
 42  import struct 
 43  import sys 
 44  import time 
 45  import random 
 46  import yaml 
 47   
 48  import rosgraph 
 49  import rosgraph.names 
 50   
 51  import roslib 
 52   
 53  import rospy.core 
 54  from rospy.core import logwarn, loginfo, logerr, logdebug 
 55  import rospy.exceptions 
 56  import rospy.names 
 57  import rospy.rostime 
 58   
 59  import rospy.impl.init 
 60  import rospy.impl.rosout  
 61  import rospy.impl.simtime 
 62   
 63  TIMEOUT_READY = 15.0 #seconds 
 64   
 65  # log level constants 
 66  from rosgraph_msgs.msg import Log 
 67  from roscpp.srv import GetLoggers, GetLoggersResponse, SetLoggerLevel, SetLoggerLevelResponse 
 68  from roscpp.msg import Logger 
 69  from rospy.impl.tcpros_service import Service 
 70  DEBUG = Log.DEBUG 
 71  INFO = Log.INFO 
 72  WARN = Log.WARN 
 73  ERROR = Log.ERROR 
 74  FATAL = Log.FATAL 
 75   
76 -def myargv(argv=None):
77 """ 78 Remove ROS remapping arguments from sys.argv arguments. 79 @return: copy of sys.argv with ROS remapping arguments removed 80 @rtype: [str] 81 """ 82 if argv is None: 83 argv = sys.argv 84 return [a for a in argv if not rosgraph.names.REMAP in a]
85
86 -def load_command_line_node_params(argv):
87 """ 88 Load node param mappings (aka private parameters) encoded in 89 command-line arguments, e.g. _foo:=bar. See also rosgraph.names.load_mappings. 90 @param argv: command-line arguments 91 @param argv: [str] 92 @return: param->value remappings. 93 @rtype: {str: val} 94 @raises: ROSInitException 95 """ 96 try: 97 mappings = {} 98 for arg in argv: 99 if rosgraph.names.REMAP in arg: 100 src, dst = [x.strip() for x in arg.split(rosgraph.names.REMAP)] 101 if src and dst: 102 if len(src) > 1 and src[0] == '_' and src[1] != '_': 103 mappings[src[1:]] = yaml.load(dst) 104 return mappings 105 except Exception as e: 106 raise rospy.exceptions.ROSInitException("invalid command-line parameters: %s"%(str(e)))
107
108 -def on_shutdown(h):
109 """ 110 Register function to be called on shutdown. This function will be 111 called before Node begins teardown. 112 @param h: Function with zero args to be called on shutdown. 113 @type h: fn() 114 """ 115 rospy.core.add_client_shutdown_hook(h)
116
117 -def spin():
118 """ 119 Blocks until ROS node is shutdown. Yields activity to other threads. 120 @raise ROSInitException: if node is not in a properly initialized state 121 """ 122 123 if not rospy.core.is_initialized(): 124 raise rospy.exceptions.ROSInitException("client code must call rospy.init_node() first") 125 logdebug("node[%s, %s] entering spin(), pid[%s]", rospy.core.get_caller_id(), rospy.core.get_node_uri(), os.getpid()) 126 try: 127 while not rospy.core.is_shutdown(): 128 rospy.rostime.wallsleep(0.5) 129 except KeyboardInterrupt: 130 logdebug("keyboard interrupt, shutting down") 131 rospy.core.signal_shutdown('keyboard interrupt')
132 133 _logging_level_names = { 134 logging.DEBUG: 'DEBUG', 135 logging.INFO: 'INFO', 136 logging.WARNING: 'WARN', 137 logging.ERROR: 'ERROR', 138 logging.CRITICAL: 'FATAL', 139 } 140
141 -def _get_loggers(request):
142 """ 143 ROS service handler to get the levels of all active loggers. 144 """ 145 ret = GetLoggersResponse() 146 for n in logging.Logger.manager.loggerDict.keys(): 147 level = logging.getLogger(n).getEffectiveLevel() 148 level = _logging_level_names[level] 149 ret.loggers.append(Logger(n, level)) 150 return ret
151 152 _names_to_logging_levels = { 153 'DEBUG': logging.DEBUG, 154 'INFO': logging.INFO, 155 'WARN': logging.WARNING, 156 'ERROR': logging.ERROR, 157 'FATAL': logging.CRITICAL, 158 } 159
160 -def _set_logger_level(request):
161 """ 162 ROS service handler to set the logging level for a particular logger 163 """ 164 level = request.level.upper() 165 if level in _names_to_logging_levels: 166 logger = logging.getLogger(request.logger) 167 logger.setLevel(_names_to_logging_levels[level]) 168 else: 169 logging.getLogger('rospy').error("Bad logging level: %s"%level) 170 ret = SetLoggerLevelResponse() 171 return ret
172
173 -def _init_node_params(argv, node_name):
174 """ 175 Uploads private params to the parameter server. Private params are specified 176 via command-line remappings. 177 178 @raises: ROSInitException 179 """ 180 181 # #1027: load in param name mappings 182 params = load_command_line_node_params(argv) 183 for param_name, param_value in params.items(): 184 logdebug("setting param %s to %s"%(param_name, param_value)) 185 set_param(rosgraph.names.PRIV_NAME + param_name, param_value)
186 187 _init_node_args = None 188
189 -def init_node(name, argv=None, anonymous=False, log_level=None, disable_rostime=False, disable_rosout=False, disable_signals=False, xmlrpc_port=0, tcpros_port=0):
190 """ 191 Register client node with the master under the specified name. 192 This MUST be called from the main Python thread unless 193 disable_signals is set to True. Duplicate calls to init_node are 194 only allowed if the arguments are identical as the side-effects of 195 this method are not reversible. 196 197 @param name: Node's name. This parameter must be a base name, 198 meaning that it cannot contain namespaces (i.e. '/') 199 @type name: str 200 201 @param argv: Command line arguments to this program, including 202 remapping arguments (default: sys.argv). If you provide argv 203 to init_node(), any previously created rospy data structure 204 (Publisher, Subscriber, Service) will have invalid 205 mappings. It is important that you call init_node() first if 206 you wish to provide your own argv. 207 @type argv: [str] 208 209 @param anonymous: if True, a name will be auto-generated for the 210 node using name as the base. This is useful when you wish to 211 have multiple instances of the same node and don't care about 212 their actual names (e.g. tools, guis). name will be used as 213 the stem of the auto-generated name. NOTE: you cannot remap 214 the name of an anonymous node. 215 @type anonymous: bool 216 217 @param log_level: log level for sending message to /rosout and log 218 file, which is INFO by default. For convenience, you may use 219 rospy.DEBUG, rospy.INFO, rospy.ERROR, rospy.WARN, rospy.FATAL 220 @type log_level: int 221 222 @param disable_signals: If True, rospy will not register its own 223 signal handlers. You must set this flag if (a) you are unable 224 to call init_node from the main thread and/or you are using 225 rospy in an environment where you need to control your own 226 signal handling (e.g. WX). If you set this to True, you should 227 call rospy.signal_shutdown(reason) to initiate clean shutdown. 228 229 NOTE: disable_signals is overridden to True if 230 roslib.is_interactive() is True. 231 232 @type disable_signals: bool 233 234 @param disable_rostime: for internal testing only: suppresses 235 automatic subscription to rostime 236 @type disable_rostime: bool 237 238 @param disable_rosout: for internal testing only: suppress 239 auto-publication of rosout 240 @type disable_rostime: bool 241 242 @param xmlrpc_port: If provided, it will use this port number for the client 243 XMLRPC node. 244 @type xmlrpc_port: int 245 246 @param tcpros_port: If provided, the TCPROS server will listen for 247 connections on this port 248 @type tcpros_port: int 249 250 @raise ROSInitException: if initialization/registration fails 251 @raise ValueError: if parameters are invalid (e.g. name contains a namespace or is otherwise illegal) 252 """ 253 if argv is None: 254 argv = sys.argv 255 else: 256 # reload the mapping table. Any previously created rospy data 257 # structure does *not* reinitialize based on the new mappings. 258 rospy.names.reload_mappings(argv) 259 260 # this test can be eliminated once we change from warning to error in the next check 261 if rosgraph.names.SEP in name: 262 raise ValueError("namespaces are not allowed in node names") 263 if not rosgraph.names.is_legal_base_name(name): 264 import warnings 265 warnings.warn("'%s' is not a legal ROS base name. This may cause problems with other ROS tools"%name, stacklevel=2) 266 267 global _init_node_args 268 269 # #972: allow duplicate init_node args if calls are identical 270 # NOTE: we don't bother checking for node name aliases (e.g. 'foo' == '/foo'). 271 if _init_node_args: 272 if _init_node_args != (name, argv, anonymous, log_level, disable_rostime, disable_signals): 273 raise rospy.exceptions.ROSException("rospy.init_node() has already been called with different arguments: "+str(_init_node_args)) 274 else: 275 return #already initialized 276 277 # for scripting environments, we don't want to use the ROS signal 278 # handlers 279 disable_signals = disable_signals or roslib.is_interactive() 280 _init_node_args = (name, argv, anonymous, log_level, disable_rostime, disable_signals) 281 282 if not disable_signals: 283 # NOTE: register_signals must be called from main thread 284 rospy.core.register_signals() # add handlers for SIGINT/etc... 285 else: 286 logdebug("signal handlers for rospy disabled") 287 288 # check for name override 289 mappings = rospy.names.get_mappings() 290 if '__name' in mappings: 291 # use rosgraph version of resolve_name to avoid remapping 292 name = rosgraph.names.resolve_name(mappings['__name'], rospy.core.get_caller_id()) 293 if anonymous: 294 logdebug("[%s] WARNING: due to __name setting, anonymous setting is being changed to false"%name) 295 anonymous = False 296 297 if anonymous: 298 # not as good as a uuid/guid, but more readable. can't include 299 # hostname as that is not guaranteed to be a legal ROS name 300 name = "%s_%s_%s"%(name, os.getpid(), int(time.time()*1000)) 301 302 resolved_node_name = rospy.names.resolve_name(name) 303 rospy.core.configure_logging(resolved_node_name) 304 # #1810 305 rospy.names.initialize_mappings(resolved_node_name) 306 307 logger = logging.getLogger("rospy.client") 308 logger.info("init_node, name[%s], pid[%s]", resolved_node_name, os.getpid()) 309 310 # node initialization blocks until registration with master 311 node = rospy.impl.init.start_node(os.environ, resolved_node_name, port=xmlrpc_port, tcpros_port=tcpros_port) 312 rospy.core.set_node_uri(node.uri) 313 rospy.core.add_shutdown_hook(node.shutdown) 314 315 if rospy.core.is_shutdown(): 316 logger.warn("aborting node initialization as shutdown has been triggered") 317 raise rospy.exceptions.ROSInitException("init_node interrupted before it could complete") 318 319 # upload private params (set via command-line) to parameter server 320 _init_node_params(argv, name) 321 322 rospy.core.set_initialized(True) 323 324 if not disable_rosout: 325 rospy.impl.rosout.init_rosout() 326 rospy.impl.rosout.load_rosout_handlers(log_level) 327 328 if not disable_rostime: 329 if not rospy.impl.simtime.init_simtime(): 330 raise rospy.exceptions.ROSInitException("Failed to initialize time. Please check logs for additional details") 331 else: 332 rospy.rostime.set_rostime_initialized(True) 333 334 logdebug("init_node, name[%s], pid[%s]", resolved_node_name, os.getpid()) 335 # advertise logging level services 336 Service('~get_loggers', GetLoggers, _get_loggers) 337 Service('~set_logger_level', SetLoggerLevel, _set_logger_level)
338 339 340 #_master_proxy is a MasterProxy wrapper 341 _master_proxy = None 342
343 -def get_master(env=os.environ):
344 """ 345 Get a remote handle to the ROS Master. 346 This method can be called independent of running a ROS node, 347 though the ROS_MASTER_URI must be declared in the environment. 348 349 @return: ROS Master remote object 350 @rtype: L{rospy.MasterProxy} 351 @raise Exception: if server cannot be located or system cannot be 352 initialized 353 """ 354 global _master_proxy 355 if _master_proxy is not None: 356 return _master_proxy 357 _master_proxy = rospy.msproxy.MasterProxy(rosgraph.get_master_uri()) 358 return _master_proxy
359 360 ######################################################### 361 # Topic helpers 362
363 -def get_published_topics(namespace='/'):
364 """ 365 Retrieve list of topics that the master is reporting as being published. 366 367 @return: List of topic names and types: [[topic1, type1]...[topicN, typeN]] 368 @rtype: [[str, str]] 369 """ 370 code, msg, val = get_master().getPublishedTopics(namespace) 371 if code != 1: 372 raise rospy.exceptions.ROSException("unable to get published topics: %s"%msg) 373 return val
374
375 -class _WFM(object):
376 - def __init__(self):
377 self.msg = None
378 - def cb(self, msg):
379 if self.msg is None: 380 self.msg = msg
381
382 -def wait_for_message(topic, topic_type, timeout=None):
383 """ 384 Receive one message from topic. 385 386 This will create a new subscription to the topic, receive one message, then unsubscribe. 387 388 @param topic: name of topic 389 @type topic: str 390 @param topic_type: topic type 391 @type topic_type: L{rospy.Message} class 392 @param timeout: timeout time in seconds 393 @type timeout: double 394 @return: Message 395 @rtype: L{rospy.Message} 396 @raise ROSException: if specified timeout is exceeded 397 @raise ROSInterruptException: if shutdown interrupts wait 398 """ 399 wfm = _WFM() 400 s = None 401 try: 402 s = rospy.topics.Subscriber(topic, topic_type, wfm.cb) 403 if timeout is not None: 404 timeout_t = time.time() + timeout 405 while not rospy.core.is_shutdown() and wfm.msg is None: 406 rospy.rostime.wallsleep(0.01) 407 if time.time() >= timeout_t: 408 raise rospy.exceptions.ROSException("timeout exceeded while waiting for message on topic %s"%topic) 409 410 else: 411 while not rospy.core.is_shutdown() and wfm.msg is None: 412 rospy.rostime.wallsleep(0.01) 413 finally: 414 if s is not None: 415 s.unregister() 416 if rospy.core.is_shutdown(): 417 raise rospy.exceptions.ROSInterruptException("rospy shutdown") 418 return wfm.msg
419 420 421 ######################################################### 422 # Param Server Access 423 424 _param_server = None
425 -def _init_param_server():
426 """ 427 Initialize parameter server singleton 428 """ 429 global _param_server 430 if _param_server is None: 431 _param_server = get_master() #in the future param server will be a service
432 433 # class and singleton to distinguish whether or not user has passed us a default value
434 -class _Unspecified(object): pass
435 _unspecified = _Unspecified() 436
437 -def get_param(param_name, default=_unspecified):
438 """ 439 Retrieve a parameter from the param server 440 441 NOTE: this method is not thread-safe. 442 443 @param default: (optional) default value to return if key is not set 444 @type default: any 445 @return: parameter value 446 @rtype: XmlRpcLegalValue 447 @raise ROSException: if parameter server reports an error 448 @raise KeyError: if value not set and default is not given 449 """ 450 try: 451 _init_param_server() 452 return _param_server[param_name] #MasterProxy does all the magic for us 453 except KeyError: 454 if default != _unspecified: 455 return default 456 else: 457 raise
458
459 -def get_param_names():
460 """ 461 Retrieve list of parameter names. 462 463 NOTE: this method is not thread-safe. 464 465 @return: parameter names 466 @rtype: [str] 467 @raise ROSException: if parameter server reports an error 468 """ 469 _init_param_server() 470 code, msg, val = _param_server.getParamNames() #MasterProxy does all the magic for us 471 if code != 1: 472 raise rospy.exceptions.ROSException("Unable to retrieve parameter names: %s"%msg) 473 else: 474 return val
475
476 -def set_param(param_name, param_value):
477 """ 478 Set a parameter on the param server 479 480 NOTE: this method is not thread-safe. 481 482 @param param_name: parameter name 483 @type param_name: str 484 @param param_value: parameter value 485 @type param_value: XmlRpcLegalValue 486 @raise ROSException: if parameter server reports an error 487 """ 488 # #2202 489 if not rosgraph.names.is_legal_name(param_name): 490 import warnings 491 warnings.warn("'%s' is not a legal ROS graph resource name. This may cause problems with other ROS tools"%param_name, stacklevel=2) 492 493 _init_param_server() 494 _param_server[param_name] = param_value #MasterProxy does all the magic for us
495
496 -def search_param(param_name):
497 """ 498 Search for a parameter on the param server 499 500 NOTE: this method is not thread-safe. 501 502 @param param_name: parameter name 503 @type param_name: str 504 @return: key of matching parameter or None if no matching parameter. 505 @rtype: str 506 @raise ROSException: if parameter server reports an error 507 """ 508 _init_param_server() 509 return _param_server.search_param(param_name)
510
511 -def delete_param(param_name):
512 """ 513 Delete a parameter on the param server 514 515 NOTE: this method is not thread-safe. 516 517 @param param_name: parameter name 518 @type param_name: str 519 @raise KeyError: if parameter is not set 520 @raise ROSException: if parameter server reports an error 521 """ 522 _init_param_server() 523 del _param_server[param_name] #MasterProxy does all the magic for us
524
525 -def has_param(param_name):
526 """ 527 Test if parameter exists on the param server 528 529 NOTE: this method is not thread-safe. 530 531 @param param_name: parameter name 532 @type param_name: str 533 @raise ROSException: if parameter server reports an error 534 """ 535 _init_param_server() 536 return param_name in _param_server #MasterProxy does all the magic for us
537