This file provides guidance to coding assistances while working with this project.
Beam Bots is a framework for building resilient robotics projects in Elixir. It provides a Spark DSL for defining robot topologies (links, joints, sensors, actuators) with automatic supervision tree generation that mirrors the physical structure for fault isolation.
See documentation/tutorials/ for guided tutorials:
01-first-robot.md- defining robots with the DSL02-starting-and-stopping.md- supervision trees and fault isolation03-sensors-and-pubsub.md- publishing and subscribing to messages04-kinematics.md- computing link positions with forward kinematics05-commands.md- the command system and robot state machine06-urdf-export.md- exporting to URDF for ROS tools07-parameters.md- runtime-adjustable configuration08-parameter-bridges.md- bidirectional parameter access with remote systems09-inverse-kinematics.md- solving inverse kinematics10-simulation.md- running robots in simulation mode
The DSL reference is in documentation/dsls/DSL-BB.md.
# Run all checks (formatter, tests, credo, dialyzer, etc.)
mix check --no-retry
# Run tests
mix test
mix test path/to/test_file.exs # Single file
mix test path/to/test_file.exs:42 # Single test at line
# Code quality
mix format
mix credo --strict
mix dialyzer
# Spark DSL tools
mix spark.formatter # Update formatter with DSL locals
mix spark.cheat_sheets # Generate DSL documentation
# URDF export
mix bb.to_urdf MyRobot # Print URDF to stdout
mix bb.to_urdf MyRobot -o robot.urdf # Write to fileThe core DSL defines robot structure using nested entities:
- settings - robot name, registry/supervisor modules
- topology - contains links, joints, sensors, actuators in a tree structure
- sensors - robot-level sensors (GPS, battery, etc.)
- controllers - robot-level controller processes
- commands - commands with handlers and state machine integration
Within the topology:
- link - kinematic link (solid body) with visual, collision, inertial properties
- joint - connection between links (revolute, prismatic, fixed, continuous, floating, planar)
- sensor/actuator - child processes attached to links or joints
The DSL supports physical units via ~u sigil (e.g., ~u(0.1 meter), ~u(90 degree)).
Transformers run in sequence to process DSL at compile-time:
DefaultNameTransformer- sets robot name to module name if unsetTopologyTransformer- validates link hierarchySupervisorTransformer- generates supervision tree specsRobotTransformer- builds optimisedBB.Robotstruct, injectsrobot/0function
Robot struct (lib/bb/robot.ex): Optimised representation with:
- Flat maps for O(1) lookup of links/joints/sensors/actuators
- All units converted to SI base (metres, radians, kg)
- Pre-computed topology for traversal
Supervision tree (lib/bb/supervisor.ex): Mirrors robot topology for fault isolation. Crashes propagate only within affected subtree.
PubSub (lib/bb/pub_sub.ex): Hierarchical message routing by path. Subscribers can match exact paths or entire subtrees.
Kinematics (lib/bb/robot/kinematics.ex): Forward kinematics using 4x4 homogeneous transform matrices (Nx tensors).
Runtime (lib/bb/robot/runtime.ex): Manages robot operational state with a state machine:
:disarmed→:idle→:executing→:idle- Commands only execute in allowed states
- Subscribes to sensor messages and updates joint positions
Commands: Short-lived GenServers defined in the DSL commands section. Handlers use use BB.Command and implement handle_command/3 and result/1 callbacks. Commands can react to messages during execution and handle safety state changes. Built-in commands include BB.Command.Arm and BB.Command.Disarm.
URDF Export (lib/bb/urdf/exporter.ex): Converts robot definitions to URDF XML format for use with ROS tools like RViz and Gazebo. Available via mix bb.to_urdf.
Robots can run in simulation mode without hardware:
# Start in kinematic simulation
MyRobot.start_link(simulation: :kinematic)
# Check simulation mode
BB.Robot.Runtime.simulation_mode(MyRobot) # => :kinematic or nilIn simulation mode:
- Actuators are replaced with
BB.Sim.Actuatorwhich publishesBeginMotionmessages with timing based on joint velocity limits - Controllers are omitted by default (configurable per-controller with
simulation: :omit | :mock | :start) - Safety system still requires arming before commands work
OpenLoopPositionEstimatorworks unchanged for position feedback
See documentation/tutorials/10-simulation.md for details.
See documentation/topics/safety.md for comprehensive safety documentation.
Changes to safety-critical code require extra care - bugs here could result in physical harm.
Key points:
- Actuators controlling hardware MUST implement
BB.Safetybehaviour disarm/1callback must work without GenServer state (process may have crashed)- Safety states:
:disarmed→:armed→:disarming→:disarmed(or:erroron failure) - Disarm callbacks run concurrently with 5 second timeout
- The
:errorstate means hardware may not be safe - requiresforce_disarm/1to recover
BB uses structured errors via BB.Error (built on Splode). All error types must implement the BB.Error.Severity protocol.
Error classes:
:hardware- Communication failures with physical devices:safety- Safety system violations (always:criticalseverity):kinematics- Motion planning failures:invalid- Configuration and validation errors:state- State machine violations (command not allowed, timeout, preempted):protocol- Low-level protocol failures (Robotis, I2C, etc.)
Severity levels:
:critical- Immediate safety response required:error- Operation failed, may retry or degrade:warning- Unusual condition, operation continues
Creating new error types:
defmodule BB.Error.State.MyError do
use BB.Error, class: :state, fields: [:field1, :field2]
defimpl BB.Error.Severity do
def severity(_), do: :error
end
def message(%{field1: f1, field2: f2}) do
"Descriptive message: #{inspect(f1)}, #{f2}"
end
endPrefer structured errors over tuples - Use {:error, %BB.Error.State.NotAllowed{}} instead of {:error, {:not_allowed, reason}}.
BB.Message wraps payloads with timestamp/frame_id. Payload types use use BB.Message with a schema for validation via Spark.Options.
- Units: Use
Cldr.Unitthroughout DSL, converted to floats (SI) in Robot struct - Transforms: 4x4 matrices in
BB.Math.Transform, angles in radians - Process registration: Uses Registry with
:viatuples, names must be globally unique per robot - DSL entities are structs in
lib/bb/dsl/matching entity names - Commands: Return
{:ok, result}or{:ok, result, next_state: state}for state transitions - State machine: Robots start
:disarmed, transition to:idlewhen armed,:executingduring commands - Errors: Use structured
BB.Errortypes instead of tuple-based errors. All errors must implementBB.Error.Severity - Safety: Actuators controlling physical hardware must implement
BB.Safetybehaviour. Test disarm callbacks thoroughly - they run when things have already gone wrong
Feature proposals for new packages are tracked in the proposals repository. Check there for planned features and their design documents.