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
endIf your actuator doesn't need special disarm logic, you can implement a no-op:
@impl BB.Actuator
def disarm(_opts), do: :okImportant 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.
Disarm the robot.
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 the robot.
If the robot's DSL declares a command with arm true (set explicitly, or
implicitly when the handler is BB.Command.Arm), this function dispatches
that command via BB.Robot.Runtime.execute/3 and awaits its result. The
command is responsible for performing whatever work the user wants done
on arming (e.g. moving joints to a home pose) and for flipping safety state
via BB.Safety.Controller.arm/1.
If no arm-flagged command is defined, this function falls through to the
safety controller's direct state-flip behaviour — the historical default.
Cannot arm if the robot is in :error state; call force_disarm/1 first.
Returns :ok or {:error, :already_armed | :in_error | :not_registered | term()}. When routed through a command, the error reason is whatever the
command returned (typically a BB.Error.State exception or a
:command_failed tuple).
Check if a robot is armed.
Fast ETS read - does not go through GenServer.
Disarm the robot.
If the robot's DSL declares a command with disarm true (set explicitly, or
implicitly when the handler is BB.Command.Disarm), this function
dispatches that command via BB.Robot.Runtime.execute/3 and awaits its
result. The command is responsible for any pre-disarm work and for flipping
safety state via BB.Safety.Controller.disarm/2. If the command returns
successfully, the robot is in whatever state the command left it in
(typically :disarmed). If the command returns an error before the safety
state has been flipped, the robot is escalated to :error — by the issue's
failure semantics, an incomplete disarm sequence means hardware may not be
in a safe state.
If no disarm-flagged command is defined, this function falls through to
the safety controller's direct disarm behaviour — the historical default.
Options
:timeout- timeout in milliseconds for each disarm callback. Defaults to 5000ms. Only applicable when nodisarm-flagged command is defined; otherwise the command's own:timeoutis used.
Returns :ok or {:error, :already_disarmed | {:disarm_failed, failures} | {:disarm_command_failed, reason} | term()}.
Check if a robot is currently disarming.
Returns true while disarm callbacks are running.
Fast ETS read - does not go through GenServer.
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}.
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 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 todisarm/1
Example
BB.Safety.register(__MODULE__,
robot: MyRobot,
path: [:arm, :shoulder_joint, :servo],
opts: [pin: 18]
)
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 modulepath- 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
Get current safety state for a robot.
Fast ETS read - does not go through GenServer.
Returns :armed, :disarmed, :disarming, or :error.