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

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{}}

# `param_schema`

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

# `get`

```elixir
@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!`

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

```elixir
@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?`

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

Check if a module implements the BB.Parameter behaviour.

# `list`

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

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

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

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

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

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

```elixir
@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")

---

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