# `BB.Safety.Controller`
[🔗](https://github.com/beam-bots/bb/blob/main/lib/bb/safety/controller.ex#L5)

Global safety controller that owns arm/disarm state for all robots.

Part of BB's application supervision tree (not per-robot), so it survives
robot crashes and maintains safety state. Runs at high scheduler priority.

Uses two ETS tables:

1. **Robots table** (protected set) - safety state per robot, writes only via GenServer
2. **Handlers table** (public bag) - direct writes for registration

Monitors robot supervisors and cleans up state when they terminate.

## Safety States

- `:disarmed` - Robot is safely disarmed, all disarm callbacks succeeded
- `:armed` - Robot is armed and ready to operate
- `:disarming` - Disarm in progress, callbacks running concurrently
- `:error` - Disarm attempted but one or more callbacks failed; hardware may not be safe

When in `:error` state, the robot cannot be armed until `force_disarm/1` is called
to acknowledge the error and reset to `:disarmed`.

Disarm callbacks run concurrently with a timeout. If any callback fails or times out,
the robot transitions to `:error` state.

Note: The executing/idle distinction is handled by Runtime as it's not safety-critical.

# `safety_state`

```elixir
@type safety_state() :: :disarmed | :armed | :disarming | :error
```

# `arm`

```elixir
@spec arm(module()) :: :ok | {:error, :already_armed | :in_error | :not_registered}
```

Arm the robot.

Goes through GenServer to ensure proper state transitions and event publishing.
Cannot arm if robot is in `:error` state - must call `force_disarm/1` first.

# `armed?`

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

Check if a robot is armed.

Fast ETS read - does not go through GenServer.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `disarm`

```elixir
@spec disarm(
  module(),
  keyword()
) ::
  :ok | {:error, :already_disarmed | :not_registered | {:disarm_failed, list()}}
```

Disarm the robot.

Goes through GenServer. Calls all registered `disarm/1` callbacks before
updating state. If any callback fails, the robot transitions to `:error`
state instead of `:disarmed`, and this function returns an error with
details of the failures.

When in `:error` state, the robot cannot be armed until `force_disarm/1`
is called to acknowledge the failure and reset to `:disarmed`.

## Options

  * `:timeout` - timeout in milliseconds for each disarm callback.
    Defaults to 5000ms. The GenServer call timeout
    is set to `timeout + 5000` to allow for processing overhead.

# `disarming?`

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

Check if a robot is currently disarming.

Returns `true` while disarm callbacks are running.

Fast ETS read - does not go through GenServer.

# `force_disarm`

```elixir
@spec force_disarm(module()) :: :ok | {:error, :not_in_error | :not_registered}
```

Force disarm from error state.

Use this function to acknowledge a failed disarm operation and reset the
robot to `:disarmed` state. This should only be called after manually
verifying that hardware is in a safe state.

**WARNING**: This bypasses safety checks. Only use when you have manually
verified that all actuators are disabled and the robot is safe.

# `in_error?`

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

Check if a robot is in error state.

Returns `true` if a disarm operation failed and the robot requires
manual intervention via `force_disarm/1`.

Fast ETS read - does not go through GenServer.

# `register`

```elixir
@spec register(
  module(),
  keyword()
) :: :ok
```

Register a safety handler (actuator/sensor/controller).

Called by processes in their `init/1`. The opts should contain all
hardware-specific parameters needed to call `disarm/1` without GenServer state.

Writes directly to ETS to avoid blocking on the Controller's mailbox.
Uses a cast to set up process monitoring for cleanup on handler restart.

# `register_robot`

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

Register a robot when it starts.

Called by `BB.Supervisor` during robot startup. Sets up monitoring of the
robot's supervision tree for automatic cleanup on crash.

# `register_topology_supervisor`

```elixir
@spec register_topology_supervisor(module()) :: :ok | {:error, :not_registered}
```

Register a robot's topology supervisor.

Called by `BB.TopologySupervisor` during its own startup. When the topology
supervisor terminates (because its restart budget is exhausted) the safety
controller force-disarms the robot and transitions it to `:error` state.

# `registered_handlers`

```elixir
@spec registered_handlers(module()) :: [module()]
```

Get list of registered handler modules for a robot.

Used by Runtime to verify all safety handlers have registered on startup.

# `report_error`

```elixir
@spec report_error(module(), [atom()], term()) :: :ok
```

Report a hardware error from a component.

Publishes a `BB.Safety.HardwareError` message to `[:safety, :error]`. Does
not affect safety state - components that need to escalate should crash so
the supervision tree can decide whether the topology supervisor's restart
budget is exhausted.

# `state`

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

Get current safety state for a robot.

Fast ETS read - does not go through GenServer.
Returns `:armed`, `:disarmed`, or `:error`.

---

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