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 optionshandle_input/2- Consume an input message and optionally emit outputs
Optional Callbacks
handle_options/2- React to parameter changes at runtimehandle_info/2,handle_call/3,handle_cast/2,handle_continue/2,terminate/2- Standard GenServer-style callbacksoptions_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_nameis either an atom matching anoutput :nameblock on the estimator, or the conventional:outatom 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
endSingle 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]}.
Summary
Types
The framework-provided init context, delivered as the :estimator_context opt.
Input delivered to handle_input/2. Single-input estimators receive the bare message; multi-input estimators receive a map keyed by input name.
An emitted output: {output_name, message}. :out is the conventional name for single-output estimators.
Callbacks
Handle synchronous calls.
Handle asynchronous casts.
Handle continue instructions.
Handle all other messages.
Consume an input message (or a fanned-in bundle of inputs) and optionally emit output messages.
Handle parameter changes at runtime.
Initialise estimator state from resolved options.
Returns the options schema for this estimator.
Clean up before termination.
Types
@type context() :: BB.Estimator.Context.t()
The framework-provided init context, delivered as the :estimator_context opt.
@type input() :: BB.Message.t() | %{required(atom()) => 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.
@type output() :: {atom(), BB.Message.t()}
An emitted output: {output_name, message}. :out is the conventional name for single-output estimators.
Callbacks
@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 GenServer.handle_call/3, extended with a
{:reply, reply, outputs, state} form that lets a call response also
publish output messages.
@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 GenServer.handle_cast/2. May emit outputs via the
{:reply, outputs, state} form.
@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 GenServer.handle_continue/2. May emit outputs.
@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 GenServer.handle_info/2. May emit outputs via the
{:reply, outputs, state} form - useful for estimators that emit on a
timer.
@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.
@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.
@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- aBB.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.
@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.
Clean up before termination.
Same semantics as GenServer.terminate/2.