Feetech arms¶
An SO-100 or SO-101 on a serial port: which driver talks to it, the units a command takes, checking the bus.
By the end of this page an SO-100, SO-101 or LeKiwi is open on a serial port, and you know which driver talks to it, the units a command takes, and how to check the bus first.
With the arm on USB, find its port:
from strands_robots import Robot
arm = Robot("so101", mode="real", port="/dev/ttyACM0") # strands driver, the default
arm = Robot("so101", mode="real", driver="lerobot", port="/dev/ttyACM0") # lerobot so101_follower
Which driver¶
driver="strands" (default) |
driver="lerobot" |
|
|---|---|---|
| class | drivers.feetech.FeetechDriver |
hardware_robot.Robot around lerobot so101_follower |
| needs | pip install 'strands-robots[serial]' |
pip install 'strands-robots[lerobot]' |
units in send_action |
degrees; gripper is percent open |
degrees (so101_follower sets use_degrees=True); gripper.pos 0 to 100 |
| keys | shoulder_pan or shoulder_pan.pos, one per motor |
shoulder_pan.pos |
| policy rollout | yes, PolicyRollout at control_frequency 30 Hz |
yes (execute, start) |
| cameras | not read (reads_cameras is not set) |
cameras={...} opened by lerobot |
| teleop leader | Teleoperator("so101_leader", port=...); the leader must report degrees |
same |
Both register for so100, so101, hope_jr and open_duck_mini; lekiwi is lerobot only, since its base is not on the six-motor map. hope_jr and open_duck_mini share the bus protocol but not the six-servo layout; pass motor_ids= to the native driver until a joint map for them lands.
The native driver's six motors, servo ids 1 to 6 in wire order from shoulder_pan to gripper, are the generated joint table on the so101 page.
Check the bus without moving¶
serial_tool reads and pings without approval; only send, send_read, feetech_position and feetech_velocity are gated.
from strands_robots import serial_tool
serial_tool(action="list_ports")
serial_tool(action="feetech_ping", port="/dev/ttyACM0", motor_id=1)
On the lerobot path the robot tool's get_state reads positions, torque and voltage over the bus lock without calling lerobot's connect() (which would write PID registers). An uncalibrated arm reports ticks converted with (ticks - 2048) * 360 / 4096 and says so.
Move one joint¶
Through the agent, pose_tool names poses and moves motors; every motion verb is gated (see agents). Direct from Python on the native driver:
arm.send_action({"shoulder_pan": 0.0, "gripper": 50.0})
import asyncio; asyncio.run(arm.stop()) # releases torque on every motor
stop() de-energises. stop_task() only halts a rollout and leaves the arm holding position.
Calibration¶
Run lerobot-calibrate --robot.type=so101_follower --robot.port=/dev/ttyACM0 --robot.id=my_arm once. The lerobot driver reads that file by id; the native driver takes calibration= (the path, or the records) and otherwise commands the servo's full travel and reports calibration_source in get_status(). Details in calibration.
LeKiwi¶
lekiwi is an SO-101 on a three-wheel base with a Raspberry Pi. lerobot's lekiwi runs on the Pi, lekiwi_client on your laptop: Robot("lekiwi_client", mode="real", remote_ip="192.168.1.50"). Robot("lekiwi", mode="real", port=...) drives the servos through FeetechDriver, by the labels on the lekiwi page.
Twin transport¶
The native driver runs unchanged against the arm's MuJoCo model, no hardware needed:
from strands_robots import Robot
sim = Robot("so101")
arm = Robot("so101", mode="real", driver="strands", transport="twin", sim=sim, realtime=False)
arm.connect_eagerly() # None on success, a reason string otherwise
arm.send_action({"gripper": 100.0}) # percent open
for _ in range(20):
sim.step()
print(arm.bus.sync_read("Present_Position")["gripper"]) # ~99.9, read back from the MuJoCo joint
arm.cleanup()
Koch and other Dynamixel arms¶
Robot("koch", mode="real", driver="strands", port=...) builds DynamixelDriver: the verbs, units and refusals above over a Protocol 2.0 bus, with the koch_follower calibration file; without driver=, koch resolves to lerobot's koch_follower. ViperX, WidowX and ALOHA have no verified motor map and are refused.