Elixir/OTP implementation of the Reachy Mini robot control system, designed for fault-tolerance, real-time control, and expressive behaviors.
- Fault-Tolerant Architecture: Supervision trees mirror physical robot structure
- Real-Time Control: 50Hz motor control loops with trajectory interpolation
- Native Performance: Zig NIFs for Dynamixel protocol, Rust NIFs for kinematics
- Expressive Behaviors: Idle animations, face tracking, and more
- Web Interface: REST API and WebSocket for remote control
- Nerves Ready: Production deployment to Raspberry Pi
ReachyMini.Supervisor
βββ Registry
βββ PubSub
βββ DynamixelBus (Zig NIF)
βββ Safety.Controller
βββ Actuators.MotorSupervisor
β βββ Motor (body_yaw)
β βββ Motor (stewart_1..6)
β βββ Motor (antenna_left, antenna_right)
βββ Actuators.StewartPlatform (Rust NIF)
βββ Motion
βββ Behaviors.Supervisor
β βββ IdleAnimations
β βββ FaceTracking
β βββ Animations
βββ Recording
βββ Bridge.Endpoint (Phoenix)
# Install dependencies
mix deps.get
# Run in simulation mode
MIX_TARGET=host mix run --no-halt
# In another terminal, interact via IEx
iex -S mix# Set target
export MIX_TARGET=rpi4
# Get dependencies and build firmware
mix deps.get
mix firmware
# Deploy to robot
mix upload reachy.local# Arm the robot
ReachyMini.arm()
# Move head
ReachyMini.set_head_pose(%{pitch: 0.2, yaw: 0.3})
# Look at a point
ReachyMini.look_at({0.5, 0.1, 0.3})
# Play animation
ReachyMini.play_animation(:happy)
# Wiggle antennas
ReachyMini.wiggle_antennas()
# Disarm
ReachyMini.disarm()| Endpoint | Method | Description |
|---|---|---|
/api/state |
GET | Get full robot state |
/api/arm |
POST | Arm the robot |
/api/disarm |
POST | Disarm the robot |
/api/move/head |
POST | Move head to pose |
/api/move/look_at |
POST | Look at point |
/api/animation/:name |
POST | Play animation |
Connect to /api/ws for real-time updates:
const socket = new Phoenix.Socket("/api/ws")
socket.connect()
const channel = socket.channel("state:full")
channel.on("state", state => console.log(state))
channel.join()# config/config.exs
config :beachy,
robot_name: "beachy",
control_rate_hz: 50,
dynamixel: [
port: "/dev/ttyUSB0",
baudrate: 1_000_000
],
motors: %{
body_yaw: %{id: 10, model: :xc330_m288},
stewart_1: %{id: 11, model: :xl330_m288},
# ...
}lib/
βββ beachy.ex # Main API
βββ beachy/
β βββ application.ex # OTP Application
β βββ safety.ex # Safety controller
β βββ motion.ex # Motion coordination
β βββ recording.ex # Motion recording/playback
β βββ diagnostics.ex # System diagnostics
β βββ telemetry.ex # Metrics collection
β βββ actuators/
β β βββ motor.ex # Individual motor control
β β βββ motor_supervisor.ex # Motor supervision
β β βββ stewart_platform.ex # Stewart platform control
β βββ kinematics/
β β βββ stewart.ex # Stewart platform kinematics
β βββ protocol/
β β βββ dynamixel.ex # Dynamixel protocol (Zig NIF)
β β βββ dynamixel_bus.ex # Serial bus management
β βββ behaviors/
β β βββ supervisor.ex # Behavior supervision
β β βββ idle_animations.ex # Idle animations
β β βββ face_tracking.ex # Face tracking
β β βββ animations.ex # Predefined animations
β βββ bridge/
β βββ endpoint.ex # Phoenix endpoint
β βββ router.ex # API routes
β βββ socket.ex # WebSocket
β βββ controllers/ # API controllers
native/
βββ dynamixel_nif/ # Zig implementation
β βββ dynamixel.zig
βββ kinematics_nif/ # Rust implementation
βββ Cargo.toml
βββ src/lib.rs
High-performance packet building and parsing with compile-time CRC tables:
// Build a sync write packet for all Stewart motors
pub fn buildSyncWritePacket(ids: []const u8, address: u16, ...) PacketFast inverse/forward kinematics using nalgebra:
// Compute leg lengths for desired head pose
pub fn inverse_kinematics(pose: &Pose) -> Result<[f64; 6], ()># Run all tests
mix test
# Run with coverage
mix test --cover
# Run specific test
mix test test/beachy/kinematics/stewart_test.exs# Build new firmware
mix firmware
# Push to running robot
mix upload reachy.localssh reachy.local
# In IEx
iex> ReachyMini.status()
iex> ReachyMini.run_diagnostics()Apache 2.0