A simple, direct example: a Python script that runs on your computer with the SO-101 (LeRobot) arm plugged into a USB port. Your computer becomes the command proxy — it registers the arm as a robot on CommandAGI, streams a webcam view, and writes every control action it receives straight to the servo bus. Anyone with the session link — you in the web UI, an AI agent, or another developer's SDK client — can then watch and drive it.
No microcontroller and no extra firmware: this talks to the SO-101's stock Feetech USB bus-servo adapter directly at 1 Mbps, the same path LeRobot uses.
Want the arm online over Wi-Fi with no computer attached (an ESP32 as the brain)? That lives in the separate firmware repo — ask the CommandAGI team.
pip install -r requirements.txt
export COMMANDAGI_API_KEY=cagi_... # create one in your dashboard (operator scope)
python so101_arm.py # auto-detects the SO-101 USB driverIt prints a session URL — open it to watch the arm's camera and drive it. Press Ctrl-C to take it offline.
- Plug the SO-101's USB driver into your computer. The script auto-detects the port
(
/dev/ttyACM*,/dev/ttyUSB*,/dev/tty.usb*); override withSO101_PORT(e.g.COM5on Windows). A USB webcam, if present, becomes the arm's camera view (SO101_CAMERApicks the index); otherwise it streams a placeholder.
fromcommandagiimportCommandAGIcagi=CommandAGI() # COMMANDAGI_API_KEY from the environmentbridge=cagi.register_robot("so101-arm") # registers a robot device, returns a bridgeprint("watch + drive at:", bridge.session_url)
bridge.run(
camera=cam.frame, # () -> JPEG bytes (the arm's observation)on_action=arm.on_action, # (action, payload) -> writes joint goals to the busfps=10,
)so101_arm.py speaks the Feetech STS3215 protocol directly over the USB driver. The six
follower joints are shoulder_pan, shoulder_lift, elbow_flex, wrist_flex, wrist_roll, gripper at servo IDs 1..6.
| action | payload | effect |
|---|---|---|
joint | {name, value} | set one joint (value −1..1; gripper 0..1) |
pose | {shoulder_pan: .., elbow_flex: .., ...} | set several joints at once |
gripper | {value} | 0 = closed, 1 = open |
home / reset | — | go to the rest pose |
stop | — | hold current targets |
move / back | {speed} | jog shoulder_lift ± (default drive buttons) |
turn | {dir, rate} | jog shoulder_pan ± |
The arm boots de-energized. Torque enables only when the first motion command
arrives and is released on exit. Goals are always clamped to the per-joint limits in
JOINTS — run the LeRobot calibration for your build and tighten those before trusting
it under torque.
- Robot developer API + Python SDK: https://commandagi.com/docs/robots
MIT licensed.