> ## Documentation Index
> Fetch the complete documentation index at: https://docs.almond.bot/llms.txt
> Use this file to discover all available pages before exploring further.

# Calibrating a Robot

> Per-robot friction and gravity calibration for smooth slow motion: one command, uploaded by hub serial, picked up by every machine that drives the robot.

The arms' impedance controllers cancel each joint's friction and gravity with a feedforward model. How smooth the arm moves at slow speed depends on how well that model matches *this* robot's gearboxes and links, so every robot should be calibrated once — at the factory, and again after a joint or gearbox is replaced.

On the jelly robot's right arm the slow-motion reference (`slow_osc`) went from **4.3–4.5 mm** of vertical tool shake untuned to about **2.5 mm** with the calibrated friction model and the current joint gains — the 1–3 Hz sway an operator feels during slow teleop.

## What a robot runs without one

An uncalibrated robot uses the coded defaults: the joint gains in [`AxolConfig`](/cli/configuration), and the jelly robot's measured friction for every joint as a fallback. That is much better than no friction model, but each robot's gearboxes differ — calibrate before relying on slow, precise motion.

## Calibrate

With both arms clear of obstacles, the e-stop in reach and the robot otherwise idle:

```bash theme={null}
axol tune.factory
```

or **Factory calibration (all joints)** in the [diagnostics dashboard](/guides/diagnostics-dashboard). It sweeps every joint of both arms (distal → proximal, about 6 minutes per joint, \~1.5 h in all) at slow and moderate speeds and fits, per joint:

* **friction** in the realtime core's own law — sliding friction growing with the gravity load, viscous drag, an offset, and the low-speed Stribeck excess (cancelled on `shoulder_1`, `shoulder_2` and the `elbow`);
* **gravity**: the link's centre of mass.

See [`tune.factory`](/cli/tune-factory) for the flags (one arm, raw sweep capture, the older quick profile).

The results apply on this machine at once (`~/.almond/calibration.json`) and, when the upload key is configured, are uploaded under the robot's identity — the Axol hub adapter's serial, which stays with the arms across compute-host swaps:

```bash theme={null}
# .env on the calibrating machine
AXOL_SUPABASE_KEY=<service key with storage write access>
```

Without the key the run still calibrates locally and says so. Upload later with [`calibration.push`](/cli/tune-factory#calibration-push) (`--dry-run` first to see what goes up).

## Every machine that drives the robot

A machine picks up its robot's calibration automatically at the end of [`can.setup`](/cli/can-setup), and with `axol calibration.pull` at any time — no credentials needed. Values layer as: coded defaults, then the uploaded factory calibration, then this machine's local calibration file.

## Check the result

Replay the built-in slow-motion reference (`slow_osc`: the arm bent up in front while shoulder\_1 sweeps slowly back and forth, generated in code and the same on every robot) and read the wrist IMU's shake score:

```bash theme={null}
axol tune.motion --motion slow_osc --arms right --repeat 2
```

Each pass prints a `wrist IMU (right): … vertical X mm = 1-3 Hz … + 3-15 Hz …` line. Compare against the same command before calibrating, in the same session (the score drifts 10–20% across a day). Jelly's numbers above were measured on an earlier recorded version of `slow_osc`, so treat them as an order of magnitude, not a target. The wrist camera only *measures* here; nothing feeds back from it.

<Note>
  One joint at a time: [`tune.friction --profile slow`](/cli/tune-friction) calibrates a single joint's friction (and `--fit-csv` re-fits a saved sweep), [`tune.gravity`](/cli/tune-gravity) its gravity. Push the result with `calibration.push`.
</Note>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.