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 configurationrecordAll(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 durationmaxFileSizeBytes(number, optional) - Max file size before splittingmaxDurationPerFileSeconds(number, optional) - Max duration per file before splittingcompression(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 topicsnodeNamespace(string, optional) - ROS node namespace (default: '/olo/rosbag')onCompleted(function, optional) - Callback when recording completesonPaused(function, optional) - Callback when recording is pausedonResumed(function, optional) - Callback when recording is resumedonSplit(function, optional) - Callback when recording splits to new file
Returns: Promise<Object> - Recording session information
// 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
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
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
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
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
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 backoptions(object, optional) - Playback configurationplaybackRate(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 completesonPaused(function, optional) - Callback when playback is pausedonResumed(function, optional) - Callback when playback is resumedonStopped(function, optional) - Callback when playback is stopped
Returns: Promise<Object> - Playback session information
// 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
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>
// 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>
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 IDaction(string) - Control action: 'play', 'pause', 'stop'params(object, optional) - Action parameters
Returns: Promise<Object> - Control result
// 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
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:
// 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:
// 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:
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
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:
// 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:
// 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
