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

Behaviour for implementing robot commands.

Commands are short-lived GenServers that can react to safety state changes
and other messages during execution. The `handle_command/3` callback is the
entry point, returning GenServer-style tuples.

## Example

    defmodule NavigateToPose do
      use BB.Command

      @impl BB.Command
      def handle_command(%{target_pose: pose}, context, state) do
        # Subscribe to position updates
        BB.PubSub.subscribe(context.robot_module, [:sensor, :position])

        # Start navigation
        send_navigation_command(pose)

        {:noreply, %{state | target: pose}}
      end

      @impl BB.Command
      def handle_info({:bb, [:sensor, :position], msg}, state) do
        if close_enough?(msg.payload.position, state.target) do
          {:stop, :normal, %{state | final_pose: msg.payload.position}}
        else
          {:noreply, state}
        end
      end

      @impl BB.Command
      def result(state) do
        {:ok, %{final_pose: state.final_pose}}
      end
    end

## State Transitions

By default, when a command completes successfully, the robot transitions to
`:idle`. Commands can override this by returning a `next_state` option from
`result/1`:

    def result(state) do
      {:ok, :armed, next_state: :idle}
    end

This is useful for commands like `Arm` and `Disarm` that need to control
the robot's state machine.

## Execution Model

Commands run as supervised GenServers spawned by the Runtime. The caller
receives the command's pid and can use `BB.Command.await/2` or
`BB.Command.yield/2` to get the result.

## Safety Handling

Commands automatically subscribe to safety state changes. When the robot
begins disarming, `handle_safety_state_change/2` is called. The default
implementation stops the command with `:disarmed` reason. Override this
callback to implement graceful shutdown or to continue execution during
safety transitions.

## Parameterised Options

Commands can receive options via child_spec format in the DSL:

    commands do
      command :move_joint do
        handler {MyMoveJointCommand, max_velocity: param([:motion, :max_velocity])}
      end
    end

ParamRefs are resolved before `init/1` is called. When parameters change,
`handle_options/2` is called with the new resolved options.

# `goal`

```elixir
@type goal() :: map()
```

# `options`

```elixir
@type options() :: [{:next_state, BB.Robot.Runtime.robot_state()}]
```

# `result`

```elixir
@type result() :: term()
```

# `state`

```elixir
@type state() :: term()
```

# `handle_call`

```elixir
@callback handle_call(request :: term(), from :: GenServer.from(), state()) ::
  {:reply, term(), state()}
  | {:reply, term(), state(), timeout() | :hibernate | {:continue, term()}}
  | {:noreply, state()}
  | {:noreply, state(), timeout() | :hibernate | {:continue, term()}}
  | {:stop, term(), state()}
  | {:stop, term(), term(), state()}
```

Handle synchronous calls.

Standard GenServer callback. The default implementation returns
`{:reply, {:error, :not_implemented}, state}`.

# `handle_cast`

```elixir
@callback handle_cast(request :: term(), state()) ::
  {:noreply, state()}
  | {:noreply, state(), timeout() | :hibernate | {:continue, term()}}
  | {:stop, term(), state()}
```

Handle asynchronous casts.

Standard GenServer callback. The default implementation returns
`{:noreply, state}`.

# `handle_command`

```elixir
@callback handle_command(goal(), BB.Command.Context.t(), state()) ::
  {:noreply, state()}
  | {:noreply, state(), timeout() | :hibernate | {:continue, term()}}
  | {:stop, term(), state()}
```

Execute the command with the given goal.

Called via `handle_continue(:execute)` immediately after `init/1`. This is
the main entry point for command execution.

The handler can:
- Return `{:noreply, state}` to continue running (waiting for messages)
- Return `{:stop, reason, state}` to complete immediately

For commands that complete immediately, simply return `{:stop, :normal, state}`
with the result stored in state.

# `handle_continue`

```elixir
@callback handle_continue(continue :: term(), state()) ::
  {:noreply, state()}
  | {:noreply, state(), timeout() | :hibernate | {:continue, term()}}
  | {:stop, term(), state()}
```

Handle continue instructions.

Standard GenServer callback. The default implementation returns
`{:noreply, state}`.

# `handle_info`

```elixir
@callback handle_info(msg :: term(), state()) ::
  {:noreply, state()}
  | {:noreply, state(), timeout() | :hibernate | {:continue, term()}}
  | {:stop, term(), state()}
```

Handle other messages.

Standard GenServer callback. The default implementation returns
`{:noreply, state}`.

# `handle_options`

```elixir
@callback handle_options(new_opts :: keyword(), state()) ::
  {:ok, state()} | {:stop, term()}
```

Handle parameter changes.

Called when a parameter that this command depends on changes. The new
resolved options are passed in. The default implementation returns
`{:ok, state}` unchanged.

# `handle_safety_state_change`

```elixir
@callback handle_safety_state_change(
  new_state :: :disarming | :disarmed | :error,
  state()
) :: {:continue, state()} | {:stop, term(), state()}
```

Handle safety state changes.

Called when the robot's safety state transitions to `:disarming`, `:disarmed`,
or `:error`. The default implementation stops the command with `:disarmed`
reason.

Return `{:continue, state}` to keep the command running during safety
transitions (use with care).

# `init`

```elixir
@callback init(opts :: keyword()) :: {:ok, state()} | {:stop, term()}
```

Initialise the command state.

Called when the command server starts. Receives resolved options including:
- `:bb` - Map with `:robot` (robot module)
- `:goal` - The command goal (arguments)
- `:context` - The command context

The default implementation returns `{:ok, Map.new(opts)}`.

# `options_schema`
*optional* 

```elixir
@callback options_schema() :: Spark.Options.schema()
```

Define the options schema for this command.

Optional. If defined, options passed to the command handler will be
validated against this schema.

# `result`

```elixir
@callback result(state()) ::
  {:ok, result()} | {:ok, result(), options()} | {:error, term()}
```

Extract the result when the command completes.

Called in `terminate/2` to get the result to return to awaiting callers.

## Return Values

- `{:ok, result}` - Command succeeded, robot transitions to `:idle`
- `{:ok, result, options}` - Command succeeded with options:
  - `next_state: state` - Robot transitions to specified state instead of `:idle`
- `{:error, reason}` - Command failed, robot transitions to `:idle`

# `terminate`

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

Clean up when the command terminates.

Standard GenServer callback. Called after the result has been extracted
and sent to awaiting callers.

# `await`

```elixir
@spec await(pid(), timeout()) ::
  {:ok, term()} | {:ok, term(), options()} | {:error, term()}
```

Await the command result, blocking until completion or timeout.

Uses `GenServer.call` internally, so standard timeout semantics apply.
If the command crashes, returns `{:error, {:command_failed, reason}}`.

## Examples

    {:ok, cmd} = MyRobot.navigate(target: pose)
    {:ok, result} = BB.Command.await(cmd)

    # With custom timeout
    {:ok, result} = BB.Command.await(cmd, 30_000)

# `cancel`

```elixir
@spec cancel(pid()) :: :ok
```

Cancel a running command.

Stops the command server with `:cancelled` reason. Awaiting callers will
receive `{:error, :cancelled}` (depending on how `result/1` handles this).

# `transition_state`

```elixir
@spec transition_state(BB.Command.Context.t(), atom()) :: :ok | {:error, term()}
```

Transition to a new operational state during command execution.

This function allows a command to change the robot's operational state
mid-execution. This is useful for multi-phase commands where different
phases require different contexts.

## Arguments

- `context` - The command context (passed to `handle_command/3`)
- `target_state` - The state to transition to (must be defined in DSL)

## Returns

- `:ok` - Transition successful
- `{:error, reason}` - Transition failed

## Example

    def handle_command(_goal, context, state) do
      # Start in processing state
      :ok = BB.Command.transition_state(context, :processing)
      # Do work...
      send(self(), :start_phase_two)
      {:noreply, state}
    end

    def handle_info(:start_phase_two, context, state) do
      # Move to finalising state
      :ok = BB.Command.transition_state(context, :finalising)
      # Do more work...
      {:stop, :normal, state}
    end

# `yield`

```elixir
@spec yield(pid(), timeout()) ::
  {:ok, term()} | {:ok, term(), options()} | {:error, term()} | nil
```

Non-blocking check for command completion.

Returns `nil` if the command is still running (timeout), otherwise returns
the result. Use this for polling-style waiting.

## Examples

    {:ok, cmd} = MyRobot.navigate(target: pose)

    case BB.Command.yield(cmd, 100) do
      nil -> IO.puts("Still running...")
      {:ok, result} -> IO.puts("Done!")
      {:error, reason} -> IO.puts("Failed: #{inspect(reason)}")
    end

---

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