Package rosbag :: Module rosbag_main
[frames] | no frames]

Source Code for Module rosbag.rosbag_main

   1  # Software License Agreement (BSD License) 
   2  # 
   3  # Copyright (c) 2010, 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 __future__ import print_function 
  34   
  35  import optparse 
  36  import os 
  37  import shutil 
  38  import signal 
  39  import subprocess 
  40  import sys 
  41  import time 
  42  try: 
  43      from UserDict import UserDict  # Python 2.x 
  44  except ImportError: 
  45      from collections import UserDict  # Python 3.x 
  46   
  47  import roslib.message 
  48  import roslib.packages 
  49   
  50  from .bag import Bag, Compression, ROSBagException, ROSBagFormatException, ROSBagUnindexedException, ROSBagEncryptNotSupportedException, ROSBagEncryptException 
  51  from .migration import MessageMigrator, fixbag2, checkbag 
  61   
62 -def handle_split(option, opt_str, value, parser):
63 parser.values.split = True 64 if len(parser.rargs) > 0 and parser.rargs[0].isdigit(): 65 print("Use of \"--split <MAX_SIZE>\" has been deprecated. Please use --split --size <MAX_SIZE> or --split --duration <MAX_DURATION>", file=sys.stderr) 66 parser.values.size = int(parser.rargs.pop(0))
67
68 69 -def _stop_process(signum, frame, old_handler, process):
70 process.terminate() 71 if old_handler: 72 old_handler(signum, frame)
73
74 75 -def _send_process_sigint(signum, frame, old_handler, process):
76 process.send_signal(signal.SIGINT) 77 if old_handler: 78 old_handler(signum, frame)
79
80 81 -def record_cmd(argv):
82 parser = optparse.OptionParser(usage="rosbag record TOPIC1 [TOPIC2 TOPIC3 ...]", 83 description="Record a bag file with the contents of specified topics.", 84 formatter=optparse.IndentedHelpFormatter()) 85 86 parser.add_option("-a", "--all", dest="all", default=False, action="store_true", help="record all topics") 87 parser.add_option("-e", "--regex", dest="regex", default=False, action="store_true", help="match topics using regular expressions") 88 parser.add_option("-p", "--publish", dest="publish", default=False, action="store_true", help="publish a msg when the record begin") 89 parser.add_option("-x", "--exclude", dest="exclude_regex", default="", action="store", help="exclude topics matching the follow regular expression (subtracts from -a or regex)") 90 parser.add_option("-q", "--quiet", dest="quiet", default=False, action="store_true", help="suppress console output") 91 parser.add_option("-o", "--output-prefix", dest="prefix", default=None, action="store", help="prepend PREFIX to beginning of bag name (name will always end with date stamp)") 92 parser.add_option("-O", "--output-name", dest="name", default=None, action="store", help="record to bag with name NAME.bag") 93 parser.add_option( "--split", dest="split", default=False, callback=handle_split, action="callback", help="split the bag when maximum size or duration is reached") 94 parser.add_option( "--max-splits", dest="max_splits", type='int', action="store", help="Keep a maximum of N bag files, when reaching the maximum erase the oldest one to keep a constant number of files.", metavar="MAX_SPLITS") 95 parser.add_option( "--size", dest="size", type='int', action="store", help="record a bag of maximum size SIZE MB. (Default: infinite)", metavar="SIZE") 96 parser.add_option( "--duration", dest="duration", type='string',action="store", help="record a bag of maximum duration DURATION in seconds, unless 'm', or 'h' is appended.", metavar="DURATION") 97 parser.add_option("-b", "--buffsize", dest="buffsize", default=256, type='int', action="store", help="use an internal buffer of SIZE MB (Default: %default, 0 = infinite)", metavar="SIZE") 98 parser.add_option("--chunksize", dest="chunksize", default=768, type='int', action="store", help="Advanced. Record to chunks of SIZE KB (Default: %default)", metavar="SIZE") 99 parser.add_option("-l", "--limit", dest="num", default=0, type='int', action="store", help="only record NUM messages on each topic") 100 parser.add_option( "--node", dest="node", default=None, type='string',action="store", help="record all topics subscribed to by a specific node") 101 parser.add_option("-j", "--bz2", dest="compression", default=None, action="store_const", const='bz2', help="use BZ2 compression") 102 parser.add_option("--lz4", dest="compression", action="store_const", const='lz4', help="use LZ4 compression") 103 parser.add_option("--tcpnodelay", dest="tcpnodelay", action="store_true", help="Use the TCP_NODELAY transport hint when subscribing to topics.") 104 parser.add_option("--udp", dest="udp", action="store_true", help="Use the UDP transport hint when subscribing to topics.") 105 106 (options, args) = parser.parse_args(argv) 107 108 if len(args) == 0 and not options.all and not options.node: 109 parser.error("You must specify a topic name or else use the '-a' option.") 110 111 if options.prefix is not None and options.name is not None: 112 parser.error("Can't set both prefix and name.") 113 114 recordpath = roslib.packages.find_node('rosbag', 'record') 115 if not recordpath: 116 parser.error("Cannot find rosbag/record executable") 117 cmd = [recordpath[0]] 118 119 cmd.extend(['--buffsize', str(options.buffsize)]) 120 cmd.extend(['--chunksize', str(options.chunksize)]) 121 122 if options.num != 0: cmd.extend(['--limit', str(options.num)]) 123 if options.quiet: cmd.extend(["--quiet"]) 124 if options.prefix: cmd.extend(["-o", options.prefix]) 125 if options.name: cmd.extend(["-O", options.name]) 126 if options.exclude_regex: cmd.extend(["--exclude", options.exclude_regex]) 127 if options.all: cmd.extend(["--all"]) 128 if options.regex: cmd.extend(["--regex"]) 129 if options.publish: cmd.extend(["--publish"]) 130 if options.compression: cmd.extend(["--%s" % options.compression]) 131 if options.split: 132 if not options.duration and not options.size: 133 parser.error("Split specified without giving a maximum duration or size") 134 cmd.extend(["--split"]) 135 if options.max_splits: 136 cmd.extend(["--max-splits", str(options.max_splits)]) 137 if options.duration: cmd.extend(["--duration", options.duration]) 138 if options.size: cmd.extend(["--size", str(options.size)]) 139 if options.node: 140 cmd.extend(["--node", options.node]) 141 if options.tcpnodelay: cmd.extend(["--tcpnodelay"]) 142 if options.udp: cmd.extend(["--udp"]) 143 144 cmd.extend(args) 145 146 old_handler = signal.signal( 147 signal.SIGTERM, 148 lambda signum, frame: _stop_process(signum, frame, old_handler, process) 149 ) 150 151 old_handler = signal.signal( 152 signal.SIGINT, 153 lambda signum, frame: _send_process_sigint(signum, frame, old_handler, process) 154 ) 155 156 # Better way of handling it than os.execv 157 # This makes sure stdin handles are passed to the process. 158 process = subprocess.Popen(cmd) 159 process.wait()
160
161 162 -def info_cmd(argv):
163 parser = optparse.OptionParser(usage='rosbag info [options] BAGFILE1 [BAGFILE2 BAGFILE3 ...]', 164 description='Summarize the contents of one or more bag files.') 165 parser.add_option('-y', '--yaml', dest='yaml', default=False, action='store_true', help='print information in YAML format') 166 parser.add_option('-k', '--key', dest='key', default=None, action='store', help='print information on the given key') 167 parser.add_option( '--freq', dest='freq', default=False, action='store_true', help='display topic message frequency statistics') 168 (options, args) = parser.parse_args(argv) 169 170 if len(args) == 0: 171 parser.error('You must specify at least 1 bag file.') 172 if options.key and not options.yaml: 173 parser.error('You can only specify key when printing in YAML format.') 174 175 for i, arg in enumerate(args): 176 try: 177 b = Bag(arg, 'r', skip_index=not options.freq) 178 if options.yaml: 179 info = b._get_yaml_info(key=options.key) 180 if info is not None: 181 print(info) 182 else: 183 print(b) 184 b.close() 185 if i < len(args) - 1: 186 print('---') 187 188 except (ROSBagEncryptNotSupportedException, ROSBagEncryptException) as ex: 189 print('ERROR: %s' % str(ex), file=sys.stderr) 190 except ROSBagUnindexedException as ex: 191 print('ERROR bag unindexed: %s. Run rosbag reindex.' % arg, 192 file=sys.stderr) 193 sys.exit(1) 194 except ROSBagException as ex: 195 print('ERROR reading %s: %s' % (arg, str(ex)), file=sys.stderr) 196 sys.exit(1) 197 except IOError as ex: 198 print('ERROR reading %s: %s' % (arg, str(ex)), file=sys.stderr) 199 sys.exit(1)
200
201 202 -def handle_topics(option, opt_str, value, parser):
203 topics = [] 204 for arg in parser.rargs: 205 if arg[:2] == "--" and len(arg) > 2: 206 break 207 if arg[:1] == "-" and len(arg) > 1: 208 break 209 topics.append(arg) 210 parser.values.topics.extend(topics) 211 del parser.rargs[:len(topics)]
212
213 214 -def handle_pause_topics(option, opt_str, value, parser):
215 pause_topics = [] 216 for arg in parser.rargs: 217 if arg[:2] == "--" and len(arg) > 2: 218 break 219 if arg[:1] == "-" and len(arg) > 1: 220 break 221 pause_topics.append(arg) 222 parser.values.pause_topics.extend(pause_topics) 223 del parser.rargs[:len(pause_topics)]
224
225 226 -def play_cmd(argv):
227 parser = optparse.OptionParser(usage="rosbag play BAGFILE1 [BAGFILE2 BAGFILE3 ...]", 228 description="Play back the contents of one or more bag files in a time-synchronized fashion.") 229 parser.add_option("-p", "--prefix", dest="prefix", default='', type='str', help="prefix all output topics") 230 parser.add_option("-q", "--quiet", dest="quiet", default=False, action="store_true", help="suppress console output") 231 parser.add_option("-i", "--immediate", dest="immediate", default=False, action="store_true", help="play back all messages without waiting") 232 parser.add_option("--pause", dest="pause", default=False, action="store_true", help="start in paused mode") 233 parser.add_option("--queue", dest="queue", default=100, type='int', action="store", help="use an outgoing queue of size SIZE (defaults to %default)", metavar="SIZE") 234 parser.add_option("--clock", dest="clock", default=False, action="store_true", help="publish the clock time") 235 parser.add_option("--hz", dest="freq", default=100, type='float', action="store", help="use a frequency of HZ when publishing clock time (default: %default)", metavar="HZ") 236 parser.add_option("-d", "--delay", dest="delay", default=0.2, type='float', action="store", help="sleep SEC seconds after every advertise call (to allow subscribers to connect)", metavar="SEC") 237 parser.add_option("-r", "--rate", dest="rate", default=1.0, type='float', action="store", help="multiply the publish rate by FACTOR", metavar="FACTOR") 238 parser.add_option("-s", "--start", dest="start", default=0.0, type='float', action="store", help="start SEC seconds into the bag files", metavar="SEC") 239 parser.add_option("-u", "--duration", dest="duration", default=None, type='float', action="store", help="play only SEC seconds from the bag files", metavar="SEC") 240 parser.add_option("--skip-empty", dest="skip_empty", default=None, type='float', action="store", help="skip regions in the bag with no messages for more than SEC seconds", metavar="SEC") 241 parser.add_option("-l", "--loop", dest="loop", default=False, action="store_true", help="loop playback") 242 parser.add_option("-k", "--keep-alive", dest="keep_alive", default=False, action="store_true", help="keep alive past end of bag (useful for publishing latched topics)") 243 parser.add_option("--try-future-version", dest="try_future", default=False, action="store_true", help="still try to open a bag file, even if the version number is not known to the player") 244 parser.add_option("--topics", dest="topics", default=[], callback=handle_topics, action="callback", help="topics to play back") 245 parser.add_option("--pause-topics", dest="pause_topics", default=[], callback=handle_pause_topics, action="callback", help="topics to pause on during playback") 246 parser.add_option("--bags", help="bags files to play back from") 247 parser.add_option("--wait-for-subscribers", dest="wait_for_subscribers", default=False, action="store_true", help="wait for at least one subscriber on each topic before publishing") 248 parser.add_option("--rate-control-topic", dest="rate_control_topic", default='', type='str', help="watch the given topic, and if the last publish was more than <rate-control-max-delay> ago, wait until the topic publishes again to continue playback") 249 parser.add_option("--rate-control-max-delay", dest="rate_control_max_delay", default=1.0, type='float', help="maximum time difference from <rate-control-topic> before pausing") 250 251 (options, args) = parser.parse_args(argv) 252 253 if options.bags: 254 args.append(options.bags) 255 256 if len(args) == 0: 257 parser.error('You must specify at least 1 bag file to play back.') 258 259 playpath = roslib.packages.find_node('rosbag', 'play') 260 if not playpath: 261 parser.error("Cannot find rosbag/play executable") 262 cmd = [playpath[0]] 263 264 if options.prefix: 265 cmd.extend(["--prefix", str(options.prefix)]) 266 267 if options.quiet: cmd.extend(["--quiet"]) 268 if options.pause: cmd.extend(["--pause"]) 269 if options.immediate: cmd.extend(["--immediate"]) 270 if options.loop: cmd.extend(["--loop"]) 271 if options.keep_alive: cmd.extend(["--keep-alive"]) 272 if options.try_future: cmd.extend(["--try-future-version"]) 273 if options.wait_for_subscribers: cmd.extend(["--wait-for-subscribers"]) 274 275 if options.clock: 276 cmd.extend(["--clock", "--hz", str(options.freq)]) 277 278 cmd.extend(['--queue', str(options.queue)]) 279 cmd.extend(['--rate', str(options.rate)]) 280 cmd.extend(['--delay', str(options.delay)]) 281 cmd.extend(['--start', str(options.start)]) 282 if options.duration: 283 cmd.extend(['--duration', str(options.duration)]) 284 if options.skip_empty: 285 cmd.extend(['--skip-empty', str(options.skip_empty)]) 286 287 if options.topics: 288 cmd.extend(['--topics'] + options.topics) 289 290 if options.pause_topics: 291 cmd.extend(['--pause-topics'] + options.pause_topics) 292 293 # prevent bag files to be passed as --topics or --pause-topics 294 if options.topics or options.pause_topics: 295 cmd.extend(['--bags']) 296 297 cmd.extend(args) 298 299 if options.rate_control_topic: 300 cmd.extend(['--rate-control-topic', str(options.rate_control_topic)]) 301 302 if options.rate_control_max_delay: 303 cmd.extend(['--rate-control-max-delay', str(options.rate_control_max_delay)]) 304 305 old_handler = signal.signal( 306 signal.SIGTERM, 307 lambda signum, frame: _stop_process(signum, frame, old_handler, process) 308 ) 309 310 old_handler = signal.signal( 311 signal.SIGINT, 312 lambda signum, frame: _send_process_sigint(signum, frame, old_handler, process) 313 ) 314 315 # Better way of handling it than os.execv 316 # This makes sure stdin handles are passed to the process. 317 process = subprocess.Popen(cmd) 318 process.wait()
319
320 321 -def filter_cmd(argv):
322 def expr_eval(expr): 323 return eval("lambda topic, m, t: %s" % expr)
324 325 parser = optparse.OptionParser(usage="""rosbag filter [options] INBAG OUTBAG EXPRESSION 326 327 EXPRESSION can be any Python-legal expression. 328 329 The following variables are available: 330 * topic: name of topic 331 * m: message 332 * t: time of message (t.secs, t.nsecs)""", 333 description='Filter the contents of the bag.') 334 parser.add_option('-p', '--print', action='store', dest='verbose_pattern', default=None, metavar='PRINT-EXPRESSION', help='Python expression to print for verbose debugging. Uses same variables as filter-expression') 335 336 options, args = parser.parse_args(argv) 337 if len(args) == 0: 338 parser.error('You must specify an in bag, an out bag, and an expression.') 339 if len(args) == 1: 340 parser.error('You must specify an out bag and an expression.') 341 if len(args) == 2: 342 parser.error("You must specify an expression.") 343 if len(args) > 3: 344 parser.error("Too many arguments.") 345 346 inbag_filename, outbag_filename, expr = args 347 348 if not os.path.isfile(inbag_filename): 349 print('Cannot locate input bag file [%s]' % inbag_filename, file=sys.stderr) 350 sys.exit(2) 351 352 if os.path.realpath(inbag_filename) == os.path.realpath(outbag_filename): 353 print('Cannot use same file as input and output [%s]' % inbag_filename, file=sys.stderr) 354 sys.exit(3) 355 356 filter_fn = expr_eval(expr) 357 358 outbag = Bag(outbag_filename, 'w') 359 360 try: 361 inbag = Bag(inbag_filename) 362 except (ROSBagEncryptNotSupportedException, ROSBagEncryptException) as ex: 363 print('ERROR: %s' % str(ex), file=sys.stderr) 364 return 365 except ROSBagUnindexedException as ex: 366 print('ERROR bag unindexed: %s. Run rosbag reindex.' % inbag_filename, file=sys.stderr) 367 sys.exit(1) 368 369 try: 370 meter = ProgressMeter(outbag_filename, inbag._uncompressed_size) 371 total_bytes = 0 372 373 if options.verbose_pattern: 374 verbose_pattern = expr_eval(options.verbose_pattern) 375 376 for topic, raw_msg, t, conn_header in inbag.read_messages(raw=True, return_connection_header=True): 377 msg_type, serialized_bytes, md5sum, pos, pytype = raw_msg 378 msg = pytype() 379 msg.deserialize(serialized_bytes) 380 381 if filter_fn(topic, msg, t): 382 print('MATCH', verbose_pattern(topic, msg, t)) 383 outbag.write(topic, msg, t, connection_header=conn_header) 384 else: 385 print('NO MATCH', verbose_pattern(topic, msg, t)) 386 387 total_bytes += len(serialized_bytes) 388 meter.step(total_bytes) 389 else: 390 for topic, raw_msg, t, conn_header in inbag.read_messages(raw=True, return_connection_header=True): 391 msg_type, serialized_bytes, md5sum, pos, pytype = raw_msg 392 msg = pytype() 393 msg.deserialize(serialized_bytes) 394 395 if filter_fn(topic, msg, t): 396 outbag.write(topic, msg, t, connection_header=conn_header) 397 398 total_bytes += len(serialized_bytes) 399 meter.step(total_bytes) 400 401 meter.finish() 402 403 finally: 404 inbag.close() 405 outbag.close() 406
407 -def fix_cmd(argv):
408 parser = optparse.OptionParser(usage='rosbag fix INBAG OUTBAG [EXTRARULES1 EXTRARULES2 ...]', description='Repair the messages in a bag file so that it can be played in the current system.') 409 parser.add_option('-n', '--noplugins', action='store_true', dest='noplugins', help='do not load rulefiles via plugins') 410 parser.add_option('--force', action='store_true', dest='force', help='proceed with migrations, even if not all rules defined') 411 412 (options, args) = parser.parse_args(argv) 413 414 if len(args) < 1: 415 parser.error('You must pass input and output bag files.') 416 if len(args) < 2: 417 parser.error('You must pass an output bag file.') 418 419 inbag_filename = args[0] 420 outbag_filename = args[1] 421 rules = args[2:] 422 423 ext = os.path.splitext(outbag_filename)[1] 424 if ext == '.bmr': 425 parser.error('Input file should be a bag file, not a rule file.') 426 if ext != '.bag': 427 parser.error('Output file must be a bag file.') 428 429 outname = outbag_filename + '.tmp' 430 431 if os.path.exists(outbag_filename): 432 if not os.access(outbag_filename, os.W_OK): 433 print('Don\'t have permissions to access %s' % outbag_filename, file=sys.stderr) 434 sys.exit(1) 435 else: 436 try: 437 file = open(outbag_filename, 'w') 438 file.close() 439 except IOError as e: 440 print('Cannot open %s for writing' % outbag_filename, file=sys.stderr) 441 sys.exit(1) 442 443 if os.path.exists(outname): 444 if not os.access(outname, os.W_OK): 445 print('Don\'t have permissions to access %s' % outname, file=sys.stderr) 446 sys.exit(1) 447 else: 448 try: 449 file = open(outname, 'w') 450 file.close() 451 except IOError as e: 452 print('Cannot open %s for writing' % outname, file=sys.stderr) 453 sys.exit(1) 454 455 if options.noplugins is None: 456 options.noplugins = False 457 458 migrator = MessageMigrator(rules, plugins=not options.noplugins) 459 460 try: 461 migrations = fixbag2(migrator, inbag_filename, outname, options.force) 462 except (ROSBagEncryptNotSupportedException, ROSBagEncryptException) as ex: 463 print('ERROR: %s' % str(ex), file=sys.stderr) 464 return 465 except ROSBagUnindexedException as ex: 466 print('ERROR bag unindexed: %s. Run rosbag reindex.' % inbag_filename, 467 file=sys.stderr) 468 sys.exit(1) 469 470 if len(migrations) == 0: 471 os.rename(outname, outbag_filename) 472 print('Bag migrated successfully.') 473 else: 474 print('Bag could not be migrated. The following migrations could not be performed:') 475 for m in migrations: 476 print_trans(m[0][0].old_class, m[0][-1].new_class, 0) 477 478 if len(m[1]) > 0: 479 print(' %d rules missing:' % len(m[1])) 480 for r in m[1]: 481 print_trans(r.old_class, r.new_class,1) 482 483 print('Try running \'rosbag check\' to create the necessary rule files or run \'rosbag fix\' with the \'--force\' option.') 484 os.remove(outname) 485 sys.exit(1)
486
487 -def check_cmd(argv):
488 parser = optparse.OptionParser(usage='rosbag check BAG [-g RULEFILE] [EXTRARULES1 EXTRARULES2 ...]', description='Determine whether a bag is playable in the current system, or if it can be migrated.') 489 parser.add_option('-g', '--genrules', action='store', dest='rulefile', default=None, help='generate a rulefile named RULEFILE') 490 parser.add_option('-a', '--append', action='store_true', dest='append', help='append to the end of an existing rulefile after loading it') 491 parser.add_option('-n', '--noplugins', action='store_true', dest='noplugins', help='do not load rulefiles via plugins') 492 (options, args) = parser.parse_args(argv) 493 494 if len(args) == 0: 495 parser.error('You must specify a bag file to check.') 496 if options.append and options.rulefile is None: 497 parser.error('Cannot specify -a without also specifying -g.') 498 if options.rulefile is not None: 499 rulefile_exists = os.path.isfile(options.rulefile) 500 if rulefile_exists and not options.append: 501 parser.error('The file %s already exists. Include -a if you intend to append.' % options.rulefile) 502 if not rulefile_exists and options.append: 503 parser.error('The file %s does not exist, and so -a is invalid.' % options.rulefile) 504 505 if options.append: 506 append_rule = [options.rulefile] 507 else: 508 append_rule = [] 509 510 # First check that the bag is not unindexed 511 try: 512 Bag(args[0]) 513 except (ROSBagEncryptNotSupportedException, ROSBagEncryptException) as ex: 514 print('ERROR: %s' % str(ex), file=sys.stderr) 515 return 516 except ROSBagUnindexedException as ex: 517 print('ERROR bag unindexed: %s. Run rosbag reindex.' % args[0], file=sys.stderr) 518 sys.exit(1) 519 520 mm = MessageMigrator(args[1:] + append_rule, not options.noplugins) 521 522 migrations = checkbag(mm, args[0]) 523 524 if len(migrations) == 0: 525 print('Bag file does not need any migrations.') 526 exit(0) 527 528 print('The following migrations need to occur:') 529 all_rules = [] 530 for m in migrations: 531 all_rules.extend(m[1]) 532 533 print_trans(m[0][0].old_class, m[0][-1].new_class, 0) 534 if len(m[1]) > 0: 535 print(" %d rules missing:" % len(m[1])) 536 for r in m[1]: 537 print_trans(r.old_class, r.new_class, 1) 538 539 if options.rulefile is None: 540 if all_rules == []: 541 print("\nAll rules defined. Bag is ready to be migrated") 542 else: 543 print("\nTo generate rules, please run with -g <rulefile>") 544 exit(0) 545 546 output = '' 547 rules_left = mm.filter_rules_unique(all_rules) 548 549 if rules_left == []: 550 print("\nNo additional rule files needed to be generated. %s not created."%(options.rulefile)) 551 exit(0) 552 553 while len(rules_left) > 0: 554 extra_rules = [] 555 for r in rules_left: 556 if r.new_class is None: 557 print('The message type %s appears to have moved. Please enter the type to migrate it to.' % r.old_class._type) 558 new_type = raw_input('>') 559 new_class = roslib.message.get_message_class(new_type) 560 while new_class is None: 561 print("\'%s\' could not be found in your system. Please make sure it is built." % new_type) 562 new_type = raw_input('>') 563 new_class = roslib.message.get_message_class(new_type) 564 new_rule = mm.make_update_rule(r.old_class, new_class) 565 R = new_rule(mm, 'GENERATED.' + new_rule.__name__) 566 R.find_sub_paths() 567 new_rules = [r for r in mm.expand_rules(R.sub_rules) if r.valid == False] 568 extra_rules.extend(new_rules) 569 print('Creating the migration rule for %s requires additional missing rules:' % new_type) 570 for nr in new_rules: 571 print_trans(nr.old_class, nr.new_class,1) 572 output += R.get_class_def() 573 else: 574 output += r.get_class_def() 575 rules_left = mm.filter_rules_unique(extra_rules) 576 f = open(options.rulefile, 'a') 577 f.write(output) 578 f.close() 579 580 print('\nThe necessary rule files have been written to: %s' % options.rulefile)
581
582 -def compress_cmd(argv):
583 parser = optparse.OptionParser(usage='rosbag compress [options] BAGFILE1 [BAGFILE2 ...]', 584 description='Compress one or more bag files.') 585 parser.add_option( '--output-dir', action='store', dest='output_dir', help='write to directory DIR', metavar='DIR') 586 parser.add_option('-f', '--force', action='store_true', dest='force', help='force overwriting of backup file if it exists') 587 parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='suppress noncritical messages') 588 parser.add_option('-j', '--bz2', action='store_const', dest='compression', help='use BZ2 compression', const=Compression.BZ2, default=Compression.BZ2) 589 parser.add_option( '--lz4', action='store_const', dest='compression', help='use lz4 compression', const=Compression.LZ4) 590 (options, args) = parser.parse_args(argv) 591 592 if len(args) < 1: 593 parser.error('You must specify at least one bag file.') 594 595 op = lambda inbag, outbag, quiet: change_compression_op(inbag, outbag, options.compression, options.quiet) 596 597 bag_op(args, False, True, lambda b: False, op, options.output_dir, options.force, options.quiet)
598
599 -def decompress_cmd(argv):
600 parser = optparse.OptionParser(usage='rosbag decompress [options] BAGFILE1 [BAGFILE2 ...]', 601 description='Decompress one or more bag files.') 602 parser.add_option( '--output-dir', action='store', dest='output_dir', help='write to directory DIR', metavar='DIR') 603 parser.add_option('-f', '--force', action='store_true', dest='force', help='force overwriting of backup file if it exists') 604 parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='suppress noncritical messages') 605 606 (options, args) = parser.parse_args(argv) 607 608 if len(args) < 1: 609 parser.error('You must specify at least one bag file.') 610 611 op = lambda inbag, outbag, quiet: change_compression_op(inbag, outbag, Compression.NONE, options.quiet) 612 613 bag_op(args, False, True, lambda b: False, op, options.output_dir, options.force, options.quiet)
614
615 -def reindex_cmd(argv):
616 parser = optparse.OptionParser(usage='rosbag reindex [options] BAGFILE1 [BAGFILE2 ...]', 617 description='Reindexes one or more bag files.') 618 parser.add_option( '--output-dir', action='store', dest='output_dir', help='write to directory DIR', metavar='DIR') 619 parser.add_option('-f', '--force', action='store_true', dest='force', help='force overwriting of backup file if it exists') 620 parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='suppress noncritical messages') 621 622 (options, args) = parser.parse_args(argv) 623 624 if len(args) < 1: 625 parser.error('You must specify at least one bag file.') 626 627 op = lambda inbag, outbag, quiet: reindex_op(inbag, outbag, options.quiet) 628 629 bag_op(args, True, True, lambda b: b.version > 102, op, options.output_dir, options.force, options.quiet)
630
631 -def encrypt_cmd(argv):
632 parser = optparse.OptionParser(usage='rosbag encrypt [options] BAGFILE1 [BAGFILE2 ...]', 633 description='Encrypt one or more bag files.') 634 parser.add_option( '--output-dir', action='store', dest='output_dir', help='write to directory DIR', metavar='DIR') 635 parser.add_option('-f', '--force', action='store_true', dest='force', help='force overwriting of backup file if it exists') 636 parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='suppress noncritical messages') 637 parser.add_option("-p", "--plugin", action='store', dest="plugin", default='rosbag/AesCbcEncryptor', help='encryptor plugin name') 638 parser.add_option("-r", "--param", action='store', dest="param", default='*', help='encryptor plugin parameter') 639 parser.add_option('-j', '--bz2', action='store_const', dest='compression', help='use BZ2 compression', const=Compression.BZ2, default=Compression.NONE) 640 parser.add_option( '--lz4', action='store_const', dest='compression', help='use lz4 compression', const=Compression.LZ4) 641 (options, args) = parser.parse_args(argv) 642 643 if len(args) < 1: 644 parser.error('You must specify at least one bag file.') 645 646 op = lambda inbag, outbag, quiet: change_encryption_op(inbag, outbag, options.plugin, options.param, options.compression, options.quiet) 647 648 bag_op(args, False, True, lambda b: False, op, options.output_dir, options.force, options.quiet)
649
650 -def decrypt_cmd(argv):
651 parser = optparse.OptionParser(usage='rosbag decrypt [options] BAGFILE1 [BAGFILE2 ...]', 652 description='Decrypt one or more bag files.') 653 parser.add_option( '--output-dir', action='store', dest='output_dir', help='write to directory DIR', metavar='DIR') 654 parser.add_option('-f', '--force', action='store_true', dest='force', help='force overwriting of backup file if it exists') 655 parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='suppress noncritical messages') 656 parser.add_option('-j', '--bz2', action='store_const', dest='compression', help='use BZ2 compression', const=Compression.BZ2, default=Compression.NONE) 657 parser.add_option( '--lz4', action='store_const', dest='compression', help='use lz4 compression', const=Compression.LZ4) 658 (options, args) = parser.parse_args(argv) 659 660 if len(args) < 1: 661 parser.error('You must specify at least one bag file.') 662 663 op = lambda inbag, outbag, quiet: change_encryption_op(inbag, outbag, 'rosbag/NoEncryptor', '*', options.compression, options.quiet) 664 # Note the second paramater is True: Python Bag class cannot read index information from encrypted bag files 665 bag_op(args, True, False, lambda b: False, op, options.output_dir, options.force, options.quiet)
666
667 -def bag_op(inbag_filenames, allow_unindexed, open_inbag, copy_fn, op, output_dir=None, force=False, quiet=False):
668 for inbag_filename in inbag_filenames: 669 if open_inbag: 670 # Check we can read the file 671 try: 672 inbag = Bag(inbag_filename, 'r', allow_unindexed=allow_unindexed) 673 except ROSBagUnindexedException: 674 print('ERROR bag unindexed: %s. Run rosbag reindex.' % inbag_filename, file=sys.stderr) 675 continue 676 except (ROSBagException, IOError) as ex: 677 print('ERROR reading %s: %s' % (inbag_filename, str(ex)), file=sys.stderr) 678 continue 679 680 # Determine whether we should copy the bag 681 copy = copy_fn(inbag) 682 683 inbag.close() 684 else: 685 copy = False 686 687 # Determine filename for output bag 688 if output_dir is None: 689 outbag_filename = inbag_filename 690 else: 691 outbag_filename = os.path.join(output_dir, os.path.split(inbag_filename)[1]) 692 693 backup_filename = None 694 if outbag_filename == inbag_filename: 695 # Rename the input bag to ###.orig.###, and open for reading 696 backup_filename = '%s.orig%s' % os.path.splitext(inbag_filename) 697 698 if not force and os.path.exists(backup_filename): 699 if not quiet: 700 print('Skipping %s. Backup path %s already exists.' % (inbag_filename, backup_filename), file=sys.stderr) 701 continue 702 703 try: 704 if copy: 705 shutil.copy(inbag_filename, backup_filename) 706 else: 707 os.rename(inbag_filename, backup_filename) 708 except OSError as ex: 709 print('ERROR %s %s to %s: %s' % ('copying' if copy else 'moving', inbag_filename, backup_filename, str(ex)), file=sys.stderr) 710 continue 711 712 source_filename = backup_filename 713 else: 714 if copy: 715 shutil.copy(inbag_filename, outbag_filename) 716 source_filename = outbag_filename 717 else: 718 source_filename = inbag_filename 719 720 try: 721 if open_inbag: 722 inbag = Bag(source_filename, 'r', allow_unindexed=allow_unindexed) 723 724 # Open the output bag file for writing 725 try: 726 if copy: 727 outbag = Bag(outbag_filename, 'a', allow_unindexed=allow_unindexed) 728 else: 729 outbag = Bag(outbag_filename, 'w') 730 except (ROSBagException, IOError) as ex: 731 print('ERROR writing to %s: %s' % (outbag_filename, str(ex)), file=sys.stderr) 732 inbag.close() 733 continue 734 735 # Perform the operation 736 try: 737 op(inbag, outbag, quiet=quiet) 738 except ROSBagException as ex: 739 print('\nERROR operating on %s: %s' % (source_filename, str(ex)), file=sys.stderr) 740 inbag.close() 741 outbag.close() 742 continue 743 744 outbag.close() 745 inbag.close() 746 else: 747 # Open the output bag file for writing 748 try: 749 if copy: 750 outbag = Bag(outbag_filename, 'a', allow_unindexed=allow_unindexed) 751 else: 752 outbag = Bag(outbag_filename, 'w') 753 except (ROSBagException, IOError) as ex: 754 print('ERROR writing to %s: %s' % (outbag_filename, str(ex)), file=sys.stderr) 755 continue 756 757 # Perform the operation 758 try: 759 op(source_filename, outbag, quiet=quiet) 760 except ROSBagException as ex: 761 print('\nERROR operating on %s: %s' % (source_filename, str(ex)), file=sys.stderr) 762 outbag.close() 763 continue 764 765 outbag.close() 766 767 except KeyboardInterrupt: 768 if backup_filename is not None: 769 try: 770 if copy: 771 os.remove(backup_filename) 772 else: 773 os.rename(backup_filename, inbag_filename) 774 except OSError as ex: 775 print('ERROR %s %s to %s: %s', ('removing' if copy else 'moving', backup_filename, inbag_filename, str(ex)), file=sys.stderr) 776 break 777 778 except (ROSBagException, IOError) as ex: 779 print('ERROR operating on %s: %s' % (inbag_filename, str(ex)), file=sys.stderr)
780
781 -def change_compression_op(inbag, outbag, compression, quiet):
782 outbag.compression = compression 783 784 if quiet: 785 for topic, msg, t, conn_header in inbag.read_messages(raw=True, return_connection_header=True): 786 outbag.write(topic, msg, t, raw=True, connection_header=conn_header) 787 else: 788 meter = ProgressMeter(outbag.filename, inbag._uncompressed_size) 789 790 total_bytes = 0 791 for topic, msg, t, conn_header in inbag.read_messages(raw=True, return_connection_header=True): 792 msg_type, serialized_bytes, md5sum, pos, pytype = msg 793 794 outbag.write(topic, msg, t, raw=True, connection_header=conn_header) 795 796 total_bytes += len(serialized_bytes) 797 meter.step(total_bytes) 798 799 meter.finish()
800
801 -def reindex_op(inbag, outbag, quiet):
802 if inbag.version == 102: 803 if quiet: 804 try: 805 for offset in inbag.reindex(): 806 pass 807 except: 808 pass 809 810 for (topic, msg, t, conn_header) in inbag.read_messages(return_connection_header=True): 811 outbag.write(topic, msg, t, connection_header=conn_header) 812 else: 813 meter = ProgressMeter(outbag.filename, inbag.size) 814 try: 815 for offset in inbag.reindex(): 816 meter.step(offset) 817 except: 818 pass 819 meter.finish() 820 821 meter = ProgressMeter(outbag.filename, inbag.size) 822 for (topic, msg, t, conn_header) in inbag.read_messages(return_connection_header=True): 823 outbag.write(topic, msg, t, connection_header=conn_header) 824 meter.step(inbag._file.tell()) 825 meter.finish() 826 else: 827 if quiet: 828 try: 829 for offset in outbag.reindex(): 830 pass 831 except (ROSBagEncryptNotSupportedException, ROSBagEncryptException) as ex: 832 raise 833 except: 834 pass 835 else: 836 meter = ProgressMeter(outbag.filename, outbag.size) 837 try: 838 for offset in outbag.reindex(): 839 meter.step(offset) 840 except (ROSBagEncryptNotSupportedException, ROSBagEncryptException) as ex: 841 raise 842 except: 843 pass 844 meter.finish()
845
846 -def change_encryption_op(inbag, outbag, plugin, param, compression, quiet):
847 # Output file must be closed before written by the encrypt process 848 outbag.close() 849 850 encryptpath = roslib.packages.find_node('rosbag', 'encrypt') 851 if not encryptpath: 852 parser.error("Cannot find rosbag/encrypt executable") 853 cmd = [encryptpath[0]] 854 if type(inbag) is str: 855 cmd.extend([inbag]) 856 else: 857 cmd.extend([inbag.filename]) 858 cmd.extend(['-o', outbag.filename]) 859 cmd.extend(['-p', plugin]) 860 cmd.extend(['-r', param]) 861 if compression == 'bz2': 862 cmd.extend(['-j']) 863 elif compression == 'lz4': 864 cmd.extend(['--lz4']) 865 if quiet: 866 cmd.extend(['-q']) 867 868 old_handler = signal.signal( 869 signal.SIGTERM, 870 lambda signum, frame: _stop_process(signum, frame, old_handler, process) 871 ) 872 873 process = subprocess.Popen(cmd) 874 process.wait()
875
876 -class RosbagCmds(UserDict):
877 - def __init__(self):
878 UserDict.__init__(self) 879 self._description = {} 880 self['help'] = self.help_cmd
881
882 - def add_cmd(self, name, function, description):
883 self[name] = function 884 self._description[name] = description
885
886 - def get_valid_cmds(self):
887 str = "Available subcommands:\n" 888 for k in sorted(self.keys()): 889 str += " %s " % k 890 if k in self._description.keys(): 891 str +="\t%s" % self._description[k] 892 str += "\n" 893 return str
894
895 - def help_cmd(self,argv):
896 argv = [a for a in argv if a != '-h' and a != '--help'] 897 898 if len(argv) == 0: 899 print('Usage: rosbag <subcommand> [options] [args]') 900 print() 901 print("A bag is a file format in ROS for storing ROS message data. The rosbag command can record, replay and manipulate bags.") 902 print() 903 print(self.get_valid_cmds()) 904 print('For additional information, see http://wiki.ros.org/rosbag') 905 print() 906 return 907 908 cmd = argv[0] 909 if cmd in self: 910 self[cmd](['-h']) 911 else: 912 print("Unknown command: '%s'" % cmd, file=sys.stderr) 913 print(self.get_valid_cmds(), file=sys.stderr)
914
915 -class ProgressMeter(object):
916 - def __init__(self, path, bytes_total, refresh_rate=1.0):
917 self.path = path 918 self.bytes_total = bytes_total 919 self.refresh_rate = refresh_rate 920 921 self.elapsed = 0.0 922 self.update_elapsed = 0.0 923 self.bytes_read = 0.0 924 925 self.start_time = time.time() 926 927 self._update_progress()
928
929 - def step(self, bytes_read, force_update=False):
930 self.bytes_read = bytes_read 931 self.elapsed = time.time() - self.start_time 932 933 if force_update or self.elapsed - self.update_elapsed > self.refresh_rate: 934 self._update_progress() 935 self.update_elapsed = self.elapsed
936
937 - def _update_progress(self):
938 max_path_len = self.terminal_width() - 37 939 path = self.path 940 if len(path) > max_path_len: 941 path = '...' + self.path[-max_path_len + 3:] 942 943 bytes_read_str = self._human_readable_size(float(self.bytes_read)) 944 bytes_total_str = self._human_readable_size(float(self.bytes_total)) 945 946 if self.bytes_read < self.bytes_total: 947 complete_fraction = float(self.bytes_read) / self.bytes_total 948 pct_complete = int(100.0 * complete_fraction) 949 950 if complete_fraction > 0.0: 951 eta = self.elapsed * (1.0 / complete_fraction - 1.0) 952 eta_min, eta_sec = eta / 60, eta % 60 953 if eta_min > 99: 954 eta_str = '--:--' 955 else: 956 eta_str = '%02d:%02d' % (eta_min, eta_sec) 957 else: 958 eta_str = '--:--' 959 960 progress = '%-*s %3d%% %8s / %8s %s ETA' % (max_path_len, path, pct_complete, bytes_read_str, bytes_total_str, eta_str) 961 else: 962 progress = '%-*s 100%% %19s %02d:%02d ' % (max_path_len, path, bytes_total_str, self.elapsed / 60, self.elapsed % 60) 963 964 print('\r', progress, end='') 965 sys.stdout.flush()
966
967 - def _human_readable_size(self, size):
968 multiple = 1024.0 969 for suffix in ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']: 970 size /= multiple 971 if size < multiple: 972 return '%.1f %s' % (size, suffix) 973 974 raise ValueError('number too large')
975
976 - def finish(self):
977 self.step(self.bytes_total, force_update=True) 978 print()
979 980 @staticmethod
981 - def terminal_width():
982 """Estimate the width of the terminal""" 983 width = 0 984 try: 985 import struct, fcntl, termios 986 s = struct.pack('HHHH', 0, 0, 0, 0) 987 x = fcntl.ioctl(1, termios.TIOCGWINSZ, s) 988 width = struct.unpack('HHHH', x)[1] 989 except (IOError, ImportError): 990 pass 991 if width <= 0: 992 try: 993 width = int(os.environ['COLUMNS']) 994 except: 995 pass 996 if width <= 0: 997 width = 80 998 999 return width
1000
1001 -def rosbagmain(argv=None):
1002 cmds = RosbagCmds() 1003 cmds.add_cmd('record', record_cmd, "Record a bag file with the contents of specified topics.") 1004 cmds.add_cmd('info', info_cmd, 'Summarize the contents of one or more bag files.') 1005 cmds.add_cmd('play', play_cmd, "Play back the contents of one or more bag files in a time-synchronized fashion.") 1006 cmds.add_cmd('check', check_cmd, 'Determine whether a bag is playable in the current system, or if it can be migrated.') 1007 cmds.add_cmd('fix', fix_cmd, 'Repair the messages in a bag file so that it can be played in the current system.') 1008 cmds.add_cmd('filter', filter_cmd, 'Filter the contents of the bag.') 1009 cmds.add_cmd('compress', compress_cmd, 'Compress one or more bag files.') 1010 cmds.add_cmd('decompress', decompress_cmd, 'Decompress one or more bag files.') 1011 cmds.add_cmd('reindex', reindex_cmd, 'Reindexes one or more bag files.') 1012 if sys.platform != 'win32': 1013 cmds.add_cmd('encrypt', encrypt_cmd, 'Encrypt one or more bag files.') 1014 cmds.add_cmd('decrypt', decrypt_cmd, 'Decrypt one or more bag files.') 1015 1016 if argv is None: 1017 argv = sys.argv 1018 1019 if '-h' in argv or '--help' in argv: 1020 argv = [a for a in argv if a != '-h' and a != '--help'] 1021 argv.insert(1, 'help') 1022 1023 if len(argv) > 1: 1024 cmd = argv[1] 1025 else: 1026 cmd = 'help' 1027 1028 try: 1029 if cmd in cmds: 1030 cmds[cmd](argv[2:]) 1031 else: 1032 cmds['help']([cmd]) 1033 except KeyboardInterrupt: 1034 pass
1035