BB.Sensor behaviour (bb v0.15.0)
View SourceBehaviour and API for sensors in the BB framework.
This module serves two purposes:
- Behaviour - Defines callbacks for sensor implementations
- API - Provides functions for working with sensors
Behaviour
Sensors read from hardware or other sources and publish messages. They can be attached at the robot level, to links, or to joints.
Usage
The use BB.Sensor macro sets up your module as a sensor callback module.
Your module is NOT a GenServer - the framework provides a wrapper GenServer
(BB.Sensor.Server) that delegates to your callbacks.
Required Callbacks
init/1- Initialise sensor state from resolved options
Optional Callbacks
disarm/1- Make hardware safe (only for sensors with active hardware)handle_options/2- React to parameter changes at runtimehandle_call/3,handle_cast/2,handle_info/2- Standard GenServer-style callbackshandle_continue/2,terminate/2- Lifecycle callbacksoptions_schema/0- Define accepted configuration options
Options Schema
If your sensor accepts configuration options, pass them via :options_schema:
defmodule MyTemperatureSensor do
use BB.Sensor,
options_schema: [
bus: [type: :string, required: true, doc: "I2C bus name"],
address: [type: :integer, required: true, doc: "I2C device address"],
poll_interval_ms: [type: :pos_integer, default: 1000, doc: "Poll interval"]
]
@impl BB.Sensor
def init(opts) do
bus = Keyword.fetch!(opts, :bus)
address = Keyword.fetch!(opts, :address)
bb = Keyword.fetch!(opts, :bb)
{:ok, %{bus: bus, address: address, bb: bb}}
end
endFor sensors that don't need configuration, omit :options_schema:
defmodule SimpleSensor do
use BB.Sensor
@impl BB.Sensor
def init(opts) do
{:ok, %{bb: opts[:bb]}}
end
endParameter References
Options can reference parameters for runtime-adjustable configuration:
sensor :temp, {MyTempSensor, poll_interval: param([:sensors, :poll_rate])}When the parameter changes, handle_options/2 is called with the new resolved
options. Override it to update your state accordingly.
Auto-injected Options
The :bb option is automatically provided and should NOT be included in your
options_schema. It contains %{robot: module, path: [atom]}.
Safety Registration
Most sensors don't require safety callbacks since they only read data.
If your sensor controls hardware that needs to be disabled on disarm
(e.g., a spinning LIDAR), implement the optional disarm/1 callback:
defmodule MyHardwareSensor do
use BB.Sensor
@impl BB.Sensor
def init(opts), do: {:ok, %{}}
@impl BB.Sensor
def disarm(opts), do: stop_hardware(opts)
endWhen disarm/1 is implemented, the framework automatically registers your
sensor with BB.Safety.
Summary
Callbacks
Make the hardware safe.
Handle synchronous calls.
Handle asynchronous casts.
Handle continue instructions.
Handle all other messages.
Handle parameter changes at runtime.
Initialise sensor state from resolved options.
Returns the options schema for this sensor.
Clean up before termination.
Callbacks
Make the hardware safe.
Called with the opts provided at registration. Must work without GenServer state. This callback is optional for sensors - only implement it if your sensor controls hardware that needs to be disabled on disarm (e.g., a spinning LIDAR).
@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()}} | {: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.
@callback handle_cast(request :: term(), state :: 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.
@callback handle_continue(continue_arg :: term(), state :: 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.
@callback handle_info(msg :: term(), state :: 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.
@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 sensor state from resolved options.
Called with options after parameter references have been resolved.
The :bb key contains %{robot: module, path: [atom]}.
Return {:ok, state} or {:ok, state, timeout_or_continue} on success,
{:stop, reason} to abort startup, or :ignore to skip this sensor.
@callback options_schema() :: Spark.Options.t()
Returns the options schema for this sensor.
The schema should NOT include the :bb option - it is auto-injected.
If this callback is not implemented, the module cannot accept options
in the DSL (must be used as a bare module).
Clean up before termination.
Same semantics as GenServer.terminate/2.