Skip to main content

so101-control

A small Python package for programmatic control of an SO-101 robot arm via lerobot.

Move the arm to specific joint-angle configurations, or drive the end-effector to a Cartesian target using inverse kinematics — no teleop or trained policy required. Install it as a library in your own project, or use the bundled so101-control CLI.

What's included

Path Purpose
src/so101_control/ The installable so101_control package (control wrappers + CLI).
src/so101_control/control.py Reusable primitives: move_to_joints, go_to_ee, home, rest.
src/so101_control/cli.py The so101-control command-line entry point.
examples/ Example scripts showing library usage.

Installation

Requires Python ≥3.12 and uv (or any PEP 621-aware installer such as pip).

From source (development)

git clone https://github.com/vi-vi-3482/so101-control
cd so101-control
uv sync

This installs the so101_control package (importable as import so101_control) together with lerobot[kinematics,feetech] (which pulls in placo for IK) and pins cmeel-urdfdom==4.0.1 to work around a shared-library mismatch in the placo wheels.

Platform compatibility

CI runs on macOS and Linux. Windows is not currently supported for direct installation: the placo/pin/coal-library chain depends on cmeel-qhull, which has no prebuilt Windows wheel and fails to build from source on recent CMake (its cmake_minimum_required predates 3.5, which modern CMake no longer accepts). This is an upstream packaging issue, not specific to so101-control.

On Windows, run so101-control inside WSL using a Linux install of lerobot until a newer upstream release ships working Windows wheels.

As a dependency in your own project

Add it to your pyproject.toml dependencies (or let uv do it for you):

# from a local checkout
uv add /path/to/so101-control
# …or from git
uv add "git+https://github.com/vi-vi-3482/so101-control.git"

Then import it from your own code:

from so101_control import (
    HOME_JOINTS,
    go_to_ee,
    home,
    move_to_joints,
    print_status,
    rest,
)

Fetch the SO-101 URDF + meshes (only needed for end-effector / IK mode)

The URDF references STL meshes in an assets/ folder. Download both:

curl -L -o so101_new_calib.urdf \
  https://raw.githubusercontent.com/TheRobotStudio/SO-ARM100/main/Simulation/SO101/so101_new_calib.urdf
mkdir -p assets && cd assets
for f in base_motor_holder_so101_v1 base_so101_v2 motor_holder_so101_base_v1 \
  motor_holder_so101_wrist_v1 moving_jaw_so101_v1 rotation_pitch_so101_v1 \
  sts3215_03a_no_horn_v1 sts3215_03a_v1 under_arm_so101_v1 upper_arm_so101_v1 \
  waveshare_mounting_plate_so101_v2 wrist_roll_follower_so101_v1 \
  wrist_roll_pitch_so101_v2; do
  curl -sL -o "$f.stl" \
    "https://raw.githubusercontent.com/TheRobotStudio/SO-ARM100/main/Simulation/SO101/assets/$f.stl"
done
cd ..

Calibrate the arm (one-time)

If you haven't already calibrated your follower, follow the commands in LeRobot. Calibration is stored under ~/.cache/huggingface/lerobot/calibration/robots/<robot-id>/ and is located automatically by the --robot-id you pass to the CLI.

Quick start

Once installed, the so101-control CLI is available.

Dry-run (no hardware needed — prints the planned trajectory):

so101-control --mode joint \
    --target-joints shoulder_pan=0,shoulder_lift=-20,elbow_flex=60,wrist_flex=30,wrist_roll=0,gripper=50 \
    --dry-run

Live joint move:

so101-control \
    --port /dev/tty.usbmodem585A0076841 \
    --robot-id my_awesome_follower_arm \
    --mode joint \
    --target-joints shoulder_pan=0,shoulder_lift=-20,elbow_flex=60,wrist_flex=30,wrist_roll=0,gripper=50

End-effector move (position-only IK by default):

so101-control \
    --port /dev/tty.usbmodem585A0076841 \
    --robot-id my_awesome_follower_arm \
    --urdf-path /absolute/path/to/so101_new_calib.urdf \
    --mode ee --target-xyz 0.2,0.0,0.15

You can also invoke the module directly without installing the console script:

uv run python -m so101_control.cli --mode home --dry-run

See the Library usage section below for the full API, library usage, safety knobs, and the joint schema.

Library usage

import time

from lerobot.model.kinematics import RobotKinematics
from lerobot.robots.so_follower import SO101Follower, SO101FollowerConfig

from so101_control import (
    HOME_JOINTS,
    go_to_ee,
    home,
    move_to_joints,
    print_status,
    rest,
)

robot = SO101Follower(
    SO101FollowerConfig(
        port="/dev/tty.usbmodem...", id="my_awesome_follower_arm"
    )
)
robot.connect(calibrate=False)

print("Moving to home joint angle")
move_to_joints(robot, HOME_JOINTS, duration_s=2.0)

kin = RobotKinematics(
    urdf_path="/absolute/path/to/so101_new_calib.urdf",  # Must be an absolute path
    target_frame_name="gripper_frame_link",
    joint_names=[
        "shoulder_pan",
        "shoulder_lift",
        "elbow_flex",
        "wrist_flex",
        "wrist_roll",
    ],
)

print_status(kin, robot)

pos = [0.3, 0.0, 0.5]
print(f"Moving to EE position {pos}")
go_to_ee(kin, robot, pos, duration_s=3.0)
time.sleep(0.5)


rest(robot)
time.sleep(0.1)
robot.disconnect()

A ready-to-edit version of the above lives at examples/example_move_ee.py.

Primitives

The package exposes four reusable primitives (all runnable without hardware via dry_run=True):

Primitive Description
move_to_joints Smoothly interpolate to a *.pos -> value joint dict.
go_to_ee Solve IK and route the result through move_to_joints.
home Return the arm to HOME_JOINTS (extended, neutral).
rest Return the arm to REST_JOINTS — the curled-up pose.

Joint schema

SO-101 has 5 arm DOF + 1 gripper. The package exposes the following constants:

Constant Value
ARM_JOINTS shoulder_pan, shoulder_lift, elbow_flex, wrist_flex, wrist_roll
ALL_JOINTS ARM_JOINTS + [gripper]
ACTION_KEYS ARM_JOINTS/ALL_JOINTS suffixed with .pos (the lerobot action keys).
EE_FRAME gripper_frame_link — the IK target frame in the SO-ARM100 URDF.
HOME_JOINTS Neutral extended pose (all arm joints at 0°, gripper at 50).
REST_JOINTS Curled-up pose; safe for storage / power-off.

Safety knobs

  • --max-relative-target (deg, CLI) / max_relative_target (lerobot config): clips per-step joint jumps inside send_action. Strongly recommended on hardware.
  • duration_s / fps: control the interpolation rate. Slower = gentler.
  • orientation_weight (default 0.0): IK orientation weight. 0.0 means position-only; pass --target-rpy to add a soft orientation constraint.
  • position_weight (default 1.0): IK position weight.

Additional Notes

The URDF file path must be an absolute path from the system root, otherwise it fails to find the assets/ folder containing the mesh STLs.

Robot Coordinate system

Orientation facing the robot. The EE is pointed forward in the home position.

Axis Orientation
X Forward
Y Right
Z Up

Release files for so101-control 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for so101-control 0.1.1
File Size Uploaded
so101_control-0.1.1.tar.gz 26.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for so101-control 0.1.1
File Interpreter ABI Platform
so101_control-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 52.7 kB

Release files / so101_control-0.1.1.tar.gz

Download URL so101_control-0.1.1.tar.gz
Size 26.2 kB
Tags Source
SHA-256 checksum
How to use checksums
dad3748702974549ca6e394327744e86ceda706c37268967b398bed2b53e6f7e
BLAKE2b-256 checksum
How to use checksums
b495f989bb70a9960cdfd576bae210a41c9eccab6fe98a47f55a0d1fd65994ce
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 2, 2026.

Transparency log

Release files / so101_control-0.1.1-py3-none-any.whl

Download URL so101_control-0.1.1-py3-none-any.whl
Size 26.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d39828ded8b13a808c5a30d7f8b4b477037e6dc310b73d62fc5f238af408665d
BLAKE2b-256 checksum
How to use checksums
9c66766d8b97a9ceaf1a034ff46827cdf4759e8215a4dafbda3a1973f780ef9b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 2, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page