# `BB.Estimator`
[🔗](https://github.com/beam-bots/bb/blob/main/lib/bb/estimator.ex#L5)

Behaviour for state estimators in the BB framework.

Estimators consume one or more input message streams and publish derived
state. The same contract covers within-sensor fusion (e.g. an AHRS algorithm
combining gyro and accelerometer from a single IMU into orientation) and
cross-sensor fusion (e.g. an EKF combining IMU and wheel odometry into a
base pose).

Estimators are declared inline in the DSL using the `estimator` entity,
which may nest inside either a `sensor` (single-input form, frame inherited)
or a `link` (cross-sensor form, frame = link).

## Usage

The `use BB.Estimator` macro sets up your module as an estimator callback
module. Your module is NOT a GenServer - the framework provides a wrapper
GenServer (`BB.Estimator.Server`) that delegates to your callbacks and
routes returned messages to the appropriate pubsub paths.

### Required Callbacks

- `init/1` - Initialise estimator state from resolved options
- `handle_input/2` - Consume an input message and optionally emit outputs

### Optional Callbacks

- `handle_options/2` - React to parameter changes at runtime
- `handle_info/2`, `handle_call/3`, `handle_cast/2`, `handle_continue/2`,
  `terminate/2` - Standard GenServer-style callbacks
- `options_schema/0` - Define accepted configuration options

### Reply Shape

Unlike sensors and controllers, estimators emit messages by returning them
from their callbacks rather than calling `BB.publish/3` directly. Each
callback that can emit messages accepts a `{:reply, outputs, state}` reply,
where `outputs` is a list of `{output_name, %BB.Message{}}` tuples.

- `output_name` is either an atom matching an `output :name` block on the
  estimator, or the conventional `:out` atom for single-output estimators.
- Returning an empty list emits nothing - useful for accumulators that
  consume many inputs before producing one output.

### Init Context

The framework injects an `:estimator_context` option carrying a
`BB.Estimator.Context` struct alongside the existing `:bb` option. The
context provides the target frame, the static transforms from each input's
source frame to the target frame, and the estimator's full path.

    defmodule MyEstimator do
      use BB.Estimator,
        options_schema: [
          gain: [type: :float, default: 0.1, doc: "Filter gain"]
        ]

      @impl BB.Estimator
      def init(opts) do
        gain = Keyword.fetch!(opts, :gain)
        context = Keyword.fetch!(opts, :estimator_context)
        {:ok, %{gain: gain, transforms: context.transforms}}
      end

      @impl BB.Estimator
      def handle_input(%BB.Message{} = msg, state) do
        out_msg = compute(msg, state)
        {:reply, [out: out_msg], state}
      end
    end

### Single vs Multi-Input Dispatch

For single-input estimators (sensor-nested, or link-nested with one declared
`input`), `handle_input/2` receives a single `%BB.Message{}`. For multi-input
estimators, it receives a map of `%{input_name => %BB.Message{}}` keyed by
the `input` declaration name, populated by the framework when the driver
input arrives.

### Auto-injected Options

The `:bb` and `:estimator_context` options are auto-injected by the framework
and should NOT appear in `options_schema/0`. The `:bb` option contains
`%{robot: module, path: [atom]}`.

# `context`

```elixir
@type context() :: BB.Estimator.Context.t()
```

The framework-provided init context, delivered as the `:estimator_context` opt.

# `input`

```elixir
@type input() :: BB.Message.t() | %{required(atom()) =&gt; BB.Message.t()}
```

Input delivered to `handle_input/2`. Single-input estimators receive the bare message; multi-input estimators receive a map keyed by input name.

# `output`

```elixir
@type output() :: {atom(), BB.Message.t()}
```

An emitted output: `{output_name, message}`. `:out` is the conventional name for single-output estimators.

# `handle_call`
*optional* 

```elixir
@callback handle_call(request :: term(), from :: GenServer.from(), state :: term()) ::
  {:reply, reply :: term(), new_state :: term()}
  | {:reply, reply :: term(), new_state :: term(),
     timeout() | :hibernate | {:continue, term()}}
  | {:reply, reply :: term(), [output()], new_state :: term()}
  | {:noreply, new_state :: term()}
  | {:noreply, new_state :: term(),
     timeout() | :hibernate | {:continue, term()}}
  | {:stop, reason :: term(), new_state :: term()}
  | {:stop, reason :: term(), reply :: term(), new_state :: term()}
```

Handle synchronous calls.

Same semantics as `c:GenServer.handle_call/3`, extended with a
`{:reply, reply, outputs, state}` form that lets a call response also
publish output messages.

# `handle_cast`
*optional* 

```elixir
@callback handle_cast(request :: term(), state :: term()) ::
  {:reply, [output()], new_state :: term()}
  | {:reply, [output()], new_state :: term(),
     timeout() | :hibernate | {:continue, term()}}
  | {:noreply, new_state :: term()}
  | {:noreply, new_state :: term(),
     timeout() | :hibernate | {:continue, term()}}
  | {:stop, reason :: term(), new_state :: term()}
```

Handle asynchronous casts.

Same semantics as `c:GenServer.handle_cast/2`. May emit outputs via the
`{:reply, outputs, state}` form.

# `handle_continue`
*optional* 

```elixir
@callback handle_continue(continue_arg :: term(), state :: term()) ::
  {:reply, [output()], new_state :: term()}
  | {:reply, [output()], new_state :: term(),
     timeout() | :hibernate | {:continue, term()}}
  | {:noreply, new_state :: term()}
  | {:noreply, new_state :: term(),
     timeout() | :hibernate | {:continue, term()}}
  | {:stop, reason :: term(), new_state :: term()}
```

Handle continue instructions.

Same semantics as `c:GenServer.handle_continue/2`. May emit outputs.

# `handle_info`
*optional* 

```elixir
@callback handle_info(msg :: term(), state :: term()) ::
  {:reply, [output()], new_state :: term()}
  | {:reply, [output()], new_state :: term(),
     timeout() | :hibernate | {:continue, term()}}
  | {:noreply, new_state :: term()}
  | {:noreply, new_state :: term(),
     timeout() | :hibernate | {:continue, term()}}
  | {:stop, reason :: term(), new_state :: term()}
```

Handle all other messages.

Same semantics as `c:GenServer.handle_info/2`. May emit outputs via the
`{:reply, outputs, state}` form - useful for estimators that emit on a
timer.

# `handle_input`

```elixir
@callback handle_input(input(), state :: term()) ::
  {:reply, [output()], new_state :: term()}
  | {:reply, [output()], new_state :: term(),
     timeout() | :hibernate | {:continue, term()}}
  | {:noreply, new_state :: term()}
  | {:noreply, new_state :: term(),
     timeout() | :hibernate | {:continue, term()}}
  | {:stop, reason :: term(), new_state :: term()}
```

Consume an input message (or a fanned-in bundle of inputs) and optionally
emit output messages.

Single-input estimators receive a `%BB.Message{}`. Multi-input estimators
receive a `%{input_name => %BB.Message{}}` map, gathered by the framework
when the configured driver input arrives.

The `{:reply, outputs, state}` return shape publishes each `{name, message}`
in `outputs` to the corresponding output path. Returning `{:noreply, state}`
or `{:reply, [], state}` emits nothing.

# `handle_options`
*optional* 

```elixir
@callback handle_options(new_opts :: keyword(), state :: term()) ::
  {:ok, new_state :: term()} | {:stop, reason :: term()}
```

Handle parameter changes at runtime.

Called when a referenced parameter changes. The `new_opts` contain all
options with the updated parameter value(s) resolved.

Return `{:ok, new_state}` to update state, or `{:stop, reason}` to shut
down.

# `init`

```elixir
@callback init(opts :: keyword()) ::
  {:ok, state :: term()}
  | {:ok, state :: term(), timeout() | :hibernate | {:continue, term()}}
  | {:stop, reason :: term()}
  | :ignore
```

Initialise estimator state from resolved options.

Called with options after parameter references have been resolved. The
framework-injected options are:

- `:bb` - `%{robot: module, path: [atom]}`
- `:estimator_context` - a `BB.Estimator.Context.t()`

Return `{:ok, state}` or `{:ok, state, timeout_or_continue}` on success,
`{:stop, reason}` to abort startup, or `:ignore` to skip this estimator.

# `options_schema`
*optional* 

```elixir
@callback options_schema() :: Spark.Options.t()
```

Returns the options schema for this estimator.

The schema should NOT include the `:bb` or `:estimator_context` options -
both are auto-injected by the framework.

# `terminate`
*optional* 

```elixir
@callback terminate(reason :: term(), state :: term()) :: term()
```

Clean up before termination.

Same semantics as `c:GenServer.terminate/2`.

---

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