Search documentation of bb

# BB.Safety.Controller (bb v0.17.0)

Copy Markdown View Source

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.

## Summary

### Types

safety_state()

### Functions

arm(robot_module)

Arm the robot.

armed?(robot_module)

Check if a robot is armed.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

disarm(robot_module, opts \\ [])

Disarm the robot.

disarming?(robot_module)

Check if a robot is currently disarming.

force_disarm(robot_module)

Force disarm from error state.

in_error?(robot_module)

Check if a robot is in error state.

register(module, opts)

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

register_robot(robot_module)

Register a robot when it starts.

register_topology_supervisor(robot_module)

Register a robot's topology supervisor.

registered_handlers(robot_module)

Get list of registered handler modules for a robot.

report_error(robot_module, path, error)

Report a hardware error from a component.

state(robot_module)

Get current safety state for a robot.

## Types

# safety_state()

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

## Functions

# arm(robot_module)

    @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?(robot_module)

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

Check if a robot is armed.

Fast ETS read - does not go through GenServer.

# child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# disarm(robot_module, opts \\ [])

    @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?(robot_module)

    @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(robot_module)

    @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?(robot_module)

    @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(module, opts)

    @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(robot_module)

    @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(robot_module)

    @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(robot_module)

    @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(robot_module, path, error)

    @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(robot_module)

    @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`.