Aller au contenu principal

hdds-recording

Record and replay DDS messages with topic filtering, file rotation, and MCAP export.

Overview​

hdds-recording provides two CLI tools for capturing and replaying DDS traffic:

  • hdds-record -- Subscribe to DDS topics and save messages to file
  • hdds-replay -- Read recorded files and republish messages with timing control

Supported formats:

Feature.hdds.mcap
Self-containedYesYes
Indexed seekingYesYes
Type metadataYesYes
Foxglove compatibleNoYes
ROS 2 compatibleNoYes
Minimal dependenciesYesNo

Installation​

cargo install hdds-recording

Or build from source:

cd crates/hdds-recording
cargo build --release

This produces two binaries: hdds-record and hdds-replay.

Quick Start​

Record​

# Record all topics on domain 0
hdds-record --domain 0 --output capture.hdds

# Record specific topics
hdds-record --domain 0 --output capture.hdds --topics "sensor/*,rt/cmd_vel"

# Record with file rotation (100 MB per file)
hdds-record --domain 0 --output capture.hdds --rotate-size 100

# Record for 60 seconds
hdds-record --domain 0 --output capture.hdds --duration 60

Replay​

# Replay at real-time speed
hdds-replay --input capture.hdds

# Replay at 2x speed
hdds-replay --input capture.hdds --speed 2.0

# Replay as fast as possible
hdds-replay --input capture.hdds --speed 0

# Loop playback
hdds-replay --input capture.hdds --loop

# Show recording info without replaying
hdds-replay --input capture.hdds --info-only

# Dry run (iterate without publishing)
hdds-replay --input capture.hdds --dry-run

hdds-record CLI Reference​

hdds-record [OPTIONS]

Options:
-d, --domain <ID> DDS domain ID [default: 0]
-o, --output <FILE> Output file path (.hdds or .mcap)
-t, --topics <PATTERN> Topic filter (comma-separated, supports wildcards)
--exclude-topics <PAT> Topics to exclude (comma-separated)
--types <PATTERN> Type filter (comma-separated)
--description <TEXT> Recording description metadata
--rotate-size <MB> Rotate files by size (megabytes)
--rotate-duration <SEC> Rotate files by duration (seconds)
--max-files <N> Maximum rotated files to keep [default: 0]
--duration <SEC> Recording duration (0 = indefinite) [default: 0]
--log-level <LEVEL> Log level [default: info]
-q, --quiet Minimal output
-h, --help Print help
-V, --version Print version

hdds-replay CLI Reference​

hdds-replay [OPTIONS]

Options:
-i, --input <FILE> Input recording file (.hdds)
-s, --speed <MULT> Playback speed (1.0 = realtime, 0 = unlimited) [default: 1.0]
-l, --loop Loop playback indefinitely
-t, --topics <PATTERN> Topic filter (comma-separated, supports wildcards)
--start <SEC> Start offset in seconds [default: 0]
--end <SEC> End time in seconds (0 = play all) [default: 0]
-d, --domain <ID> DDS domain ID for publishing [default: 0]
--dry-run Iterate without publishing
--info-only Show recording info and exit
--log-level <LEVEL> Log level [default: info]
-q, --quiet Minimal output
-h, --help Print help
-V, --version Print version

Topic Filtering​

Wildcard patterns are supported for both recording and replay:

PatternMatches
sensor/*sensor/temp, sensor/humidity
*/Temperatureroom1/Temperature, sensor/Temperature
*sensor*room/sensor/temp, sensor_data
rt/*/statusrt/robot1/status, rt/node/status
*All topics

File Rotation​

For long recordings, file rotation prevents individual files from growing too large:

# Rotate every 100 MB, keep last 10 files
hdds-record --output capture.hdds --rotate-size 100 --max-files 10

# Rotate every hour
hdds-record --output capture.hdds --rotate-duration 3600

Rotated files are named sequentially: capture_0001.hdds, capture_0002.hdds, etc.

Rotation policies:

TriggerDescription
SizeRotate when file reaches N megabytes
DurationRotate after N seconds
MessagesRotate after N messages (library API only)

Native .hdds Format​

The .hdds format is a compact binary format optimized for DDS recordings:

File Structure​

+----------------------------------------------------------+
| File Header (64 bytes) |
| Magic (8) | Version (4) | Flags (4) | MetaOffset (8) |
| MetaSize (4) | IndexOffset (8) | IndexCount (4) | ... |
+----------------------------------------------------------+
| Segment 0 |
| SegmentHeader (32) | Message[] | CRC32 (4) |
+----------------------------------------------------------+
| ... |
+----------------------------------------------------------+
| Index Table |
| IndexEntry[] (topic_hash, segment_id, offset, count) |
+----------------------------------------------------------+
| Metadata (JSON) |
+----------------------------------------------------------+
  • Magic bytes: HDDSREC\0
  • CRC32 integrity check per segment
  • FNV-1a topic hashing for indexed seeking
  • JSON metadata with topic list, domain ID, hostname, and timestamps

MCAP Export​

With the mcap feature flag, recordings can be exported to MCAP format for use with Foxglove Studio and ROS 2 tools:

# Record directly to MCAP
hdds-record --domain 0 --output capture.mcap

Enable the feature in Cargo.toml:

[dependencies]
hdds-recording = { path = "../hdds/crates/hdds-recording", features = ["mcap"] }

Library API​

The recording and replay functionality is also available as a Rust library:

Recording​

use hdds_recording::{Recorder, RecorderConfig, TopicFilter};

let config = RecorderConfig::new("capture.hdds")
.domain_id(0)
.topic_filter(TopicFilter::include(vec!["sensor/*".into()]))
.description("Test recording");

let mut recorder = Recorder::new(config);
recorder.start()?;

// Record samples
recorder.record_sample(
"sensor/temp",
"SensorData",
"0102030405060708090a0b0c00000302",
1, // sequence number
&payload, // CDR-encoded bytes
0, // QoS hash
)?;

let stats = recorder.stop()?;
println!("Recorded {} messages in {:.1}s", stats.message_count, stats.duration_secs);

Replay​

use hdds_recording::{Player, PlayerConfig, PlaybackSpeed};
use std::time::Duration;

let config = PlayerConfig::new("capture.hdds")
.speed(PlaybackSpeed::Speed(2.0))
.loop_playback(false)
.start_offset(Duration::from_secs(10));

let mut player = Player::new(config);
player.open()?;

while let Some(msg) = player.next()? {
println!("{}: {} bytes", msg.topic_name, msg.payload.len());
}

let stats = player.stats();
println!("Played {} messages", stats.messages_played);