# `BMI323.Sampler`

GenServer that drains the BMI323's on-chip FIFO and dispatches sample frames
to a subscriber process.

## Why this exists

Polling the BMI323's data-ready bits from the BEAM is unreliable at higher
ODRs: the scheduler can lose samples between polls. The chip's 2 KB FIFO and
hardware interrupt pin solve this by buffering samples and notifying the host
on a watermark threshold. This module wires those features up.

## Configurable FIFO frame format

The sampler accepts any non-empty subset of `:accelerometer`, `:gyroscope`,
`:temperature`, and `:sensor_time` via the `:sources` option. The chip emits
frames in the canonical order
(`accel → gyro → temp → sensor_time`), and `BMI323.Fifo.parse_frames/3` decodes
whichever subset is enabled. See `BMI323.Fifo` for the protocol details.

Each sample is dispatched as a `{BMI323.Sampler, sampler_pid, [frame, ...]}`
message. Frames are maps containing only the enabled sources — see
`t:BMI323.Fifo.frame/0`.

## Usage

    {:ok, i2c} = Wafer.Driver.Circuits.I2C.acquire(bus_name: "i2c-1", address: 0x68)
    {:ok, bmi} = BMI323.acquire(conn: i2c, soft_reset: true)
    {:ok, bmi} = BMI323.configure_accelerometer(bmi, mode: :normal, range: 4, odr: 200)
    {:ok, bmi} = BMI323.configure_gyroscope(bmi, mode: :normal, range: 2000, odr: 200)

    {:ok, int1} = Wafer.Driver.Circuits.GPIO.acquire(pin: 17, direction: :in)

    {:ok, _sampler} =
      BMI323.Sampler.start_link(
        bmi: bmi,
        int1: int1,
        sources: [:accelerometer, :gyroscope, :sensor_time],
        watermark_frames: 8
      )

    receive do
      {BMI323.Sampler, _pid, frames} -> IO.inspect(frames)
    end

# `option`

```elixir
@type option() ::
  {:bmi, BMI323.t()}
  | {:int1, Wafer.Conn.t() | nil}
  | {:subscriber, pid()}
  | {:watermark_frames, pos_integer()}
  | {:sources, [BMI323.Fifo.source()]}
  | {:stop_on_full, boolean()}
  | {:name, GenServer.name()}
```

Options accepted by `start_link/1`.

# `t`

```elixir
@type t() :: %BMI323.Sampler{
  bmi: BMI323.t(),
  frame_words: pos_integer(),
  int1: Wafer.Conn.t() | nil,
  ranges: BMI323.Fifo.ranges(),
  sources: [BMI323.Fifo.source()],
  subscriber: pid(),
  watermark_frames: pos_integer()
}
```

Internal sampler state.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `drain`

```elixir
@spec drain(GenServer.server()) :: {:ok, [BMI323.Fifo.frame()]} | {:error, term()}
```

Synchronously drain the FIFO and return any complete frames. Used in
pull-only mode (no INT1 wired) or when the caller wants the latest state
on demand.

# `flush`

```elixir
@spec flush(GenServer.server()) :: :ok | {:error, term()}
```

Issue a FIFO flush via `FIFO_CTRL`.

# `start_link`

```elixir
@spec start_link([option()]) :: GenServer.on_start()
```

Start a FIFO sampler.

## Options

  * `:bmi` (required) — a `BMI323` struct. The cached ranges on the struct
    must cover every scaled source (`:accelerometer_range` if
    `:accelerometer` is in `:sources`, `:gyroscope_range` for `:gyroscope`).
  * `:int1` (optional) — a `Wafer.GPIO`-implementing connection wired to the
    device's INT1 pin. If absent the sampler runs in pull-only mode; call
    `drain/1` to fetch buffered frames.
  * `:subscriber` (default `self()`) — process to which sample messages are
    delivered.
  * `:watermark_frames` (default `8`) — fire the watermark interrupt after
    this many frames have accumulated in the FIFO.
  * `:sources` (default `[:accelerometer, :gyroscope, :temperature, :sensor_time]`) — list of FIFO
    sources to enable. Order is irrelevant; the chip always emits in
    canonical order.
  * `:stop_on_full` (default `false`) — when `true`, the FIFO stops accepting
    new data on overflow instead of overwriting oldest.
  * `:name` — optional GenServer registration name.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
