BB.Safety (bb v0.15.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 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.

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.