Tutorial 9: Record & Replay (C++)

Record all topic data while your robot runs, then replay it offline for debugging. This is the robotics equivalent of a flight recorder.

What You'll Learn

  • Using horus record CLI to capture topic data
  • Using horus replay to play back recorded data
  • Using BlackBox for crash analysis
  • Designing nodes that work with both live and recorded data

Recording Live Data

While your robot is running:

# Record all topics to a file
horus record --output session_001.horus

# Record specific topics only
horus record --topics lidar.scan,cmd_vel,odom --output drive_test.horus

# Record for 60 seconds
horus record --duration 60 --output minute_log.horus

The recording captures every message on every topic with nanosecond timestamps.

Replaying Recorded Data

Play back recorded data — your subscriber nodes see the same messages as during recording:

# Replay at original speed
horus replay session_001.horus

# Replay at 2x speed (for faster analysis)
horus replay session_001.horus --rate 2.0

# Replay specific topics only
horus replay session_001.horus --topics lidar.scan,odom

Designing for Replay

The key pattern: separate your processing nodes from your hardware drivers. Processing nodes subscribe to topics — they don't care if data comes from live hardware or a recording.

// This controller works identically with live OR recorded data
class Controller : public horus::Node {
public:
    Controller() : Node("controller") {
        scan_sub_ = subscribe<horus::msg::LaserScan>("lidar.scan");
        cmd_pub_  = advertise<horus::msg::CmdVel>("cmd_vel");
    }

    void tick() override {
        auto scan = scan_sub_->recv();
        if (!scan) return;

        // This code runs the same whether lidar.scan comes from
        // a real LiDAR or from horus replay
        float min_range = 999.0f;
        for (int i = 0; i < 360; i++) {
            float r = scan->get()->ranges[i];
            if (r > 0.01f && r < min_range) min_range = r;
        }

        horus::msg::CmdVel cmd{};
        cmd.linear = min_range > 0.5f ? 0.3f : 0.0f;
        cmd_pub_->send(cmd);
    }

private:
    horus::Subscriber<horus::msg::LaserScan>* scan_sub_;
    horus::Publisher<horus::msg::CmdVel>* cmd_pub_;
};

BlackBox for Crash Analysis

The BlackBox records events even if the process crashes:

// Record important events during operation
horus::blackbox::record("controller", "Obstacle detected at 0.3m");
horus::blackbox::record("safety", "E-stop triggered");
horus::blackbox::record("motor", "Current spike: 15A");

// After a crash, inspect with CLI:
// horus blackbox --last 100
// horus log --since "5 minutes ago"

Workflow

1. Run robot        → horus record --output test.horus
2. Bug happens      → stop recording
3. Fix controller   → edit code, recompile
4. Test with replay → horus replay test.horus
5. Verify fix       → no bug? ship it

No need to set up hardware again. No need to reproduce the exact scenario. The recording has everything.

Key Takeaways

  • horus record captures all SHM topic data with timestamps
  • horus replay plays it back — subscribers see the same messages
  • Design nodes as pure topic processors — they work with live AND recorded data
  • BlackBox events survive crashes — use for safety-critical logging
  • Replay at different speeds for fast iteration