Browse by type
SJTU & Evo-Tech
Architecture Overview

| Getting Started | Training Pipeline | Project Info |
|---|---|---|
| ⚡ Quick Start | 4) Value Function Training | Model & Dataset |
| 1) Installation | 5) Value Inference | Community Channels |
| 2) Hardware Setup | 6) Policy Training | Affiliations |
| 3) Data Collection | 7) Closed-loop Rollout and Next Round | Citation / License |
Value Visual Results
Success Case

Failure Case

Policy Rollout Visual Results

Human-in-the-Loop Visual Results

LeRobot-aligned foundation: we use LeRobot as the base of this codebase because its inference and data-collection logic are highly aligned with real-world RL workflows.
git clone https://github.com/MINT-SJTU/Evo-RL.git
cd Evo-RL
conda create -y -n evo-rl python=3.10
conda activate evo-rl
pip install -e .
For setup details and platform-specific dependencies, follow the official LeRobot configuration guide.
For SO-series setup, please follow the official tutorial in detail and complete all installation and configuration steps there before continuing. The examples below use SO101 as the reference configuration.
Recommended path strategy:
/dev/serial/by-id/ (stable across reboots)./dev/v4l/by-id/; if IDs are not unique, use /dev/v4l/by-path/.by-id, camera paths use by-path.You can inspect available stable paths with:
ls -l /dev/serial/by-id/
ls -l /dev/v4l/by-id/
ls -l /dev/v4l/by-path/
For single-arm users, no major changes are required. After setup, run the command below to verify your system is ready for the next stage:
lerobot-teleoperate \
--robot.type=so101_follower \
--robot.port=/dev/serial/by-id/<SO101_FOLLOWER_PORT> \
--robot.id=my_so101_follower \
--teleop.type=so101_leader \
--teleop.port=/dev/serial/by-id/<SO101_LEADER_PORT> \
--teleop.id=my_so101_leader
For dual-arm users, we recommend mirroring the mechanical parts corresponding to servos 4/5/6 on the left leader and left follower arms, which usually provides a more natural bimanual operation feel.
Before running the dual-arm command, make sure calibration files exist under ~/.cache/huggingface/lerobot/calibration/ like:
calibration/
├── robots
│ └── so_follower
│ ├── bi_so101_follower_left.json
│ └── bi_so101_follower_right.json
└── teleoperators
└── so_leader
├── bi_so101_leader_left.json
└── bi_so101_leader_right.json
This layout is slightly different from single-arm setup.
Then run this command to verify dual-arm setup:
lerobot-teleoperate \
--robot.type=bi_so_follower \
--robot.left_arm_config.port=/dev/serial/by-id/<LEFT_FOLLOWER_PORT> \
--robot.right_arm_config.port=/dev/serial/by-id/<RIGHT_FOLLOWER_PORT> \
--robot.id=bi_so101_follower \
--teleop.type=bi_so_leader \
--teleop.left_arm_config.port=/dev/serial/by-id/<LEFT_LEADER_PORT> \
--teleop.right_arm_config.port=/dev/serial/by-id/<RIGHT_LEADER_PORT> \
--teleop.id=bi_so101_leader
Before data collection, validate camera mapping first.
Check whether each camera supports your target setting (for example, 640x480 @ 30):
v4l2-ctl -d /dev/v4l/by-path/<CAM_PATH> --list-formats-ext
Single-arm camera check (example):
lerobot-teleoperate \
--robot.type=so101_follower \
--robot.port=/dev/serial/by-id/<SO101_FOLLOWER_PORT> \
--robot.id=my_so101_follower \
--robot.cameras='{ front: {type: opencv, index_or_path: "/dev/v4l/by-path/<FRONT_CAM>", width: 640, height: 480, fps: 30}}' \
--teleop.type=so101_leader \
--teleop.port=/dev/serial/by-id/<SO101_LEADER_PORT> \
--teleop.id=my_so101_leader \
--display_data=true
Dual-arm camera check (example):
lerobot-teleoperate \
--robot.type=bi_so_follower \
--robot.left_arm_config.port=/dev/serial/by-id/<LEFT_FOLLOWER_PORT> \
--robot.right_arm_config.port=/dev/serial/by-id/<RIGHT_FOLLOWER_PORT> \
--robot.id=my_bi_so101_follower \
--robot.left_arm_config.cameras='{ wrist: {type: opencv, index_or_path: "/dev/v4l/by-path/<LEFT_WRIST_CAM_PATH>", width: 640, height: 480, fps: 30}}' \
--robot.right_arm_config.cameras='{ wrist: {type: opencv, index_or_path: "/dev/v4l/by-path/<RIGHT_WRIST_CAM_PATH>", width: 640, height: 480, fps: 30}, front: {type: opencv, index_or_path: "/dev/v4l/by-path/<FRONT_CAM_PATH>", width: 640, height: 480, fps: 30}}' \
--teleop.type=bi_so_leader \
--teleop.left_arm_config.port=/dev/serial/by-id/<LEFT_LEADER_PORT> \
--teleop.right_arm_config.port=/dev/serial/by-id/<RIGHT_LEADER_PORT> \
--teleop.id=my_bi_so101_leader \
--display_data=true
For dual-arm camera mapping, it is fine to attach front under either the left-arm or right-arm camera config. If you use more camera views, place them under either the left or right arm camera config as well.
If needed, you can also use temporary device paths (for example /dev/ttyACM* and /dev/video*) during initial debugging.
PiPER commands use the USB-CAN adapter's stable ID_SERIAL_SHORT instead of a temporary Linux
canN name. List the connected adapters with:
for device in /sys/class/net/*; do
[ "$(cat "$device/type" 2>/dev/null)" = "280" ] || continue
serial=$(udevadm info --query=property --path="$device" | sed -n 's/^ID_SERIAL_SHORT=//p')
echo "$(basename "$device"): $serial"
done
Then configure the adapters needed for the run:
lerobot-setup-can --mode=setup \
--usb_can_serials=<USB_CAN_SERIAL_1>,<USB_CAN_SERIAL_2>,<USB_CAN_SERIAL_3>,<USB_CAN_SERIAL_4>
For single-arm users, run the command below to verify the system is ready:
lerobot-teleoperate \
--robot.type=piperx_follower \
--robot.port=<FOLLOWER_USB_CAN_SERIAL> \
--robot.id=my_piperx_follower \
--teleop.type=piperx_leader \
--teleop.port=<LEADER_USB_CAN_SERIAL> \
--teleop.id=my_piperx_leader
For bimanual users, run this command to verify dual-arm teleoperation:
lerobot-teleoperate \
--robot.type=bi_piperx_follower \
--robot.id=my_bi_piperx_follower \
--robot.left_arm_config.port=<LEFT_FOLLOWER_USB_CAN_SERIAL> \
--robot.right_arm_config.port=<RIGHT_FOLLOWER_USB_CAN_SERIAL> \
--teleop.type=bi_piperx_leader \
--teleop.id=my_bi_piperx_leader \
--teleop.left_arm_config.port=<LEFT_LEADER_USB_CAN_SERIAL> \
--teleop.right_arm_config.port=<RIGHT_LEADER_USB_CAN_SERIAL>
For PiPER (non-X), replace bi_piperx_follower/bi_piperx_leader with bi_piper_follower/bi_piper_leader.
Collect rollout data with lerobot-human-inloop-record.
Bimanual template:
lerobot-human-inloop-record \
--robot.type=bi_so_follower \
--robot.left_arm_config.port=/dev/serial/by-id/<LEFT_FOLLOWER_PORT> \
--robot.right_arm_config.port=/dev/serial/by-id/<RIGHT_FOLLOWER_PORT> \
--robot.id=my_bi_so101_follower \
--robot.left_arm_config.cameras='{ wrist: {type: opencv, index_or_path: "/dev/v4l/by-path/<LEFT_WRIST_CAM_PATH>", width: 640, height: 480, fps: 30, fourcc: "MJPG"}}' \
--robot.right_arm_config.cameras='{ wrist: {type: opencv, index_or_path: "/dev/v4l/by-path/<RIGHT_WRIST_CAM_PATH>", width: 640, height: 480, fps: 30, fourcc: "MJPG"}, front: {type: intelrealsense, serial_number_or_name: "<REALSENSE_SN>", width: 640, height: 480, fps: 30, warmup_s: 2}}' \
--teleop.type=bi_so_leader \
--teleop.left_arm_config.port=/dev/serial/by-id/<LEFT_LEADER_PORT> \
--teleop.right_arm_config.port=/dev/serial/by-id/<RIGHT_LEADER_PORT> \
--teleop.id=my_bi_so101_leader \
--dataset.repo_id=<HF_USERNAME_OR_ORG>/<DATASET_NAME> \
--dataset.single_task="<YOUR_TASK_DESCRIPTION>" \
--dataset.num_episodes=<NUM_EPISODES> \
--dataset.episode_time_s=<EPISODE_SECONDS> \
--dataset.reset_time_s=<RESET_SECONDS> \
--dataset.push_to_hub=true \
--display_data=true
Recommendation: use fourcc: "MJPG" for OpenCV and warmup_s for RealSense. In this example front uses RealSense, but you can switch it to OpenCV with the same structure.
Bimanual template (left/right, PiPER-X example):
lerobot-human-inloop-record \
--robot.type=bi_piperx_follower \
--robot.id=my_bi_piperx_follower \
--robot.left_arm_config.port=<LEFT_FOLLOWER_USB_CAN_SERIAL> \
--robot.right_arm_config.port=<RIGHT_FOLLOWER_USB_CAN_SERIAL> \
--teleop.type=bi_piperx_leader \
--teleop.id=my_bi_piperx_leader \
--teleop.left_arm_config.port=<LEFT_LEADER_USB_CAN_SERIAL> \
--teleop.right_arm_config.port=<RIGHT_LEADER_USB_CAN_SERIAL> \
--dataset.repo_id=<HF_USERNAME_OR_ORG>/<DATASET_NAME> \
--dataset.single_task="<YOUR_TASK_DESCRIPTION>" \
--dataset.num_episodes=<NUM_EPISODES> \
--dataset.episode_time_s=<EPISODE_SECONDS> \
--dataset.reset_time_s=<RESET_SECONDS> \
--dataset.push_to_hub=true \
--display_data=true
Hotkeys:
i: toggle intervention mode (policy <-> teleop takeover)s: mark success and end current episodef: mark failure and end current episodeRight Arrow: end the current loop earlyLeft Arrow: end early and re-record the current episodeEsc: stop the recording sessionQuick quality check:
lerobot-dataset-report --dataset <HF_USERNAME_OR_ORG>/<DATASET_NAME>
This prints: dataset meta, totals, episode-length stats/histogram, success/intervention metrics, task list, and full feature schema.
Train the value function on the current dataset. Current default: [Pi
$ claude mcp add Evo-RL \
-- python -m otcore.mcp_server <graph>