SO-101
Robot id: so101_follower.
Tier 1 supports SO-101 simulation and an optional local bridge path. Disconnected preview uses conservative manifest limits. After a verified bridge connection, RoboBuddy reads the Feetech Min_Position_Limit and Max_Position_Limit registers and installs those values as temporary runtime limits for the active robot session.
Hardware rules:
- use local bridge only
- no direct browser hardware control
- calibration is required before real movement
- motion is rejected by the bridge until the operator explicitly arms the connected session
- STOP remains available
- bridge fails safely if LeRobot is not installed
The main page owns the real hardware connection. Python and Gemini programs can route through the shared robot runtime only after the SO-101 is connected, calibrated, and armed for the current session.
Python multi-joint programming
Select so101_follower on python.html and use the manifest-driven robot API. The canonical joint ids are shoulder_pan, shoulder_lift, elbow_flex, wrist_flex, wrist_roll, and gripper.
robot.move_joints({
"shoulder_pan": 5,
"shoulder_lift": -85,
"elbow_flex": 80,
"wrist_flex": 68,
"wrist_roll": 83,
"gripper": 80,
}, speed=30)
Every joint number is an absolute end position in the active calibrated joint coordinate. For example, robot.move_joint("elbow_flex", 40) targets position 40°; it does not add 40°. Zero and positive direction are joint-specific calibration/manifest coordinates, not world-space angles. Relative changes must be calculated explicitly from the planned pose, such as pose["elbow_flex"] -= 5, before sending the resulting absolute target.
One robot.move_joints({...}) call remains one RoboBuddy command and one bridge execution request. If every included joint is already within its relative-step guard, the bridge uses one LeRobot action map and one Feetech bus sync_write. For a larger move, it automatically expands the command into measured, bounded action maps. The included joints advance together along a straight joint-space path, but synchronized physical arrival is not guaranteed. The current program bridge validates speed from 1..100 but does not use it to set physical velocity, duration, or common arrival time.
LeRobot's relative-target protection defaults to max_relative_target = 12.0 unless configured otherwise. Before each program write, RoboBuddy reads fresh measured feedback and computes the largest common progress fraction that keeps every included joint within 90% of its active guard. While measured motion continues, the bridge refreshes that bounded look-ahead goal on each observation cycle; only the exact final destination must fully settle. The final requested numbers remain absolute destinations; users do not calculate safety increments. A returned action that differs from an accepted bounded goal is a fault rather than a silent partial success; motion that stops making measured progress also faults. In either case, the bridge holds measured position when possible and disarms.
Verified Home, finite Python poses, manual sliders, and Leader modes all retain their separate motion-ownership paths. Each may use bounded intermediate actions while its requested target remains absolute.
robot.get_joints() returns planned interpreter state, not a live blocking hardware observation. A finite Python pose is also separate from SolU's continuously refreshed Virtual Leader and Phone Leader sessions. One Python run cannot control SO-101 and LeKiwi concurrently.
See Programming SO-101 and LeKiwi with RoboBuddy Python for complete examples, joint tables, and troubleshooting.
Readiness states:
Bridge: the localhost bridge answered/healthPort: the operator selected or entered a COM portCalibration: LeRobot reports calibration for the selectedrobotInstanceIdArm Motion: the operator enabled the non-persisted session toggle and the bridge accepted the arm request
If the bridge returns NEEDS_CALIBRATION, run the displayed command, for example:
lerobot-calibrate --robot.type=so101_follower --robot.port=COM24 --robot.id=classroom_so101_01
The bridge disconnects after this status so PowerShell calibration can open the same COM port.
The bridge never starts LeRobot calibration from a browser request because calibration is interactive and physically manipulates the arm.
The real adapter imports LeRobot lazily in bridge/robobuddy_bridge/adapters/so101_lerobot.py and maps validated RoboBuddy commands to LeRobot <joint>.pos actions.
Manual Control
The slider thumb and large value are the requested target. While motion is armed, dragging sends coalesced targets at up to 30 Hz with one HTTP request and one serial operation in flight at a time. The 3D preview still responds immediately. Releasing the pointer or keyboard key sends a final target that may continue converging through LeRobot's relative-target protection.
When LeRobot clips a distant target to its configured relative step, RoboBuddy latches that intermediate servo goal and waits for fresh measured progress before issuing the next step. Repeated browser updates that would produce the same clipped goal are coalesced, and they do not reset the 5 Hz observation schedule. A contact-blocked gripper therefore keeps its semantic requested target without repeatedly generating identical all-servo reads and writes.
The smaller Actual line is measured servo feedback cached by the bridge. Delayed observations never move the active slider thumb. While a joint is moving, the bridge samples at 5 Hz and reports Moving, Reached, Lagging, Cancelled, or Feedback unavailable without adding a serial read for every browser poll.
LeRobot's bounded-target send performs its own Present_Position read. RoboBuddy serializes that operation and retries transport-level ConnectionError failures up to three total attempts with short backoffs. The same policy protects measured-position and calibration-register reads. One recovered packet miss therefore does not disarm motion or change a requested target; validation errors, semantic calibration mismatches, disconnected-device errors, and motor-reported faults are never retried. Three failed command attempts remain terminal. Manual feedback is considered unavailable only after three consecutive failed observation transactions, with any successful observation resetting that count.
Connection has a separate, narrowly scoped recovery policy. LeRobot exclusively owns the selected COM port, and RoboBuddy retries only the exact pre-configuration handshake failure where expected motor IDs are missing. It creates a fresh robot instance for each of three attempts and closes a failed bus without sending torque or configuration commands. Wrong-model, port, semantic calibration, configuration, and other connection failures are not retried as whole connections. Transient ConnectionError packet loss while reading calibration registers still uses the normal bounded serial retry. A connection is not reported ready until calibration limits and fresh measured positions have both been read successfully.
After a terminal communication failure the bridge remains connected when possible but disarms motion. Selecting Arm again clears the failed operation and stale error display without reading, holding, replaying, or otherwise moving the robot. Requested and measured positions remain visible, and a new slider, Home, or program command is required before movement resumes.
If a non-final drag stream is interrupted for 250 ms, the bridge expires that target and holds the latest measured pose. A newer update from the same still-active drag may resume smoothly; an explicit cancellation retires the session so late requests cannot revive it. Starting another joint or a program transfers motion ownership away from the active manual stream. Disarming, disconnecting, hiding the page, Home, and STOP cancel it as safety operations. STOP and disconnect require explicit re-arming before another movement command is accepted.
During a continuing takeover, an arm joint is held at its latest measured pose before the next joint moves. The gripper is different: its requested target remains latched while another joint or program moves so contact preload is not replaced with the measured jaw position. A gripper touching an object can therefore remain below its requested target and report Lagging without RoboBuddy reducing that target. If an explicit cancellation or safety operation occurs, any unsent gripper takeover command is discarded and cannot be revived by a late request.
If an object still slips while the requested gripper target remains unchanged, investigate payload mass, contact geometry, surface friction, power delivery, and the motor's configured protections separately. Do not treat positional limits as permission to increase gripper torque or current.
Safety holds are issued only from a successful fresh feedback read. If feedback is unavailable, STOP and disarm still cancel motion and clear arming, but the bridge does not write cached joint coordinates back to the motors.
Targets outside the current verified safe limits, non-finite values, unknown joints, and invalid program batches are rejected in full before manual takeover or serial I/O. Rejection does not disarm the arm, alter requested targets, or cancel a latched gripper preload. Persistent transport or motor feedback failures remain terminal and disarm motion.
If the bridge reports that no motors responded after three connection attempts, inspect the robot power supply under load, the USB adapter and cable, and every serial-bus connector. Persistent whole-bus packet loss is a hardware or transport fault; do not increase retry counts or disable automatic disarming to conceal it.
Cached-state polling tolerates two transient failures. After three consecutive missed updates, the browser marks the bridge offline, clears live-control state and hardware-limit overrides, and blocks further hardware commands until the operator tests and reconnects the bridge.
Keyboard controls use 1 degree or 1 percent steps, Shift for 5-unit steps, and Home/End for the effective minimum/maximum. Targets are quantized to encoder-supported steps before transmission.
Servo-register limits
For degree joints, the bridge translates Feetech register ticks with the same 4095-tick midpoint model used by LeRobot. The gripper is translated to 0–100 percent through its calibration range. Exact floating-point limits remain internal; the UI rounds only endpoint labels. Raw ticks, translated ranges, and encoder resolution are available in Diagnostics.
Values configured with FT SCServo Debug therefore become the Manual Control slider endpoints after a successful connection, provided the active LeRobot calibration matches the connected servos. A mismatch may display the values as Hardware · unverified, but all hardware motion remains blocked until calibration is corrected. RoboBuddy does not edit or silently accept a LeRobot calibration file.
Hardware register limits are positional bounds only. They do not detect self-collision, protect a payload, or replace supervised operation.