> ## Documentation Index
> Fetch the complete documentation index at: https://docs.efference.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Recording

> Create host-file or device-local MCAP recordings

Host-file and device-local recordings use the same MCAP schema and replay
through the same API.

## Two targets

|                          | `HOST_FILE`                    | `DEVICE_LOCAL`                |
| ------------------------ | ------------------------------ | ----------------------------- |
| Written to               | a `.mcap` path on your machine | the device's internal storage |
| Requires                 | live data plane (USB or UDP)   | control link only             |
| Survives host disconnect | no                             | **yes**                       |
| Typical use              | development, debugging         | field data collection         |

## Host-file recording

Write the live stream to a local MCAP file:

```cpp theme={null}
RecordingParameters rp;
rp.target = RECORDING_TARGET::HOST_FILE;
rp.path   = "session.mcap";
dev.enable_recording(rp);

while (dev.grab() == ERROR_CODE::SUCCESS) {
    // Each grab writes video and drains all queued IMU into the MCAP.
}

dev.disable_recording();
```

While recording, the SDK drains the IMU on every `grab()` so the file carries
all queued samples even when `retrieve_imu()` is never called. If the process
stops calling `grab()`, no new host frames are recorded. `close()` finalizes
an active host file.

## Device-local recording

Record to the M1, disconnect the host, and retrieve the file later:

```cpp theme={null}
RecordingParameters rp;
rp.target = RECORDING_TARGET::DEVICE_LOCAL;
rp.name   = "run_42";          // "" has the device generate a name
dev.enable_recording(rp);
// safe to close() / disconnect here; recording continues
```

An empty name has the M1 generate `rec_<utc>_<seq>`. Device-local recording
cannot start while another recording, upload, update, or conflicting health
sweep is active.

Manage sessions over any control link (USB or BLE):

```cpp theme={null}
std::vector<RecordingStatus> all;
dev.list_recordings(all);

RecordingStatus rs;
dev.get_recording_status(rs);              // "" = the active session

dev.download_recording("run_42", "run_42.mcap");  // pull over USB/BLE
dev.delete_recording("run_42");

uint64_t free_b, total_b;
dev.get_storage(free_b, total_b);          // device storage headroom
```

`download_recording()` uses the control link in chunks and does not require
WiFi. A failed transfer leaves the partial file at the destination, and a
re-run resumes it after verifying it belongs to the same recording; the file
is not a valid MCAP until a run returns `SUCCESS`. `delete_recording()`
returns immediately and reclaims large files in the background.

Deleting the session that is currently recording returns `DEVICE_BUSY`,
indicating that the call can be retried once the session stops rather than that
it failed outright. When a recording call is refused, `last_error_message()`
reports the device's specific reason; the `ERROR_CODE` provides only the
category.

## Stop reasons and power loss

A recording continues until it is stopped, until storage reaches the reserve
(10% of the recording store, at which point the session finalizes cleanly on its
own), or until power is lost.

`RecordingStatus::stopped_reason` says why a completed session ended: `USER`,
`DISK_FULL`, `WRITE_ERROR`, or `INTERRUPTED` for a power cut. It reads
`UNSPECIFIED` while recording and from firmware older than v00.09.16.

After a power loss the M1 recovers the interrupted session on its next boot.
An unencrypted recording is salvaged into a normal, listed, downloadable MCAP
covering everything flushed up to roughly 250 ms before the cut. An encrypted one
cannot be repaired on the device (the ciphertext is opaque to it), so it is listed with
`RecordingStatus::partial` set and served as-is: `download_recording()` and
uploads move the partial bytes, and `ef-decrypt` recovers everything up to the
cut with a truncated-tail warning.

## Encrypted recordings

Recordings can be AES-256-GCM encrypted at rest under a per-device key.
`RecordingStatus::encrypted` reports whether a stored recording is, read off the
container on disk rather than from the current setting, so it stays correct for
recordings written before that setting last changed.

Neither `download_recording()` nor an upload decrypts: the bytes move as they are
stored, so an encrypted recording arrives as ciphertext and needs the key before
any MCAP reader will open it. Decrypt on the host with `ef-decrypt`. Setting up a
key, and reading a file back, are covered in [Access Control and
Encryption](/device/security).

## Uploading from the device

With WiFi provisioned, the M1 can upload a stopped recording with HTTP PUT:

```cpp theme={null}
dev.upload_recording("run_42", "https://bucket.s3.amazonaws.com/...");
// poll get_recording_status(rs, "run_42"): rs.upload, rs.upload_bytes_sent
dev.stop_upload("run_42");   // cancel
```

The URL must use HTTP or HTTPS and accept a PUT of the complete file.
`upload_recording()` starts a background transfer and returns after the M1
accepts the job.

## Location metadata

Each recording carries a `LocationFix`. Set the persistent device location
once with `dev.set_location(lat, lon)`, or override per-session via
`RecordingParameters`:

```cpp theme={null}
RecordingParameters rp;
rp.target = RECORDING_TARGET::DEVICE_LOCAL;
rp.name = "run_42";
rp.has_location = true;
rp.location = {37.7749, -122.4194, 0.0, 0.0};
dev.enable_recording(rp);
```

The per-session override does not change the persistent device location.
