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

ETS-backed mutable state for robot instances.

This module manages joint configurations, velocities, and computed transforms
for robot instances. Each robot instance has its own ETS table for
concurrent read access.

## Configurations, not positions

A joint's configuration is a point in its own configuration space, and its
shape depends on the joint's type:

| Joint type | DoF | Configuration | Velocity |
|---|---|---|---|
| `:fixed` | 0 | `0.0`, the only one it has | `0.0` |
| `:revolute`, `:continuous` | 1 | `float` angle in radians | `float` rad/s |
| `:prismatic` | 1 | `float` displacement in metres | `float` m/s |
| `:planar` | 3 | `BB.Math.Transform2D` | `BB.Message.Geometry.Twist2D` |
| `:floating` | 6 | `BB.Math.Transform` | `BB.Message.Geometry.Twist` |

"Position" is the wrong word for a 4x4 homogeneous transform, which is why
these functions say "configuration" — the standard term for a point in a
robot's configuration space, and correct for every joint type.

Writes are whole-configuration and atomic. There is no API for setting part of
a floating joint, because a partial update invites inconsistent intermediate
states and the natural producer of such a configuration is an estimator
emitting a complete pose.

A fixed joint has zero degrees of freedom, so its configuration space is a
single point and `0.0` is the only value it accepts. It is still present in
`get_all_configurations/1` so that map can be handed straight back to
`set_configurations/2`.

## Usage

    # Create state for a robot instance
    {:ok, state} = BB.Robot.State.new(robot)

    # Set/get a joint configuration
    :ok = BB.Robot.State.set_configuration(state, :shoulder, 0.5)
    {:ok, angle} = BB.Robot.State.get_configuration(state, :shoulder)

    # Get every joint's configuration as a map
    configurations = BB.Robot.State.get_all_configurations(state)

    # Clean up when done
    :ok = BB.Robot.State.delete(state)

# `configuration`

```elixir
@type configuration() :: float() | BB.Math.Transform2D.t() | BB.Math.Transform.t()
```

A joint's configuration, shaped to its type.

See the module documentation for which shape belongs to which joint type.

# `t`

```elixir
@type t() :: %BB.Robot.State{robot: BB.Robot.t(), table: :ets.table()}
```

# `velocity`

```elixir
@type velocity() ::
  float() | BB.Message.Geometry.Twist2D.t() | BB.Message.Geometry.Twist.t()
```

A joint's velocity, shaped to its type.

# `delete`

```elixir
@spec delete(t()) :: :ok
```

Delete a state table and free resources.

# `find_schema_for_parameter`

```elixir
@spec find_schema_for_parameter(t(), [atom()]) ::
  {:ok, [atom()], Spark.Options.t()} | {:error, :not_found}
```

Find the schema that applies to a given parameter path.

Searches for the longest matching schema prefix.

# `get_all_configurations`

```elixir
@spec get_all_configurations(t()) :: %{required(atom()) =&gt; configuration()}
```

Get every joint's configuration as a map.

Every joint in the robot is present, shaped to its own type.

## Examples

    iex> BB.Robot.State.get_all_configurations(state)
    %{shoulder: 0.5, elbow: -0.2, base: %BB.Math.Transform{}}

# `get_all_velocities`

```elixir
@spec get_all_velocities(t()) :: %{required(atom()) =&gt; velocity()}
```

Get every joint's velocity as a map.

# `get_chain_configurations`

```elixir
@spec get_chain_configurations(t(), atom()) :: [{atom(), configuration()}]
```

Get the configurations of joints along a path from root to a target link.

Returns a list of `{joint_name, configuration}` tuples in traversal order.

# `get_configuration`

```elixir
@spec get_configuration(t(), atom()) ::
  {:ok, configuration()} | {:error, BB.Error.Kinematics.UnknownJoint.t()}
```

Get the current configuration of a joint.

## Examples

    {:ok, 0.5} = BB.Robot.State.get_configuration(state, :shoulder)
    {:ok, %BB.Math.Transform{}} = BB.Robot.State.get_configuration(state, :base)

# `get_parameter`

```elixir
@spec get_parameter(t(), [atom()]) :: {:ok, term()} | {:error, :not_found}
```

Get a parameter value by path.

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

# `get_parameter_schema`

```elixir
@spec get_parameter_schema(t(), [atom()]) ::
  {:ok, Spark.Options.t()} | {:error, :not_found}
```

Get the registered schema for a path prefix.

Returns `{:ok, schema}` if found, `{:error, :not_found}` otherwise.

# `get_robot_state`

```elixir
@spec get_robot_state(t()) :: atom()
```

Get the current robot state machine state.

Returns the state atom (e.g., `:disarmed`, `:idle`, `:executing`).

# `get_velocity`

```elixir
@spec get_velocity(t(), atom()) ::
  {:ok, velocity()} | {:error, BB.Error.Kinematics.UnknownJoint.t()}
```

Get the current velocity of a joint.

# `list_parameters`

```elixir
@spec list_parameters(t(), [atom()]) :: [{[atom()], map()}]
```

List all parameters, optionally filtered by path prefix.

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

# `new`

```elixir
@spec new(BB.Robot.t()) :: {:ok, t()}
```

Create a new state table for a robot.

Returns `{:ok, state}` on success.

# `register_parameter_schema`

```elixir
@spec register_parameter_schema(t(), [atom()], Spark.Options.t()) :: :ok
```

Register a parameter schema for a component path.

The schema is stored and used for validation and metadata.

# `reset`

```elixir
@spec reset(t()) :: :ok
```

Reset all joints to their identity configurations and zero velocities.

# `set_configuration`

```elixir
@spec set_configuration(t(), atom(), configuration()) ::
  :ok
  | {:error,
     BB.Error.Kinematics.UnknownJoint.t() | BB.Error.Invalid.JointConfig.t()}
```

Set the configuration of a joint.

The value's shape must match the joint's type, so a `BB.Math.Transform2D`
aimed at a revolute joint is an error rather than a wrong pose.

## Examples

    :ok = BB.Robot.State.set_configuration(state, :shoulder, 0.5)
    :ok = BB.Robot.State.set_configuration(state, :base, transform)

# `set_configurations`

```elixir
@spec set_configurations(t(), %{required(atom()) =&gt; configuration()}) ::
  :ok
  | {:error,
     BB.Error.Kinematics.UnknownJoint.t() | BB.Error.Invalid.JointConfig.t()}
```

Set multiple joint configurations at once.

Every value is validated before anything is written, so a rejected map leaves
the table untouched rather than applying part of itself.

## Examples

    :ok = BB.Robot.State.set_configurations(state, %{
      shoulder: 0.5,
      elbow: -0.3,
      base: transform
    })

# `set_parameter`

```elixir
@spec set_parameter(t(), [atom()], term()) :: :ok
```

Set a parameter value by path.

This is a low-level function that does not validate or notify.
Use `BB.Parameter.set/3` for the validated, notifying version.

# `set_parameters`

```elixir
@spec set_parameters(t(), [{[atom()], term()}]) :: :ok
```

Set multiple parameters atomically.

This is a low-level function that does not validate or notify.

# `set_robot_state`

```elixir
@spec set_robot_state(t(), atom()) :: :ok
```

Set the robot state machine state.

# `set_velocities`

```elixir
@spec set_velocities(t(), %{required(atom()) =&gt; velocity()}) ::
  :ok
  | {:error,
     BB.Error.Kinematics.UnknownJoint.t() | BB.Error.Invalid.JointConfig.t()}
```

Set multiple joint velocities at once.

# `set_velocity`

```elixir
@spec set_velocity(t(), atom(), velocity()) ::
  :ok
  | {:error,
     BB.Error.Kinematics.UnknownJoint.t() | BB.Error.Invalid.JointConfig.t()}
```

Set the velocity of a joint.

---

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