ROSBag Recording and Playback

The ROSBag recording module provides comprehensive capabilities for recording ROS topics to bag files and playing them back. This is essential for data collection, debugging, testing robot behaviors, and replaying scenarios. The system supports recording specific topics or all topics, with customizable storage formats and quality settings.

Key Features

  • Topic Selection - Record specific topics or all available topics
  • QoS Override Support - Customize Quality of Service settings for better compatibility
  • Storage Format Control - Choose between MCAP and SQLite3 storage formats
  • Compression Options - Support for zstd, lz4, or no compression
  • File Size Management - Automatic file splitting based on size or duration limits
  • Playback Control - Full playback with rate control, looping, and topic filtering
  • Session Management - Manage multiple concurrent recordings and playbacks
  • Completion Callbacks - Get notified when recordings/playbacks complete

Recording Capabilities

Supported Storage Formats:

  • MCAP - Modern, efficient format (recommended when available)
  • SQLite3 - Standard ROS bag format (universal compatibility)

Compression Options:

  • zstd - High compression ratio, good performance
  • lz4 - Fast compression, lower ratio
  • none - No compression, fastest recording

Topic Recording Modes:

  • All Topics - Record everything being published
  • Specific Topics - Record only selected topics for focused data collection

startRecording(options)

Start recording ROS topics to a bag file.

Parameters:

  • options (object) - Recording configuration
    • recordAll (boolean) - Record all topics (default: false)
    • topics (Array) - Specific topics to record (required if recordAll is false)
    • filename (string, optional) - Custom filename (auto-generated if not provided)
    • maxDurationSeconds (number, optional) - Auto-stop after duration
    • maxFileSizeBytes (number, optional) - Max file size before splitting
    • maxDurationPerFileSeconds (number, optional) - Max duration per file before splitting
    • compression (string, optional) - Compression type: 'zstd', 'lz4', 'none' (default: 'zstd')
    • storageFormat (string, optional) - Storage format: 'mcap', 'sqlite3' (default: 'mcap')
    • qosOverrides (string, optional) - YAML string with QoS overrides for specific topics
    • nodeNamespace (string, optional) - ROS node namespace (default: '/olo/rosbag')
    • onCompleted (function, optional) - Callback when recording completes
    • onPaused (function, optional) - Callback when recording is paused
    • onResumed (function, optional) - Callback when recording is resumed
    • onSplit (function, optional) - Callback when recording splits to new file

Returns: Promise<Object> - Recording session information

JS
// Record specific topics with QoS overrides const recording = await oloClient.rosbagRecording.startRecording({ recordAll: false, topics: ['/tf', '/tf_static', '/odom', '/scan', '/camera/image_raw/compressed'], maxDurationSeconds: 30, compression: 'zstd', storageFormat: 'mcap', qosOverrides: ` /scan: durability: volatile reliability: reliable history: keep_last depth: 1 /camera/image_raw/compressed: durability: volatile reliability: best_effort history: keep_last depth: 5 `, onCompleted: (event) => { console.log('🎬 Recording completed!'); console.log(`📁 Recording: ${event.filename}`); console.log(`⏱️ Duration: ${event.duration}`); console.log(`📊 Files: ${event.fileStats.fileCount}`); console.log(`💾 Total Size: ${event.fileStats.totalSizeFormatted}`); }, onSplit: (event) => { console.log(`✂️ Recording split into new file: ${event.recordingId}`); } }); console.log(`Recording started: ${recording.recordingId}`); console.log(`Filename: ${recording.filename}`); // Record all topics with auto-stop const allTopicsRecording = await oloClient.rosbagRecording.startRecording({ recordAll: true, maxDurationSeconds: 60, filename: 'complete_robot_session', compression: 'lz4', onCompleted: (event) => { console.log(`Complete recording saved: ${event.filename}`); } });

stopRecording(recordingId)

Stop a specific recording by its ID.

Parameters:

  • recordingId (string) - ID of the recording to stop

Returns: Promise<Object> - Recording completion information

JS
const result = await oloClient.rosbagRecording.stopRecording(recording.recordingId); console.log(`Recording stopped: ${result.filename}`); console.log(`Duration: ${result.duration}`); console.log(`Files created: ${result.fileStats.fileCount}`);

stopAllRecordings()

Stop all active recordings.

Returns: Promise<Array> - Array of stopped recording information

JS
const results = await oloClient.rosbagRecording.stopAllRecordings(); console.log(`Stopped ${results.length} recording(s)`); results.forEach(result => { if (result.success !== false) { console.log(`📦 ${result.filename} - ${result.fileStats.fileCount} files, ${result.fileStats.totalSizeFormatted}`); } });

getActiveRecordings()

Get list of currently active recordings.

Returns: Promise<Array> - Array of active recording information

JS
const activeRecordings = await oloClient.rosbagRecording.getActiveRecordings(); console.log(`Active recordings: ${activeRecordings.length}`); activeRecordings.forEach(rec => { const topicsText = rec.recordAll ? 'ALL' : rec.topics.slice(0, 3).join(', ') + (rec.topics.length > 3 ? '...' : ''); console.log(`📦 ${rec.recordingId.substring(0, 8)}... - ${topicsText} (${rec.duration}) [${rec.status}]`); });

getRecordedFiles()

Get list of recorded bag files stored on the appliance.

Returns: Promise<Array> - Array of recorded file information

JS
const recordings = await oloClient.rosbagRecording.getRecordedFiles(); console.log(`Found ${recordings.length} recorded bag(s):`); recordings.slice(0, 10).forEach(recording => { const date = new Date(recording.created).toLocaleString(); console.log(`📦 ${recording.name} (${recording.sizeFormatted}, ${recording.fileCount} files) - ${date}`); }); const totalSize = recordings.reduce((sum, r) => sum + r.size, 0); console.log(`Total storage used: ${Math.round(totalSize / 1024 / 1024)} MB`);

deleteRecording(recordingName)

Delete a recorded bag file from the appliance.

Parameters:

  • recordingName (string) - Name of the recording to delete

Returns: Promise<Object> - Deletion confirmation

JS
await oloClient.rosbagRecording.deleteRecording('old_recording_2024-01-15'); console.log('Recording deleted successfully');

Playback Control

The ROSBag playback system allows you to replay recorded data with full control over playback rate, looping, topic selection, and timing.

startPlayback(recordingName, options)

Start playback of a recorded bag file.

Parameters:

  • recordingName (string) - Name of the recording to play back
  • options (object, optional) - Playback configuration
    • playbackRate (number) - Playback speed multiplier (default: 1.0)
    • loop (boolean) - Loop playback continuously (default: false)
    • startOffset (number) - Seconds to skip at beginning (default: 0)
    • topics (Array) - Specific topics to play (empty array = play all topics)
    • onCompleted (function, optional) - Callback when playback completes
    • onPaused (function, optional) - Callback when playback is paused
    • onResumed (function, optional) - Callback when playback is resumed
    • onStopped (function, optional) - Callback when playback is stopped

Returns: Promise<Object> - Playback session information

JS
// Auto-select and play the most recent recording const recordings = await oloClient.rosbagRecording.getRecordedFiles(); const latestRecording = recordings[0]; const playback = await oloClient.rosbagRecording.startPlayback(latestRecording.name, { playbackRate: 1.0, loop: false, startOffset: 5, // Skip first 5 seconds topics: ['/scan', '/odom', '/tf'], // Only play these topics onCompleted: (event) => { console.log('🎬 Playback completed!'); console.log(`📁 Recording: ${event.recordingName}`); console.log(`⏱️ Duration: ${event.duration}`); console.log(`🔄 Reason: ${event.reason}`); }, onPaused: (event) => { console.log(`⏸️ Playback paused: ${event.playbackId}`); }, onResumed: (event) => { console.log(`▶️ Playback resumed: ${event.playbackId}`); } }); console.log(`Playback started: ${playback.playbackId}`); console.log(`Playing: ${playback.recordingName} at ${playback.playbackRate}x speed`); console.log(`Loop: ${playback.loop ? 'enabled' : 'disabled'}`); console.log(`Topics: ${playback.topics.includes('all') ? 'ALL' : playback.topics.join(', ')}`);

stopPlayback(playbackId)

Stop a specific playback session.

Parameters:

  • playbackId (string) - ID of the playback session to stop

Returns: Promise<Object> - Playback completion information

JS
const result = await oloClient.rosbagRecording.stopPlayback(playback.playbackId); console.log(`Playback stopped: ${result.recordingName}`); console.log(`Total duration played: ${result.duration}`);

pauseRecording(recordingId) / resumeRecording(recordingId)

Pause or resume an active recording.

Parameters:

  • recordingId (string) - ID of the recording to pause/resume

Returns: Promise<void>

JS
// Pause active recording await oloClient.rosbagRecording.pauseRecording(recording.recordingId); console.log('Recording paused'); // Resume recording await oloClient.rosbagRecording.resumeRecording(recording.recordingId); console.log('Recording resumed');

splitRecording(recordingId)

Split an active recording into a new file while continuing to record.

Parameters:

  • recordingId (string) - ID of the recording to split

Returns: Promise<void>

JS
await oloClient.rosbagRecording.splitRecording(recording.recordingId); console.log('Recording split into new file');

controlPlayback(playbackId, action, params)

Control playback with various actions.

Parameters:

  • playbackId (string) - Playback session ID
  • action (string) - Control action: 'play', 'pause', 'stop'
  • params (object, optional) - Action parameters

Returns: Promise<Object> - Control result

JS
// Pause playback await oloClient.rosbagRecording.controlPlayback(playbackId, 'pause'); // Resume playback await oloClient.rosbagRecording.controlPlayback(playbackId, 'play'); // Stop playback await oloClient.rosbagRecording.controlPlayback(playbackId, 'stop');

getActivePlayback()

Get list of active playback sessions.

Returns: Promise<Array> - Array of active playback information

JS
const activePlayback = await oloClient.rosbagRecording.getActivePlayback(); console.log(`Active playback sessions: ${activePlayback.length}`); activePlayback.forEach(playback => { const topicsText = playback.topics.includes('all') ? 'ALL' : playback.topics.slice(0, 3).join(', '); console.log(`🎬 ${playback.playbackId.substring(0, 8)}... - ${playback.recordingName} [${playback.status}]`); console.log(` Rate: ${playback.playbackRate}x, Loop: ${playback.loop}, Topics: ${topicsText}`); });

stopAllPlayback()

Stop all active playback sessions.

Returns: Promise<Array> - Array of stopped playback information

Complete ROSBag Workflow Examples

Data Collection Workflow:

JS
// Complete workflow: Record robot session, then replay for analysis async function recordAndAnalyzeSession() { // Step 1: Record robot performing a task console.log('🎬 Starting data collection...'); const recording = await oloClient.rosbagRecording.startRecording({ topics: ['/tf', '/tf_static', '/odom', '/scan', '/cmd_vel', '/camera/image_raw/compressed'], maxDurationSeconds: 60, compression: 'zstd', storageFormat: 'mcap', onCompleted: (event) => { console.log(`✅ Recording completed: ${event.filename}`); console.log(`📊 Captured ${event.fileStats.fileCount} files, ${event.fileStats.totalSizeFormatted}`); } }); // Step 2: Robot performs task (user controls or autonomous behavior) console.log('🤖 Robot is now performing task. Recording in progress...'); console.log(`Recording ID: ${recording.recordingId}`); // Recording will auto-stop after 60 seconds and trigger completion callback } // Step 3: Replay the recorded session for analysis async function replaySession() { // Get the most recent recording const recordings = await oloClient.rosbagRecording.getRecordedFiles(); if (recordings.length === 0) { console.log('No recordings available for playback'); return; } const latestRecording = recordings[0]; console.log(`🎬 Replaying: ${latestRecording.name}`); // Start playback with analysis const playback = await oloClient.rosbagRecording.startPlayback(latestRecording.name, { playbackRate: 0.5, // Slow motion for detailed analysis loop: false, topics: [], // Play all topics onCompleted: (event) => { console.log(`✅ Replay completed: ${event.recordingName}`); console.log('📊 Analysis session finished'); } }); console.log('🔍 Watch robot behavior replay at 0.5x speed for detailed analysis'); return playback; }

Testing and Validation Workflow:

JS
// Automated testing workflow: Record expected behavior, replay for validation async function automatedTestingWorkflow() { try { // Record expected robot behavior console.log('📹 Recording expected behavior...'); const recording = await oloClient.rosbagRecording.startRecording({ recordAll: true, maxDurationSeconds: 30, filename: 'test_baseline_behavior', onCompleted: (event) => { console.log(`Baseline recorded: ${event.filename}`); } }); // Allow robot to perform baseline behavior (30 seconds) // Recording will auto-stop and trigger callback // Later: Replay for validation setTimeout(async () => { console.log('🎮 Replaying baseline for validation...'); const playback = await oloClient.rosbagRecording.startPlayback('test_baseline_behavior', { playbackRate: 1.0, loop: false, onCompleted: (event) => { console.log('✅ Validation replay completed'); console.log('🔍 Compare actual vs expected robot behavior'); } }); }, 35000); // Start replay 35 seconds later } catch (error) { console.error('Testing workflow failed:', error.message); } }

QoS Override Configuration

The ROSBag recording system supports QoS (Quality of Service) overrides to ensure compatibility with topics that have different QoS settings than the default subscriber.

Common QoS Issues:

  • Camera topics often use "best_effort" reliability
  • Sensor data typically needs "volatile" durability
  • Control topics usually require "reliable" reliability

QoS Override Example:

JS
const qosOverrides = ` # Camera topics - often use best_effort /camera/image_raw/compressed: durability: volatile reliability: best_effort history: keep_last depth: 5 # LiDAR data - reliable for accuracy /scan: durability: volatile reliability: reliable history: keep_last depth: 1 # Transform data - reliable and higher queue /tf: durability: volatile reliability: reliable history: keep_last depth: 100 # Static transforms - transient_local for persistence /tf_static: durability: transient_local reliability: reliable history: keep_last depth: 100 `; const recording = await oloClient.rosbagRecording.startRecording({ topics: ['/camera/image_raw/compressed', '/scan', '/tf', '/tf_static'], qosOverrides: qosOverrides, maxDurationSeconds: 60 });

Error Handling

JS
try { const recording = await oloClient.rosbagRecording.startRecording({ topics: ['/scan', '/odom'], maxDurationSeconds: 30 }); } catch (error) { if (error.code === 'RECORDING_ALREADY_ACTIVE') { console.log('⚠️ A recording is already active'); console.log(`Existing recording: ${error.existingRecording.recordingId}`); // Options: stop existing or use different namespace await oloClient.rosbagRecording.stopAllRecordings(); } else if (error.message?.includes('topics')) { console.log('🔧 Topic issue - check that topics exist and are publishing'); } else { console.error('Recording failed:', error.message); } }

Best Practices

1. Storage Management:

JS
// Regular cleanup of old recordings const recordings = await oloClient.rosbagRecording.getRecordedFiles(); const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000); const oldRecordings = recordings.filter(recording => new Date(recording.created) < oneHourAgo ); for (const recording of oldRecordings.slice(0, 5)) { await oloClient.rosbagRecording.deleteRecording(recording.name); console.log(`Deleted old recording: ${recording.name}`); }

2. Topic Selection Strategy:

JS
// Focus on essential topics for smaller files const essentialTopics = ['/tf', '/tf_static', '/odom', '/scan']; const debugTopics = ['/cmd_vel', '/diagnostics']; const visionTopics = ['/camera/image_raw/compressed']; // Different recording strategies const navigationRecording = await oloClient.rosbagRecording.startRecording({ topics: [...essentialTopics, ...debugTopics], filename: 'navigation_debug' }); const visionRecording = await oloClient.rosbagRecording.startRecording({ topics: [...essentialTopics, ...visionTopics], filename: 'vision_analysis' });

3. Format Selection Guidelines:

  • MCAP - Use when available for better performance and features
  • SQLite3 - Use for maximum compatibility across ROS tools
  • zstd compression - Best balance of compression and speed
  • lz4 compression - Use when recording speed is critical
  • No compression - Use for maximum recording performance