LeRobot plugin for the Agilex Piper 7-DOF robotic arm. Provides follower (robot) and leader (teleoperator) interfaces that integrate directly with the LeRobot framework for teleoperation, dataset recording, and autonomous policy deployment.
- Leader-Follower Teleoperation: Mirror a leader arm's movements onto a follower arm in real time
- Dataset Recording: Collect episodes (joint positions + camera frames) for imitation learning
- CAN Bus Communication: Direct hardware control via
piper_sdkandwego_piper - Safety Limits: Configurable
max_relative_targetto cap per-step joint movement - Camera Integration: Attach multiple USB cameras as observations on the follower
- GUI Tools: Tkinter-based UIs for direct control (
piper-ui) and teleoperation monitoring (piper-monitor)
- Python >= 3.10
- LeRobot >= 0.3.0
piper_sdkwego_piper- CAN-USB interface connected to the Piper arm
pip install lerobot_robot_piperOr install from source:
git clone <repo-url>
cd lerobot_robot_piper
pip install -e .Before connecting, each CAN interface must be brought up. Use the bundled GUI or run manually:
# Bring up a CAN interface (e.g., can0 at 1 Mbps)
sudo ip link set can0 type can bitrate 1000000
sudo ip link set can0 upThe piper-ui and piper-monitor GUIs can detect available CAN interfaces and initialize them automatically.
| Arm | Interface |
|---|---|
| Follower (robot) | e.g., can0 |
| Leader (human input) | e.g., can1 |
Run this first when setting up multiple arms.
piper-setup4-step wizard for configuring up to 4 arms:
- Scan — detect all CAN ports, initialize, connect, and read firmware version from each arm
- Config — select arm configuration (1 Leader/1 Follower, 2 Followers, 2 Leaders, 2 Leaders/2 Followers)
- Identify — click
Findfor each slot, then physically move the target arm more than 45° to assign it; useSet Roleto apply leader/follower mode andTorque Offto release the arm - Finalize — rename CAN ports to canonical names (
can_leader1,can_follower1, etc.) and save~/piper_config.json
piper-ui- CAN Setup: detect interfaces, view firmware version and USB port per interface, select role (leader/follower), initialize bitrate
- Connect: select a port via radio button, then click Connect — sliders automatically sync to the arm's current joint positions
- Torque: toggle torque on/off; sliders and Parking are disabled while torque is off
- Set Leader / Set Follower: apply
MasterSlaveConfigto switch the arm between teaching input mode and motion output mode - Parking: move the arm to the home position (joint values: 0, −100, 100, 0, 35, 0, 0)
- Joints: drag sliders to move joints in real time (only active when torque is on); live position readout next to each slider
piper-monitor- Detect and initialize CAN interfaces
- Launch pre-configured teleoperation/recording commands
- Real-time joint position bars for both arms (leader & follower)
- Follower arm status (enable state, motion status, error codes)
from lerobot_robot_piper import PiperFollowerConfig, PiperLeaderConfig, PiperFollower, PiperLeader
follower_cfg = PiperFollowerConfig(port="can0")
leader_cfg = PiperLeaderConfig(port="can1")
follower = PiperFollower(follower_cfg)
leader = PiperLeader(leader_cfg)
follower.connect()
leader.connect()
try:
while True:
action = leader.get_action() # read leader joint positions
follower.send_action(action) # mirror to follower
obs = follower.get_observation() # read follower state + cameras
finally:
follower.disconnect()
leader.disconnect()# Teleoperate
python -m lerobot.teleoperate \
--robot.type=piper_follower \
--robot.port=can0 \
--teleop.type=piper_leader \
--teleop.port=can1
# Record a dataset
python -m lerobot.record \
--robot.type=piper_follower \
--robot.port=can0 \
--teleop.type=piper_leader \
--teleop.port=can1 \
--dataset-id=my_piper_dataset| Parameter | Type | Default | Description |
|---|---|---|---|
port |
str |
"can0" |
CAN port for the follower arm |
disable_torque_on_disconnect |
bool |
True |
Disable motors when disconnecting |
cameras |
dict[str, CameraConfig] |
{} |
Named cameras for observation |
max_relative_target |
float | dict | None |
None |
Max per-step joint movement (safety limit) |
| Parameter | Type | Default | Description |
|---|---|---|---|
port |
str |
"can1" |
CAN port for the leader arm |
gripper_open_pos |
float |
50.0 |
Position value representing gripper fully open |
The Piper arm has 7 joints. Normalization maps raw encoder counts to the ranges shown below.
| Joint | Model | Normalized Range | Physical Range |
|---|---|---|---|
| Joint 1 | AGILEX-M | −100 to +100 | ±150° |
| Joint 2 | AGILEX-M | −100 to +100 | 0–180° |
| Joint 3 | AGILEX-M | −100 to +100 | −170–0° |
| Joint 4 | AGILEX-S | −100 to +100 | ±100° |
| Joint 5 | AGILEX-S | −100 to +100 | ±65° |
| Joint 6 | AGILEX-S | −100 to +100 | ±100–130° |
| Gripper | AGILEX-S | 0 to 100 | 0–68° |
The parking (home) position in normalized values: 0, −100, 100, 0, 35, 0, 0.
lerobot_robot_piper/
├── config_piper.py # PiperFollowerConfig
├── config_piper_leader.py # PiperLeaderConfig
├── piper_follower.py # PiperFollower (Robot)
├── piper_leader.py # PiperLeader (Teleoperator)
├── ui.py # piper-ui entrypoint
├── teleop_ui.py # piper-monitor entrypoint
├── arm_setup_ui.py # piper-setup entrypoint (multi-arm wizard)
└── motors/
├── piper_motors_bus.py # CAN bus abstraction
└── tables.py # Motor model tables
Apache-2.0


