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

Safety system API.

This module provides the API for arming/disarming robots and managing safety state.
The `disarm/1` callback that components implement is now defined in `BB.Controller`
and `BB.Actuator` behaviours.

## 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.

## Implementing Disarm Callbacks

Controllers and actuators implement the `disarm/1` callback via their behaviours:

    defmodule MyActuator do
      use GenServer
      use BB.Actuator

      @impl BB.Actuator
      def disarm(opts) do
        pin = Keyword.fetch!(opts, :pin)
        MyHardware.disable(pin)
        :ok
      end

      def init(opts) do
        BB.Safety.register(__MODULE__,
          robot: opts[:bb].robot,
          path: opts[:bb].path,
          opts: [pin: opts[:pin]]
        )
        # ...
      end
    end

If your actuator doesn't need special disarm logic, you can implement a no-op:

    @impl BB.Actuator
    def disarm(_opts), do: :ok

## Important Limitations

The BEAM virtual machine provides soft real-time guarantees, not hard real-time.
Disarm callbacks may be delayed by garbage collection, scheduler load, or other
system activity. For safety-critical applications, always implement hardware-level
safety controls as your primary protection.

See the Safety documentation topic for detailed recommendations.

# `arm`

Arm the robot.

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

Returns `:ok` or `{:error, :already_armed | :in_error | :not_registered}`.

# `armed?`

Check if a robot is armed.

Fast ETS read - does not go through GenServer.

# `disarm`

Disarm the robot.

Goes through the safety controller GenServer. Calls all registered `disarm/1`
callbacks before updating state. If any callback fails, the robot transitions
to `:error` state instead of `:disarmed`.

## Options

  * `:timeout` - timeout in milliseconds for each disarm callback.
    Defaults to 5000ms.

Returns `:ok` or `{:error, :already_disarmed | {:disarm_failed, failures}}`.

# `disarming?`

Check if a robot is currently disarming.

Returns `true` while disarm callbacks are running.

Fast ETS read - does not go through GenServer.

# `force_disarm`

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.

Returns `:ok` or `{:error, :not_in_error | :not_registered}`.

# `in_error?`

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`

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.

## Options

- `:robot` (required) - The robot module
- `:path` (required) - The path to this component (for logging)
- `:opts` - Hardware-specific options passed to `disarm/1`

## Example

    BB.Safety.register(__MODULE__,
      robot: MyRobot,
      path: [:arm, :shoulder_joint, :servo],
      opts: [pin: 18]
    )

# `report_error`

Report a hardware error from a component.

This function should be called by controllers, actuators, or sensors when
they detect a hardware error condition. The behaviour depends on the robot's
`auto_disarm_on_error` setting:

- If `true` (default): The robot is automatically disarmed
- If `false`: The error is published but no automatic action is taken

In both cases, a `BB.Safety.HardwareError` message is published to
`[:safety, :error]` for subscribers to handle.

## Parameters

- `robot_module` - The robot module
- `path` - Path to the component reporting the error (e.g., `[:dynamixel, :servo_1]`)
- `error` - Component-specific error details

## Example

    # In a controller detecting servo overheating:
    BB.Safety.report_error(MyRobot, [:dynamixel, :servo_1], {:hardware_error, 0x04})

## Customising Error Handling

To implement custom error handling instead of auto-disarm:

    defmodule MyRobot do
      use BB

      settings do
        auto_disarm_on_error false
      end
    end

    # Then subscribe to error events:
    BB.subscribe(MyRobot, [:safety, :error])

# `state`

Get current safety state for a robot.

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

---

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