Search documentation of bb

# BB.Safety (bb v0.8.0)

View Source

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.

# Summary

## Functions

arm(robot_module)

Arm the robot.

armed?(robot_module)

Check if a robot is armed.

disarm(robot_module)

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

report_error(robot_module, path, error)

Report a hardware error from a component.

state(robot_module)

Get current safety state for a robot.

# Functions

# arm(robot_module)

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

Check if a robot is armed.

Fast ETS read - does not go through GenServer.

# disarm(robot_module)

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

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

# disarming?(robot_module)

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)

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

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)

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

Get current safety state for a robot.

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