# `BB.Robot.Runtime`
[🔗](https://github.com/beam-bots/bb/blob/main/lib/bb/robot/runtime.ex#L5)

Runtime process for a BB robot.

Manages the robot's runtime state including:
- The `BB.Robot` struct (static topology)
- The `BB.Robot.State` ETS table (dynamic joint state)
- Robot state machine (disarmed/idle/executing)
- Command execution lifecycle
- Sensor telemetry collection (subscribes to `JointState` messages)

## Robot States

The robot progresses through these states:
- `:disarmed` - Robot is not armed, commands restricted
- `:idle` - Robot is armed and ready for commands
- `:executing` - A command is currently executing

## State Transitions

```
:disarmed ──arm──→ :idle
:idle ──execute──→ :executing
:executing ──complete──→ :idle
:executing ──disarm──→ :disarmed
:idle ──disarm──→ :disarmed
```

## Command Execution

Commands execute as supervised GenServers. The caller receives the command
pid and can use `BB.Command.await/2` or `BB.Command.yield/2` to get the
result. The Runtime monitors the command server and transitions back to
`:idle` when it completes.

# `robot_state`

```elixir
@type robot_state() :: :disarmed | :disarming | :idle | :executing | :error | atom()
```

# `simulation_mode`

```elixir
@type simulation_mode() :: nil | :kinematic | :external
```

# `t`

```elixir
@type t() :: %BB.Robot.Runtime{
  category_counts: %{required(atom()) =&gt; non_neg_integer()},
  category_limits: %{required(atom()) =&gt; pos_integer()},
  commands: %{required(atom()) =&gt; BB.Dsl.Command.t()},
  current_command_name: atom() | nil,
  current_command_pid: pid() | nil,
  current_command_ref: reference() | nil,
  current_execution_id: reference() | nil,
  executing_commands: %{required(reference()) =&gt; BB.Robot.CommandInfo.t()},
  operational_state: atom(),
  parameter_store: module() | nil,
  parameter_store_state: term() | nil,
  robot: BB.Robot.t(),
  robot_module: module(),
  robot_state: BB.Robot.State.t(),
  simulation_mode: simulation_mode(),
  valid_states: [atom()]
}
```

# `cancel`

```elixir
@spec cancel(module()) :: :ok | {:error, :no_execution}
```

Cancel the currently executing command.

Stops the command server with `:cancelled` reason. Awaiting callers
will receive the result from the command's `result/1` callback.

# `category_availability`

```elixir
@spec category_availability(module()) :: %{
  required(atom()) =&gt; {non_neg_integer(), pos_integer()}
}
```

Get the availability of each command category.

Returns a map of category names to `{current_count, limit}` tuples.

# `check_allowed`

```elixir
@spec check_allowed(module(), [robot_state()]) ::
  :ok | {:error, BB.Error.State.NotAllowed.t()}
```

Check if the robot is in one of the allowed states.

Reads directly from ETS for fast concurrent access.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `execute`

```elixir
@spec execute(module(), atom(), map()) :: {:ok, pid()} | {:error, term()}
```

Execute a command with the given goal.

Returns `{:ok, pid}` where `pid` is the command server process.
Use `BB.Command.await/2` or `BB.Command.yield/2` to get the result.

## Examples

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

    # Or with timeout
    case BB.Command.yield(cmd, 5000) do
      nil -> still_running()
      {:ok, result} -> handle_result(result)
      {:error, reason} -> handle_error(reason)
    end

## Errors

- `{:error, %StateError{}}` - Robot not in allowed state
- `{:error, {:unknown_command, name}}` - Command not found
- Other errors are returned through `BB.Command.await/2`

# `executing?`

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

Check if any command is currently executing.

Reads directly from ETS for fast concurrent access.

# `executing?`

```elixir
@spec executing?(module(), atom()) :: boolean()
```

Check if a specific category has commands executing.

# `executing_commands`

```elixir
@spec executing_commands(module()) :: [map()]
```

Get information about all currently executing commands.

# `get_robot`

```elixir
@spec get_robot(module()) :: BB.Robot.t()
```

Get the static robot struct (topology).

# `get_robot_state`

```elixir
@spec get_robot_state(module()) :: BB.Robot.State.t()
```

Get the robot state (ETS-backed joint positions/velocities).

# `operational_state`

```elixir
@spec operational_state(module()) :: atom()
```

Get the actual operational state, without backwards compatibility translation.

Unlike `state/1`, this returns the actual operational state regardless of
whether commands are executing. Use this when you need to know the true
operational context (e.g., `:idle`, `:recording`, `:reacting`).

Reads directly from ETS for fast concurrent access.

# `positions`

```elixir
@spec positions(module()) :: %{required(atom()) =&gt; float()}
```

Get all joint positions as a map.

Reads directly from ETS for fast concurrent access. Returns a map of
joint names to their current positions (in radians for revolute joints,
metres for prismatic joints).

Positions are updated automatically by the Runtime when sensors publish
`JointState` messages.

## Examples

    iex> BB.Robot.Runtime.positions(MyRobot)
    %{pan_joint: 0.0, tilt_joint: 0.0}

# `simulation_mode`

```elixir
@spec simulation_mode(module()) :: simulation_mode()
```

Get the simulation mode for a robot.

Returns `nil` if running in hardware mode, or the simulation mode atom
(e.g., `:kinematic`, `:external`) if running in simulation.

# `start_link`

Starts the runtime for a robot module.

# `state`

```elixir
@spec state(module()) :: robot_state()
```

Get the current robot state machine state.

Returns `:disarmed` if the robot is not armed (via BB.Safety),
otherwise returns the internal operational state.

For backwards compatibility:
- When `operational_state` is `:idle` but commands are executing, returns `:executing`
- Custom operational states (e.g., `:recording`) are returned directly

Reads directly from ETS for fast concurrent access.

# `transition`

```elixir
@spec transition(module(), robot_state()) :: {:ok, robot_state()} | {:error, term()}
```

Transition the robot to a new state.

# `transition_operational_state`

```elixir
@spec transition_operational_state(module(), reference(), atom()) ::
  :ok | {:error, term()}
```

Transition the operational state during command execution.

This is called by `BB.Command.transition_state/2` to change the robot's
operational state mid-execution. Only the command with the matching
execution_id can trigger a transition.

# `velocities`

```elixir
@spec velocities(module()) :: %{required(atom()) =&gt; float()}
```

Get all joint velocities as a map.

Reads directly from ETS for fast concurrent access. Returns a map of
joint names to their current velocities (in rad/s for revolute joints,
m/s for prismatic joints).

Velocities are updated automatically by the Runtime when sensors publish
`JointState` messages.

## Examples

    iex> BB.Robot.Runtime.velocities(MyRobot)
    %{pan_joint: 0.0, tilt_joint: 0.0}

# `via`

Returns the via tuple for process registration.

---

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