BB.Safety (bb v0.16.0)

Copy Markdown 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 the robot.

Check if a robot is armed.

Check if a robot is currently disarming.

Force disarm from error state.

Check if a robot is in error state.

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

Report a hardware error from a component.

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, opts \\ [])

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?(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.

Publishes a BB.Safety.HardwareError message to [:safety, :error] for subscribers to handle. This is a pure notification - it does not disarm the robot or change safety state.

Components that detect an unrecoverable hardware fault should raise or exit instead of (or in addition to) calling this function. The supervisor will restart the offending process; if the restart budget on the topology supervisor is exhausted, the safety controller will force-disarm the robot. This is the OTP-native way to signal hardware failure: let it crash, and let supervision escalate.

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 - publish for observers,
# then crash so the supervisor decides whether to escalate:
BB.Safety.report_error(MyRobot, [:dynamixel, :servo_1], {:hardware_error, 0x04})
raise BB.Error.Hardware.Overheat, servo: 1

state(robot_module)

Get current safety state for a robot.

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