Skip to content

First arm: SO-101, real

python3 -m pip install "strands-robots[sim,feetech]"
ls /dev/tty.usbmodem* /dev/ttyACM* 2>/dev/null      # the arm's serial port

Read before you move:

from strands_robots import Robot

arm = Robot("so101", mode="real", port="/dev/tty.usbmodem5AB01818061")
obs = arm.observe()
print(obs.frame, sorted(obs.joints))              # ticks by default; six joints
print(arm.is_connected())                         # asked of the wire

Move one joint:

receipt = arm.act({"shoulder_pan": obs.joints["shoulder_pan"] + 50})   # 50 ticks, clamped per step
print(receipt.clamped)
arm.close()                                       # torque off, port released

From the shell, real motion needs --confirm:

strands-robots observe so101 --mode real --port /dev/tty.usbmodem5AB01818061
strands-robots move so101 '{"shoulder_pan": 2100}' --mode real \
    --port /dev/tty.usbmodem5AB01818061 --confirm
rail what it does
per-step clamp every write is bounded by the row's max_step in the arm's native frame
preflight(policy) refuses a policy whose action_frame or DOF the arm cannot consume, before motion
close() torque off and port released on every exit path
twin Robot("so101", mode="twin") runs the same Feetech driver against the MuJoCo model: same verbs, same refusals, no bench

Calibration lives with the driver: an arm without a calibration file gets a synthetic one that is safe but not metric, and arm.calibration says synthetic rather than preexisting. No bench today? Robot("so101", mode="twin") runs every line above against the MuJoCo model.