BB.Parameter behaviour (bb v0.2.1)

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

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

Functions

Get a parameter value.

Get a parameter value, raising if not found.

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

Check if a module implements the BB.Parameter behaviour.

List all parameters, optionally filtered by path prefix.

List parameters available on a remote system via a bridge.

Register a component's parameters with the robot.

Set a parameter value.

Set multiple parameters atomically.

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

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.Parameter.Protocol.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.Parameter.Protocol.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.Parameter.Protocol.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")