Search documentation of bb

# BB.Parameter behaviour (bb v0.17.0)

Copy Markdown View Source

Runtime-adjustable parameters for robot components.

Parameters provide a way to configure robot behaviour at runtime without recompilation. They support validation, change notifications via PubSub, and optional persistence.

## Behaviour

Components that expose parameters implement the `BB.Parameter` behaviour:

    defmodule MyController do
      use GenServer
      @behaviour BB.Parameter

      @impl BB.Parameter
      def param_schema do
        Spark.Options.new!(
          kp: [type: :float, required: true, doc: "Proportional gain"],
          ki: [type: :float, default: 0.0, doc: "Integral gain"],
          kd: [type: :float, default: 0.0, doc: "Derivative gain"]
        )
      end
    end

## Path-Based Identification

Parameters are identified by hierarchical paths that match the PubSub convention:

- `[:robot, :max_velocity]` - Robot-level parameter
- `[:controller, :pid, :kp]` - Component parameter
- `[:sensor, :imu, :sample_rate]` - Sensor configuration

## Usage

    # Read a parameter (fast, direct ETS access)
    {:ok, value} = BB.Parameter.get(MyRobot, [:motion, :max_speed])

    # Write a parameter (validated, publishes change)
    :ok = BB.Parameter.set(MyRobot, [:motion, :max_speed], 2.0)

    # Atomic batch update
    :ok = BB.Parameter.set_many(MyRobot, [
      {[:controller, :pid, :kp], 2.0},
      {[:controller, :pid, :ki], 0.2}
    ])

    # List parameters
    params = BB.Parameter.list(MyRobot, prefix: [:controller])

## Change Notifications

Parameter changes are published via `BB.PubSub` with the `:param` prefix:

    BB.PubSub.subscribe(MyRobot, [:param, :controller, :pid])
    # Receives: {:bb, [:param, :controller, :pid, :kp], %BB.Message{}}

## Summary

### Callbacks

param_schema()

Returns a compiled `Spark.Options` schema for this component's parameters.

### Functions

get(robot_module, path)

Get a parameter value.

get!(robot_module, path)

Get a parameter value, raising if not found.

get_remote(robot_module, bridge_name, param_id)

Get a parameter value from a remote system via a bridge.

implements?(module)

Check if a module implements the BB.Parameter behaviour.

list(robot_module, opts \\ [])

List all parameters, optionally filtered by path prefix.

list_remote(robot_module, bridge_name)

List parameters available on a remote system via a bridge.

register(robot_module, path, component_module)

Register a component's parameters with the robot.

set(robot_module, path, value)

Set a parameter value.

set_many(robot_module, params)

Set multiple parameters atomically.

set_remote(robot_module, bridge_name, param_id, value)

Set a parameter value on a remote system via a bridge.

subscribe_remote(robot_module, bridge_name, param_id)

Subscribe to changes for a remote parameter via a bridge.

## Callbacks

# param_schema()

    @callback param_schema() :: Spark.Options.t()

Returns a compiled `Spark.Options` schema for this component's parameters.

The schema defines parameter names, types, defaults, and constraints.

## Functions

# get(robot_module, path)

    @spec get(module(), [atom()]) :: {:ok, term()} | {:error, :not_found}

Get a parameter value.

Returns `{:ok, value}` if the parameter exists, `{:error, :not_found}` otherwise.

This is a fast operation - it reads directly from ETS.

## Examples

    {:ok, 1.5} = BB.Parameter.get(MyRobot, [:motion, :max_speed])
    {:error, :not_found} = BB.Parameter.get(MyRobot, [:nonexistent])

# get!(robot_module, path)

    @spec get!(module(), [atom()]) :: term()

Get a parameter value, raising if not found.

## Examples

    1.5 = BB.Parameter.get!(MyRobot, [:motion, :max_speed])

# get_remote(robot_module, bridge_name, param_id)

    @spec get_remote(module(), atom(), BB.Bridge.param_id()) ::
      {:ok, term()} | {:error, term()}

Get a parameter value from a remote system via a bridge.

The bridge must implement `get_remote/2`.

## Examples

    {:ok, 0.15} = BB.Parameter.get_remote(MyRobot, :mavlink, "PITCH_RATE_P")

# implements?(module)

    @spec implements?(module()) :: boolean()

Check if a module implements the BB.Parameter behaviour.

# list(robot_module, opts \\ [])

    @spec list(
      module(),
      keyword()
    ) :: [{[atom()], map()}]

List all parameters, optionally filtered by path prefix.

Returns a list of `{path, metadata}` tuples where metadata includes the current value, type, and other schema information.

## Options

- `:prefix` - Only return parameters under this path prefix (default: `[]`)

## Examples

    # All parameters
    params = BB.Parameter.list(MyRobot)

    # Parameters under [:controller]
    params = BB.Parameter.list(MyRobot, prefix: [:controller])

# list_remote(robot_module, bridge_name)

    @spec list_remote(module(), atom()) :: {:ok, [map()]} | {:error, term()}

List parameters available on a remote system via a bridge.

Returns a list of parameter info from the remote (e.g., flight controller). The bridge must implement `list_remote/1`.

## Examples

    {:ok, params} = BB.Parameter.list_remote(MyRobot, :mavlink)
    # => [{id: "PITCH_RATE_P", value: 0.1, type: :float, doc: "..."}, ...]

# register(robot_module, path, component_module)

    @spec register(module(), [atom()], module()) :: :ok | {:error, term()}

Register a component's parameters with the robot.

Called by components during init to register their parameter schema. Parameters are initialised with default values from the schema.

## Examples

    def init(opts) do
      bb = Keyword.fetch!(opts, :bb)
      BB.Parameter.register(bb.robot, bb.path, __MODULE__)
      {:ok, %{bb: bb}}
    end

# set(robot_module, path, value)

    @spec set(module(), [atom()], term()) :: :ok | {:error, term()}

Set a parameter value.

The value is validated against the registered schema (if any) before being stored. On success, a change notification is published via PubSub.

Returns `:ok` on success, `{:error, reason}` on validation failure.

## Examples

    :ok = BB.Parameter.set(MyRobot, [:motion, :max_speed], 2.0)
    {:error, reason} = BB.Parameter.set(MyRobot, [:motion, :max_speed], -1.0)

# set_many(robot_module, params)

    @spec set_many(module(), [{[atom()], term()}]) :: :ok | {:error, [{[atom()], term()}]}

Set multiple parameters atomically.

All parameters are validated before any are written. If any validation fails, no parameters are changed.

## Examples

    :ok = BB.Parameter.set_many(MyRobot, [
      {[:controller, :pid, :kp], 2.0},
      {[:controller, :pid, :ki], 0.2}
    ])

# set_remote(robot_module, bridge_name, param_id, value)

    @spec set_remote(module(), atom(), BB.Bridge.param_id(), term()) ::
      :ok | {:error, term()}

Set a parameter value on a remote system via a bridge.

The bridge must implement `set_remote/3`.

## Examples

    :ok = BB.Parameter.set_remote(MyRobot, :mavlink, "PITCH_RATE_P", 0.15)

# subscribe_remote(robot_module, bridge_name, param_id)

    @spec subscribe_remote(module(), atom(), BB.Bridge.param_id()) ::
      :ok | {:error, term()}

Subscribe to changes for a remote parameter via a bridge.

When the remote parameter changes, the bridge publishes via `BB.PubSub`. The path structure is determined by the bridge implementation.

The bridge must implement `subscribe_remote/2`.

## Examples

    :ok = BB.Parameter.subscribe_remote(MyRobot, :mavlink, "PITCH_RATE_P")