> ## 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.

# Device

> The device handle: lifecycle, data plane, and control plane

`ef::Device` is the single entry point to the SDK. It is move-only (one handle
owns one device session).

Key behavior:

* `open()` claims and configures a live device, caches its identity, and
  normally leaves it ready in `IDLE` (pre-existing device activity may report
  `STREAMING`). The first `grab()` starts the USB or WiFi/UDP data plane.
  MCAP replay creates its reader during `open()`.
* Calls that communicate with the device return `ERROR_CODE` and return data
  through `out` parameters. `get_*` methods without an `ERROR_CODE` do not
  block and return cached values.
* Calls that are invalid for the current `DEVICE_STATE` return
  `INVALID_FUNCTION_CALL` without contacting the device.

## Lifecycle

```cpp theme={null}
static std::vector<DeviceProperties> get_device_list(bool scan_ble = false,
                                                     uint32_t scan_ms = 3000);
```

Discovers attached devices: USB in all cases, and BLE advertisers when
`scan_ble` is set. Static, so no open handle is required.

```cpp theme={null}
ERROR_CODE open(InitParameters params = InitParameters());
bool       is_open() const;
bool       is_authenticated() const;
void       close();
InitParameters get_init_parameters() const;
DEVICE_STATE   get_state() const;
```

`open()` validates the session configuration against the device's enabled
capability menu (`INVALID_RESOLUTION` / `INVALID_FPS` /
`UNSUPPORTED_COMPRESSION`), syncs the device clock, and prepares capture.
`close()` stops any host-file recording and releases the transport. It does
not stop a device-local recording. `get_state()`
returns the last known `DEVICE_STATE` and never blocks.

`is_authenticated()` reports whether this session has supplied a valid control
password. It is meaningful only when the device gates the link, which applies to
any BLE link and to a USB link where `DeviceInformation::usb_locked` is set.
`open()` **succeeds with an incorrect password**, because a gated device still
responds to `info`, `state` and `storage`, which an operator requires when
recovering a device whose password has been lost. Check this value before calling
a gated verb. See [Access Control and Encryption](/device/security).

```cpp theme={null}
bool poll_fault(std::string* reason = nullptr);
```

Refreshes the device state from the firmware state machine, so a subsequent
`get_state()` is also current, and reports whether a fault is latched. A latched
device has entered SAFE and requires a health-gated recovery. Because the
`DEVICE_STATE` enum has no fault value, such a device reports `CLOSED`, so
`poll_fault` is the means of distinguishing a fault-closed device from one that
is simply not open. When `reason` is supplied it receives the most recent anomaly
cause. It is populated both for a latched fault and for an abnormal session end
(for example `disk_full` or `capture_stopped`), and is empty once a session
starts cleanly, so `reason` should be checked even when the return value is
`false`.

## Data plane

```cpp theme={null}
ERROR_CODE grab(RuntimeParameters params = RuntimeParameters());
RuntimeParameters get_runtime_parameters() const;
```

Blocks until the next frame is assembled, for at most `grab_timeout_ms`.
Returns `SUCCESS`, `GRAB_TIMEOUT` (non-fatal; continue the loop),
`CORRUPTED_FRAME` (a lossy frame, delivered only with `return_partial`),
`END_OF_BUFFER` (MCAP replay exhausted), or `COMMUNICATION_ERROR` (the transport
has failed).

```cpp theme={null}
ERROR_CODE retrieve_image(Mat& mat, VIEW view = VIEW::NV12);
```

Decodes and converts the grabbed frame into `mat` in the requested `VIEW`. The
`Mat` owns its buffer. In a build without FFmpeg, the only supported combination
is a `COMPRESSION_MODE::RAW` session retrieved as `VIEW::NV12`; any other
combination returns `UNSUPPORTED_COMPRESSION`.

```cpp theme={null}
ERROR_CODE retrieve_imu(SensorsData& data,
                        TIME_REFERENCE ref = TIME_REFERENCE::IMAGE);
```

Drains queued IMU samples in chronological order.

```cpp theme={null}
Timestamp get_timestamp(TIME_REFERENCE ref = TIME_REFERENCE::CURRENT) const;
```

`IMAGE` = last grabbed frame's capture time (device clock); `CURRENT` = host
wall clock now.

## Identity & health

```cpp theme={null}
DeviceInformation get_device_information() const;    // cached at open()
ERROR_CODE refresh_device_information();             // retake the snapshot
ERROR_CODE health_check(HealthStatus& out, bool deep = false);  // blocks
HealthStatus get_health_status() const;              // last completed sweep
```

`get_device_information()` returns a cached snapshot and performs no device
communication. The snapshot is captured at `open()` and updated by the WiFi
calls, so a handle held open across a network change continues to report the
values captured earlier. Call `refresh_device_information()` to update it, as
`poll_fault()` does for device state, and read the struct again afterwards:
the accessor returns by value, so copies taken earlier are unaffected.

If the transport is unavailable at the time of the refresh, the WiFi association
and the BLE link are reset to unknown rather than retaining their previous
values, so a disconnected device does not continue to report itself as
connected. The health status is retained, because it records a sweep that
completed and clearing it would misreport a healthy device as failed.

## Recording

```cpp theme={null}
ERROR_CODE enable_recording(RecordingParameters params);
ERROR_CODE disable_recording();
RecordingParameters get_recording_parameters() const;

ERROR_CODE get_recording_status(RecordingStatus& out, const std::string& name = "");
ERROR_CODE list_recordings(std::vector<RecordingStatus>& out);
ERROR_CODE get_storage(uint64_t& free_bytes, uint64_t& total_bytes);
ERROR_CODE delete_recording(const std::string& name);
ERROR_CODE download_recording(const std::string& name, const std::string& dest_path);
ERROR_CODE upload_recording(const std::string& name, const std::string& url);
ERROR_CODE stop_upload(const std::string& name);
```

`HOST_FILE` writes the live stream to a local MCAP file. `DEVICE_LOCAL` records
on the M1 and continues across `close()` or a host disconnect. Downloads use the
USB or Bluetooth LE (BLE) control link; uploads use WiFi.

## Updates

```cpp theme={null}
ERROR_CODE check_update(UpdateAvailability& out);
ERROR_CODE update(const std::string& url = "",
                  const std::function<void(const UpdateStatus&)>& on_progress = {});
ERROR_CODE abort_update();
ERROR_CODE get_update_status(UpdateStatus& out);
const std::string& last_error_message() const;
```

`check_update()` issues a single host-side request to the update-check service
and downloads nothing. An empty `url` installs whatever the service offers, an
`https://` URL is downloaded as given, and a **local `.eff` path** is transferred
over the wire, which requires USB. `update()` blocks through apply and reboot,
reporting progress through the callback.

`last_error_message()` returns the reason reported by whichever component refused
the operation, the device or the service. The `ERROR_CODE` provides the category,
while this message is generally the actionable detail.

## WiFi, time, location, admin

```cpp theme={null}
ERROR_CODE wifi_add(const std::string& ssid, const std::string& psk,
                    const std::string& country = "");   // "US" unlocks 5 GHz
ERROR_CODE wifi_remove(const std::string& ssid);
ERROR_CODE wifi_select(const std::string& ssid);
ERROR_CODE scan_wifi_networks(std::vector<WifiNetwork>& out);   // DEVICE_BUSY while recording/streaming

ERROR_CODE set_ble_password(const std::string& old_password,
                            const std::string& new_password);   // control password, both transports

ERROR_CODE sync_time();
ERROR_CODE get_device_time(Timestamp& out);

ERROR_CODE set_location(double latitude, double longitude,
                        double altitude = 0.0, double covariance_diag = 0.0);
ERROR_CODE get_location(Location& out);

ERROR_CODE set_configuration(int width, int height, int fps,
                             COMPRESSION_MODE codec);   // persisted; IDLE only
ERROR_CODE set_configuration(int width, int height, int fps,
                             COMPRESSION_MODE codec, IMU_DATA imu_data);
ERROR_CODE reboot();
```

The `IMU_DATA` overload also selects on-device IMU handling for the session:
`RAW` records uncalibrated samples (the default), `CALIBRATED` applies the stored
`M*S*(x-b)` per sample, and `BOTH` emits both.

## Access control and encryption

```cpp theme={null}
ERROR_CODE set_usb_lock(bool locked, bool session_only = false);
ERROR_CODE set_encryption(bool enabled);
ERROR_CODE get_encryption_key(EncryptionKey& out);
ERROR_CODE create_encryption_key(EncryptionKey& out);
ERROR_CODE delete_encryption_key(const std::string& key_id, EncryptionKey& out);
ERROR_CODE factory_reset();
```

`set_usb_lock` locks or unlocks the USB control plane. Unlocked, the factory
default, USB has full privileges. Locked, it gates exactly as BLE does and
`open()` authenticates with `InitParameters::ble_password`. Changing it in either
direction requires the current password. `session_only` opens an **already
locked** device for the current power session without changing the stored policy,
so the device continues to report `usb_locked` and the override is lost when
power is removed. It is refused on a device that is not locked, and
authentication never sets it implicitly.

`set_encryption` enables or disables at-rest encryption for subsequent
recordings. Existing recordings retain the state they were written with. It
returns `INVALID_FUNCTION_CALL` when no key exists, so `enabled` never indicates
that recordings are being written unencrypted. Read `last_error_message()` for
the device's specific reason for any refusal described below; the `ERROR_CODE`
provides only the category.

`create_encryption_key` generates the device key and returns it. This is the only
occasion on which the key is returned in full, so the caller must retain it: it
decrypts every recording made from that point onward. It is refused with
`INVALID_FUNCTION_CALL` when a key already exists, because replacing one would
render every recording written under it permanently undecryptable. Rotation is
`delete_encryption_key` followed by create.

`delete_encryption_key` requires `key_id` to match the installed key (see
`DeviceInformation::encryption_key_id`), which prevents a caller from destroying
a key it has not identified; a mismatch returns `INVALID_FUNCTION_CALL`. `out`
contains the destroyed key with `present == false`, which is the final
opportunity to retain it for ciphertext already recorded. It requires the `IDLE`
state, since a running session holds the key in memory and would continue
encrypting under it after the call reported it destroyed.

`factory_reset` restores defaults: password, USB unlocked, encryption off, and
WiFi, calibration, capture configuration, recordings and runtime state cleared.
It is ungated on USB only, so that physical possession provides a recovery path
when the password is lost; over BLE it requires the password like any other verb.
It is refused while a capture is active.

<Warning>
  `factory_reset` **destroys the encryption key**. Every recording made under it
  becomes permanently undecryptable, including copies already transferred
  elsewhere. Unlike `delete_encryption_key`, it does not return the key first,
  because it responds unauthenticated over USB and returning a key to an
  unauthenticated caller is the exposure that destroying it removes.
</Warning>

Read the current state from `get_device_information()`. The conceptual model, the
CLI equivalents, and host-side decryption with `ef-decrypt` are covered in
[Access Control and Encryption](/device/security).

## Calibration

```cpp theme={null}
ERROR_CODE set_camera_calibration(const CalibrationParameters& calibration,
                                  int width, int height);
ERROR_CODE set_imu_calibration(const ImuCalibrationParameters& calibration);
ERROR_CODE reset_calibration(bool camera, bool imu = false);
```

All three require the `IDLE` state and take effect on the next capture session.
`set_camera_calibration` persists the Double Sphere intrinsics produced by the
OpenCV calibration tool. Read them back through
`get_device_information().camera_configuration.calibration`.

`CalibrationParameters` also carries two rectification fields:

| Field       | Default | Meaning                                                                                            |
| ----------- | ------- | -------------------------------------------------------------------------------------------------- |
| `rectify`   | `false` | undistort frames on the device using these intrinsics                                              |
| `fov_scale` | `1.0`   | rectified output field of view: `<1` widens (more periphery, black corners), `>1` zooms the center |

The intrinsics are published as recording metadata in either configuration, so a
host consumer can perform rectification instead. With `rectify` enabled, the
device produces rectilinear frames and `fov_scale` determines how much of the
lens field they cover. With it disabled, frames remain raw fisheye under the
double-sphere model.

`set_imu_calibration` persists the IMU field calibration from the host-side
solve, comprising bias, `M*S`, per-sensor noise and temporal terms, the
camera-to-IMU transform, and the time offset. All fields round-trip through
`get_device_information().sensors_configuration`. `reset_calibration` restores
the factory default for the selected sensors. The camera factory default is all
zeros, meaning uncalibrated.
