00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041 from __future__ import division, print_function
00042
00043 NAME='rostopic'
00044
00045 import cogni_topic_listener
00046
00047 import os
00048 import sys
00049 import math
00050 import socket
00051 import time
00052 import traceback
00053 import yaml
00054 import xmlrpclib
00055
00056 from operator import itemgetter
00057 from urlparse import urlparse
00058
00059 import genpy
00060
00061 import roslib.message
00062 import rosgraph
00063
00064 import rospy
00065
00066 class ROSTopicException(Exception):
00067 """
00068 Base exception class of rostopic-related errors
00069 """
00070 pass
00071 class ROSTopicIOException(ROSTopicException):
00072 """
00073 rostopic errors related to network I/O failures
00074 """
00075 pass
00076
00077 def _check_master():
00078 """
00079 Make sure that master is available
00080 :raises: :exc:`ROSTopicException` If unable to successfully communicate with master
00081 """
00082 try:
00083 rosgraph.Master('/rostopic').getPid()
00084 except socket.error:
00085 raise ROSTopicIOException("Unable to communicate with master!")
00086
00087 def _master_get_topic_types(master):
00088 try:
00089 val = master.getTopicTypes()
00090 except xmlrpclib.Fault:
00091
00092 sys.stderr.write("WARNING: rostopic is being used against an older version of ROS/roscore\n")
00093 val = master.getPublishedTopics('/')
00094 return val
00095
00096 def _sleep(duration):
00097 rospy.rostime.wallsleep(duration)
00098
00099
00100
00101 def msgevalgen(pattern):
00102 """
00103 Generates a function that returns the relevant field (aka 'subtopic') of a Message object
00104 :param pattern: subtopic, e.g. /x. Must have a leading '/' if specified, ``str``
00105 :returns: function that converts a message into the desired value, ``fn(Message) -> value``
00106 """
00107 if not pattern or pattern == '/':
00108 return None
00109 def msgeval(msg):
00110
00111 try:
00112 return eval('msg'+'.'.join(pattern.split('/')))
00113 except AttributeError as e:
00114 sys.stdout.write("no field named [%s]"%pattern+"\n")
00115 return None
00116 return msgeval
00117
00118 def _get_topic_type(topic):
00119 """
00120 subroutine for getting the topic type
00121 :returns: topic type, real topic name and fn to evaluate the message instance
00122 if the topic points to a field within a topic, e.g. /rosout/msg, ``(str, str, fn)``
00123 """
00124 try:
00125 val = _master_get_topic_types(rosgraph.Master('/rostopic'))
00126 except socket.error:
00127 raise ROSTopicIOException("Unable to communicate with master!")
00128
00129
00130 matches = [(t, t_type) for t, t_type in val if t == topic]
00131 if not matches:
00132 matches = [(t, t_type) for t, t_type in val if topic.startswith(t+'/')]
00133
00134 matches.sort(key=itemgetter(0), reverse=True)
00135 if matches:
00136 t, t_type = matches[0]
00137 if t_type == rosgraph.names.ANYTYPE:
00138 return None, None, None
00139 return t_type, t, msgevalgen(topic[len(t):])
00140 else:
00141 return None, None, None
00142
00143
00144
00145 def get_topic_type(topic, blocking=False):
00146 """
00147 Get the topic type.
00148
00149 :param topic: topic name, ``str``
00150 :param blocking: (default False) block until topic becomes available, ``bool``
00151
00152 :returns: topic type, real topic name and fn to evaluate the message instance
00153 if the topic points to a field within a topic, e.g. /rosout/msg. fn is None otherwise. ``(str, str, fn)``
00154 :raises: :exc:`ROSTopicException` If master cannot be contacted
00155 """
00156 topic_type, real_topic, msg_eval = _get_topic_type(topic)
00157 if topic_type:
00158 return topic_type, real_topic, msg_eval
00159 elif blocking:
00160 sys.stderr.write("WARNING: topic [%s] does not appear to be published yet\n"%topic)
00161 while not rospy.is_shutdown():
00162 topic_type, real_topic, msg_eval = _get_topic_type(topic)
00163 if topic_type:
00164 return topic_type, real_topic, msg_eval
00165 else:
00166 _sleep(0.1)
00167 return None, None, None
00168
00169 def get_topic_class(topic, blocking=False):
00170 """
00171 Get the topic message class
00172 :returns: message class for topic, real topic
00173 name, and function for evaluating message objects into the subtopic
00174 (or ``None``). ``(Message, str, str)``
00175 :raises: :exc:`ROSTopicException` If topic type cannot be determined or loaded
00176 """
00177 topic_type, real_topic, msg_eval = get_topic_type(topic, blocking=blocking)
00178 if topic_type is None:
00179 return None, None, None
00180 msg_class = roslib.message.get_message_class(topic_type)
00181 if not msg_class:
00182 raise ROSTopicException("Cannot load message class for [%s]. Are your messages built?"%topic_type)
00183 return msg_class, real_topic, msg_eval
00184
00185
00186
00187 class CallbackEcho(object):
00188 """
00189 Callback instance that can print callback data in a variety of
00190 formats. Used for all variants of rostopic echo
00191 """
00192
00193 def __init__(self, topic, msg_eval=None, plot=False, filter_fn=None,
00194 echo_clear=False, echo_all_topics=False,
00195 offset_time=False, count=None,
00196 field_filter_fn=None):
00197 """
00198 :param plot: if ``True``, echo in plotting-friendly format, ``bool``
00199 :param filter_fn: function that evaluates to ``True`` if message is to be echo'd, ``fn(topic, msg)``
00200 :param echo_all_topics: (optional) if ``True``, echo all messages in bag, ``bool``
00201 :param offset_time: (optional) if ``True``, display time as offset from current time, ``bool``
00202 :param count: number of messages to echo, ``None`` for infinite, ``int``
00203 :param field_filter_fn: filter the fields that are strified for Messages, ``fn(Message)->iter(str)``
00204 """
00205 if topic and topic[-1] == '/':
00206 topic = topic[:-1]
00207 self.topic = topic
00208 self.msg_eval = msg_eval
00209 self.plot = plot
00210 self.filter_fn = filter_fn
00211
00212 self.prefix = ''
00213 self.suffix = '' if not plot else ''
00214
00215 self.echo_all_topics = echo_all_topics
00216 self.offset_time = offset_time
00217
00218
00219 self.done = False
00220 self.max_count = count
00221 self.count = 0
00222
00223
00224 if plot:
00225
00226 self.str_fn = _str_plot
00227 self.sep = ''
00228 else:
00229
00230 self.str_fn = self.custom_strify_message
00231 if echo_clear:
00232 self.prefix = '\033[2J\033[;H'
00233
00234 self.field_filter=field_filter_fn
00235
00236
00237 self.first = True
00238
00239
00240 self.last_topic = None
00241 self.last_msg_eval = None
00242
00243 def custom_strify_message(self, val, indent='', time_offset=None, current_time=None, field_filter=None, type_information=None):
00244
00245 if type_information and type_information.startswith('uint8['):
00246 val = [ord(x) for x in val]
00247 return genpy.message.strify_message(val, indent=indent, time_offset=time_offset, current_time=current_time, field_filter=field_filter)
00248
00249 def callback(self, data, callback_args, current_time=None):
00250 """
00251 Callback to pass to rospy.Subscriber or to call
00252 manually. rospy.Subscriber constructor must also pass in the
00253 topic name as an additional arg
00254 :param data: Message
00255 :param topic: topic name, ``str``
00256 :param current_time: override calculation of current time, :class:`genpy.Time`
00257 """
00258 topic = callback_args['topic']
00259 type_information = callback_args.get('type_information', None)
00260 if self.filter_fn is not None and not self.filter_fn(data):
00261 return
00262
00263 if self.max_count is not None and self.count >= self.max_count:
00264 self.done = True
00265 return
00266
00267 try:
00268 msg_eval = self.msg_eval
00269 if topic == self.topic:
00270 pass
00271 elif self.topic.startswith(topic + '/'):
00272
00273 if topic == self.last_topic:
00274
00275 msg_eval = self.last_msg_eval
00276 else:
00277
00278 self.last_msg_eval = msg_eval = msgevalgen(self.topic[len(topic):])
00279 self.last_topic = topic
00280 elif not self.echo_all_topics:
00281 return
00282
00283 if msg_eval is not None:
00284 data = msg_eval(data)
00285
00286
00287 if data is not None:
00288
00289
00290 self.count += 1
00291 str_output = ""
00292
00293 if self.offset_time:
00294 str_output = self.prefix+self.str_fn(data, time_offset=rospy.get_rostime(),current_time=current_time, field_filter=self.field_filter, type_information=type_information)+self.suffix
00295 else:
00296 str_output = self.prefix+self.str_fn(data,current_time=current_time, field_filter=self.field_filter, type_information=type_information)+self.suffix
00297
00298 cogni_topic_listener.internal_topic_update(topic, str_output)
00299
00300
00301
00302
00303
00304
00305
00306
00307
00308
00309
00310
00311
00312
00313
00314
00315 sys.stdout.flush()
00316
00317 if self.max_count is not None and self.count >= self.max_count:
00318 self.done = True
00319
00320 except IOError:
00321 self.done = True
00322 except:
00323
00324 self.done = True
00325 traceback.print_exc()
00326
00327
00328
00329
00330
00331 def _rostopic_echo(topic, callback_echo, bag_file=None, echo_all_topics=False):
00332 """
00333 Print new messages on topic to screen.
00334
00335 :param topic: topic name, ``str``
00336 :param bag_file: name of bag file to echo messages from or ``None``, ``str``
00337 """
00338
00339
00340 if bag_file:
00341
00342 rospy.rostime.set_rostime_initialized(True)
00343 _rostopic_echo_bag(callback_echo, bag_file)
00344 else:
00345 _check_master()
00346 rospy.init_node(NAME, anonymous=True)
00347 msg_class, real_topic, msg_eval = get_topic_class(topic, blocking=True)
00348 if msg_class is None:
00349
00350 return
00351 callback_echo.msg_eval = msg_eval
00352
00353
00354 type_information = None
00355 if len(topic) > len(real_topic):
00356 subtopic = topic[len(real_topic):]
00357 subtopic = subtopic.strip('/')
00358 if subtopic:
00359 fields = subtopic.split('/')
00360 submsg_class = msg_class
00361 while fields:
00362 field = fields[0].split('[')[0]
00363 del fields[0]
00364 index = submsg_class.__slots__.index(field)
00365 type_information = submsg_class._slot_types[index]
00366 if fields:
00367 submsg_class = roslib.message.get_message_class(type_information)
00368
00369 use_sim_time = rospy.get_param('/use_sim_time', False)
00370 sub = rospy.Subscriber(real_topic, msg_class, callback_echo.callback, {'topic': topic, 'type_information': type_information})
00371
00372 if use_sim_time:
00373
00374
00375 timeout_t = time.time() + 2.
00376 while time.time() < timeout_t and \
00377 callback_echo.count == 0 and \
00378 not rospy.is_shutdown() and \
00379 not callback_echo.done:
00380 _sleep(0.1)
00381
00382 if callback_echo.count == 0 and \
00383 not rospy.is_shutdown() and \
00384 not callback_echo.done:
00385 sys.stderr.write("WARNING: no messages received and simulated time is active.\nIs /clock being published?\n")
00386
00387
00388
00389
00390
00391
00392
00393
00394
00395 def _rostopic_cmd_echo(argv):
00396 def expr_eval(expr):
00397 def eval_fn(m):
00398 return eval(expr)
00399 return eval_fn
00400
00401 args = argv[2:]
00402 from optparse import OptionParser
00403 parser = OptionParser(usage="usage: %prog echo [options] /topic", prog=NAME)
00404 parser.add_option("-b", "--bag",
00405 dest="bag", default=None,
00406 help="echo messages from .bag file", metavar="BAGFILE")
00407 parser.add_option("-p",
00408 dest="plot", default=False,
00409 action="store_true",
00410 help="echo in a plotting friendly format")
00411 parser.add_option("--filter",
00412 dest="filter_expr", default=None,
00413 metavar="FILTER-EXPRESSION",
00414 help="Python expression to filter messages that are printed. Expression can use Python builtins as well as m (the message) and topic (the topic name).")
00415 parser.add_option("--nostr",
00416 dest="nostr", default=False,
00417 action="store_true",
00418 help="exclude string fields")
00419 parser.add_option("--noarr",
00420 dest="noarr", default=False,
00421 action="store_true",
00422 help="exclude arrays")
00423 parser.add_option("-c", "--clear",
00424 dest="clear", default=False,
00425 action="store_true",
00426 help="clear screen before printing next message")
00427 parser.add_option("-a", "--all",
00428 dest="all_topics", default=False,
00429 action="store_true",
00430 help="display all message in bag, only valid with -b option")
00431 parser.add_option("-n",
00432 dest="msg_count", default=None, metavar="COUNT",
00433 help="number of messages to echo")
00434 parser.add_option("--offset",
00435 dest="offset_time", default=False,
00436 action="store_true",
00437 help="display time as offsets from current time (in seconds)")
00438
00439 (options, args) = parser.parse_args(args)
00440 if len(args) > 1:
00441 parser.error("you may only specify one input topic")
00442 if options.all_topics and not options.bag:
00443 parser.error("Display all option is only valid when echoing from bag files")
00444 if options.offset_time and options.bag:
00445 parser.error("offset time option is not valid with bag files")
00446 if options.all_topics:
00447 topic = ''
00448 else:
00449 if len(args) == 0:
00450 parser.error("topic must be specified")
00451 topic = rosgraph.names.script_resolve_name('rostopic', args[0])
00452
00453
00454
00455
00456 filter_fn = None
00457 if options.filter_expr:
00458 filter_fn = expr_eval(options.filter_expr)
00459
00460 try:
00461 msg_count = int(options.msg_count) if options.msg_count else None
00462 except ValueError:
00463 parser.error("COUNT must be an integer")
00464
00465 field_filter_fn = create_field_filter(options.nostr, options.noarr)
00466 callback_echo = CallbackEcho(topic, None, plot=options.plot,
00467 filter_fn=filter_fn,
00468 echo_clear=options.clear, echo_all_topics=options.all_topics,
00469 offset_time=options.offset_time, count=msg_count,
00470 field_filter_fn=field_filter_fn)
00471 try:
00472 _rostopic_echo(topic, callback_echo, bag_file=options.bag)
00473 except socket.error:
00474 sys.stderr.write("Network communication failed. Most likely failed to communicate with master.\n")
00475
00476 def create_field_filter(echo_nostr, echo_noarr):
00477 def field_filter(val):
00478 fields = val.__slots__
00479 field_types = val._slot_types
00480 for f, t in zip(val.__slots__, val._slot_types):
00481 if echo_noarr and '[' in t:
00482 continue
00483 elif echo_nostr and 'string' in t:
00484 continue
00485 yield f
00486 return field_filter
00487
00488
00489
00490 def cogni_topic_echo(topic, callback_echo):
00491 """
00492 Print new messages on topic to screen.
00493
00494 :param topic: topic name, ``str``
00495 :param bag_file: name of bag file to echo messages from or ``None``, ``str``
00496 """
00497
00498
00499
00500 _check_master()
00501 rospy.init_node(NAME, anonymous=True)
00502 msg_class, real_topic, msg_eval = get_topic_class(topic, blocking=True)
00503 if msg_class is None:
00504
00505 return
00506 callback_echo.msg_eval = msg_eval
00507
00508
00509 type_information = None
00510 if len(topic) > len(real_topic):
00511 subtopic = topic[len(real_topic):]
00512 subtopic = subtopic.strip('/')
00513 if subtopic:
00514 fields = subtopic.split('/')
00515 submsg_class = msg_class
00516 while fields:
00517 field = fields[0].split('[')[0]
00518 del fields[0]
00519 index = submsg_class.__slots__.index(field)
00520 type_information = submsg_class._slot_types[index]
00521 if fields:
00522 submsg_class = roslib.message.get_message_class(type_information)
00523
00524 use_sim_time = rospy.get_param('/use_sim_time', False)
00525 sub = rospy.Subscriber(real_topic, msg_class, callback_echo.callback, {'topic': topic, 'type_information': type_information})
00526 _subscribers[topic] = sub
00527 if use_sim_time:
00528
00529
00530 timeout_t = time.time() + 2.
00531 while time.time() < timeout_t and \
00532 callback_echo.count == 0 and \
00533 not rospy.is_shutdown() and \
00534 not callback_echo.done:
00535 _sleep(0.1)
00536
00537 if callback_echo.count == 0 and \
00538 not rospy.is_shutdown() and \
00539 not callback_echo.done:
00540 sys.stderr.write("WARNING: no messages received and simulated time is active.\nIs /clock being published?\n")
00541
00542
00543
00544
00545
00546 _active_listeners = {}
00547 _subscribers = {}
00548
00549 def update_topics():
00550
00551 topics = cogni_topic_listener.internal_get_topics()
00552
00553
00554 for new_topic in topics:
00555 if new_topic not in _active_listeners:
00556 add_topic_listener(new_topic)
00557
00558
00559
00560 for active_topic in _active_listeners.keys():
00561 if active_topic not in topics:
00562 _subscribers[active_topic].unregister()
00563 del _active_listeners[active_topic]
00564 del _subscribers[active_topic]
00565
00566
00567
00568 def add_topic_listener(topic):
00569 callback_echo = CallbackEcho(topic, None)
00570 cogni_topic_echo(topic, callback_echo)
00571 _active_listeners[str(topic)] = callback_echo
00572
00573 def is_interruption_request():
00574 return cogni_topic_listener.internal_interruption_requested()
00575
00576
00577
00578
00579
00580 while True:
00581 _sleep(1)
00582 update_topics()
00583 if (is_interruption_request()):
00584 break