~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·
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·
--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.