Search documentation of bb

# BB.Sensor behaviour (bb v0.8.0)

View Source

Behaviour and API for sensors in the BB framework.

This module serves two purposes:

1. Behaviour - Defines callbacks for sensor implementations
2. 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 runtime
- `handle_call/3`, `handle_cast/2`, `handle_info/2` - Standard GenServer-style callbacks
- `handle_continue/2`, `terminate/2` - Lifecycle callbacks
- `options_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
    end

For 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
    end

### Parameter 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)
    end

When `disarm/1` is implemented, the framework automatically registers your sensor with `BB.Safety`.

# Summary

## Callbacks

disarm(opts)

Make the hardware safe.

handle_call(request, from, state)

Handle synchronous calls.

handle_cast(request, state)

Handle asynchronous casts.

handle_continue(continue_arg, state)

Handle continue instructions.

handle_info(msg, state)

Handle all other messages.

handle_options(new_opts, state)

Handle parameter changes at runtime.

init(opts)

Initialise sensor state from resolved options.

options_schema()

Returns the options schema for this sensor.

terminate(reason, state)

Clean up before termination.

# Callbacks

# disarm(opts)

(optional)

    @callback disarm(opts :: keyword()) :: :ok | {:error, term()}

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

# handle_call(request, from, state)

(optional)

    @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`.

# handle_cast(request, state)

(optional)

    @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`.

# handle_continue(continue_arg, state)

(optional)

    @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`.

# handle_info(msg, state)

(optional)

    @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`.

# handle_options(new_opts, state)

(optional)

    @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(opts)

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

# options_schema()

(optional)

    @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).

# terminate(reason, state)

(optional)

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

Clean up before termination.

Same semantics as `GenServer.terminate/2`.