Skip to content

~3 min read

Install·

The call sheet: what you need on set, and the order we shoot it in. Five steps, one of them is a hand on my joints.

0. What you need·

An SO-101 leader arm — 6 Feetech STS3215 on one bus at 1 000 000 baud, through the kit's Waveshare/CH343 USB adapter. Its 5 V supply is fine for a leader that is moved by hand; to lift me you need 6–8.4 V on the bus. Optionally an Arduino Nicla Vision on the printed pan/tilt head. Python ≥ 3.11.

1. Find me·

git clone git@github.com:cagataycali/fomo-the-arm.git && cd fomo-the-arm
pip install -e ".[dashboard]"
fomo doctor                 # read-only: my port, six servos, calibration, camera

This is what it printed on the bench it was written on, 2026-09-09 (the panel owned the bus, so FOMO_PORT=off):

fomo 0.1.0
home: /Users/cagatay/.fomo
serial candidates (5): /dev/cu.usbmodem375F367435322, /dev/cu.usbmodem5AB01818061, /dev/cu.usbmodem5C843358881, /dev/cu.usbmodemB5C41AAC2, /dev/cu.usbmodemflip_C21
bus port: AMBIGUOUS or none -- set FOMO_PORT
calibrated 2026-09-07T08:43:36Z (imported:eeprom_prep)
1 base           home  176.66  window 66.4..251.0
2 shoulder_lift  home   83.85  window 83.2..290.0
3 elbow          home  262.35  window 68.7..262.4
4 wrist_flex     home  256.46  window 84.3..284.9
5 pan            home  187.82  window continuous
6 tilt           home  180.35  window continuous
camera: lan=unset, usb=unset

Five usbmodem* candidates — a Nicla, the servo adapter, a Sticky, a Flipper — and two share the CH343 chip, so with more than one I refuse to guess: export FOMO_PORT=/dev/cu.usbmodemXXXX. With a port, doctor also pings ids 1–7 and prints each servo's degrees, volts, °C and EEPROM window. FOMO_PORT=off means "no arm on purpose" — another dashboard owns the adapter, and two processes on one bus lose every other packet.

2. Calibrate me by hand — once·

fomo calibrate --manual --hold 40 --i-know

Torque drops. For 40 s (never less than 20), move every joint to both ends of its travel, bottom-up — base, shoulder, elbow, wrist, then the head last: the Nicla's USB cable is the only thing that limits pan and tilt. Fold me before the timer ends. The fold becomes home, the extremes become each joint's window, and a span ≥ 350° is stored as continuous.

I don't search for my own stops. On the arm this was written on, the automatic search only ever found the fold contact, 30–45° short of the metal. The hand found:

joint window (servo °) span
base 66.36..251.02 184.7°
shoulder_lift 83.23..290.04 206.8°
elbow 68.73..262.44 193.7°
wrist_flex 84.29..284.85 200.6°
pan · tilt continuous (free-spinning horn) guarded to ±160 / ±80 of home

Calibrated 2026-09-07T08:43:36Z. These cells are read from the calibration file at build time, not typed.

--eeprom also burns MIN/MAX into servos 1–4. --import path/to/calib.json adopts a strands-arm calibration instead of sampling — that is where the table above came from.

3. Open the gate, move·

fomo move --read
fomo move --look 40 -10 --i-know
fomo pose goto up --i-know
fomo photo --open

--i-know means I am watching the arm and nothing is in its sweep. For a bench you trust, echo '{"ok": true}' > ~/.fomo/bench.json opens the gate permanently. Everything else the guard decides — nine rules — hard limits only, each with an incident behind it.

4. The panel·

fomo dash                                          # http://127.0.0.1:8091
cd dashboard/frontend && npm i && npm run build    # the SPA; the server serves dist/ when present

Then sh dashboard/deploy/install.sh for launchd and dashboard/deploy/cloudflared-fomo.yml for a name. Mine is fomo.cagatay.my. Control routes need a token; state is public.

5. No arm?·

FOMO_SIM=1 FOMO_HOME=/tmp/fomo fomo dash          # six simulated servos, ramping at my real speed
FOMO_FAKE=1 pytest -q                             # the same bus, instant — what the tests use

The simulated bus is parked at the reference fold and obeys the same guard. Every tool, every test and the whole panel run against it — that is what fomo.cagatay.my shows while the servo port belongs to another dashboard.

6. Eyes (optional)·

The head is an Arduino Nicla Vision running Fomo's own node — no IDE. Flash OpenMV ≥ 5.0 with the Wi-Fi blobs, copy firmware/nicla/ onto the board's drive, set FOMO_NICLA_URL to what the console prints. The exact dfu-util lines as done on this bench: Reference → Flashing the eyes.