Software Setup

Plugin installation, leader/follower arm configuration, ROS2 bimanual control, LeRobot DK1 integration, Python API, and troubleshooting. From a fresh Ubuntu install to a moving bimanual system.

Jump to a section:

Step 1 — SDK Installation

SDK Installation

The DK1 SDK is distributed as a LeRobot plugin via the trlc-dk1 repository. It registers four device types: dk1_leader, dk1_follower, bi_dk1_leader, and bi_dk1_follower.

Install LeRobot first

pip install lerobot

Clone and install the DK1 plugin

git clone https://github.com/TRLC-AI/trlc-dk1.git
cd trlc-dk1
uv pip install -e .

Verify the installation

python3 -c "from lerobot.common.robots import make_robot; print('DK1 plugin OK')"

# Check that DK1 device types are registered
python3 -c "
from lerobot.common.robots.factory import get_robot_types
types = get_robot_types()
for t in types:
    if 'dk1' in t:
        print(t)
"

You should see dk1_leader, dk1_follower, bi_dk1_leader, bi_dk1_follower printed. If not, ensure the plugin installed correctly with uv pip show trlc-dk1.

Install from a specific commit (for reproducibility)

cd trlc-dk1
git checkout v0.3.0
uv pip install -e .
Step 2 — Leader/Follower Configuration

Leader/Follower Arm Configuration

The DK1's bimanual system relies on port assignment to distinguish the leader arm (Dynamixel XL330) from the follower arm (DM4340 + DM4310). Incorrect port assignments are the most common setup error.

Detect USB serial ports

Run the LeRobot port-detection utility with one arm connected at a time:

# Connect ONLY the leader arm (Dynamixel XL330)
python -m lerobot.scripts.find_motors_bus_port
# Note the reported port, e.g. /dev/ttyACM0

# Disconnect leader, connect ONLY the follower arm (DM series)
python -m lerobot.scripts.find_motors_bus_port
# Note the reported port, e.g. /dev/ttyACM1

Create the bimanual robot config

Create a YAML configuration file for the bimanual pair. LeRobot uses this to route commands to the correct arm:

# ~/.lerobot/robots/dk1_bimanual.yaml
robot_type: bi_dk1_follower
leader_arms:
  left:
    port: /dev/ttyACM0
    motors: [shoulder_pan, shoulder_lift, elbow_flex, wrist_flex, wrist_roll, gripper_left, gripper_right]
follower_arms:
  left:
    port: /dev/ttyACM1
    motors: [shoulder_pan, shoulder_lift, elbow_flex, wrist_flex, wrist_roll, gripper_left, gripper_right]
cameras:
  wrist_left:
    type: opencv
    index: 0
    fps: 30
    width: 640
    height: 480
  overhead:
    type: opencv
    index: 2
    fps: 30
    width: 640
    height: 480

Verify the configuration

python -m lerobot.scripts.control_robot \
  --robot.type=bi_dk1_follower \
  --robot.config=~/.lerobot/robots/dk1_bimanual.yaml \
  --control.type=none

This connects to both arms without moving them. Check for connection errors. If either arm fails to connect, re-run port detection or swap port assignments.

Port persistence: USB serial ports can change between reboots. Use udev rules to bind a port to a specific arm by USB serial number. See the Setup Guide for the udev rule template.

Step 3 — ROS2 Bimanual Setup

ROS2 Bimanual Control

ROS2 Humble provides a higher-level control layer for the DK1 with full MoveIt2 bimanual planning support. This is optional for LeRobot-only data collection workflows.

Install ROS2 Humble and bimanual packages

sudo apt update && sudo apt install ros-humble-desktop \
  ros-humble-ros2-control ros-humble-ros2-controllers \
  ros-humble-moveit ros-humble-joint-state-publisher-gui -y

Clone and build the DK1 ROS2 package

mkdir -p ~/dk1_ws/src && cd ~/dk1_ws/src
git clone https://github.com/TRLC-AI/trlc-dk1-ros2.git
cd ~/dk1_ws
source /opt/ros/humble/setup.bash
colcon build --symlink-install

Launch in bimanual mode

source ~/dk1_ws/install/setup.bash

# Launch both arms (use_fake_hardware for testing without hardware)
ros2 launch trlc_dk1_ros2 dk1_bimanual.launch.py \
  use_fake_hardware:=false \
  leader_port:=/dev/ttyACM0 \
  follower_port:=/dev/ttyACM1

Test with fake hardware (no arms needed)

ros2 launch trlc_dk1_ros2 dk1_bimanual.launch.py \
  use_fake_hardware:=true

Send a bimanual trajectory

ros2 topic pub /follower_left/joint_trajectory_controller/joint_trajectory \
  trajectory_msgs/msg/JointTrajectory \
  '{joint_names: ["shoulder_pan"], points: [{positions: [0.3], time_from_start: {sec: 2}}]}'
Step 4 — LeRobot DK1 Config

LeRobot DK1 Configuration

LeRobot with the DK1 plugin handles bimanual teleoperation recording natively. The bi_dk1_follower device type records from both arms and all cameras simultaneously.

Calibrate both arms

# Calibrate the leader arm
python -m lerobot.scripts.control_robot \
  --robot.type=dk1_leader \
  --robot.port=/dev/ttyACM0 \
  --control.type=calibrate

# Calibrate the follower arm
python -m lerobot.scripts.control_robot \
  --robot.type=dk1_follower \
  --robot.port=/dev/ttyACM1 \
  --control.type=calibrate

Start bimanual teleoperation

python -m lerobot.scripts.control_robot \
  --robot.type=bi_dk1_follower \
  --robot.config=~/.lerobot/robots/dk1_bimanual.yaml \
  --control.type=teleoperate

Record a bimanual dataset

python -m lerobot.scripts.control_robot \
  --robot.type=bi_dk1_follower \
  --robot.config=~/.lerobot/robots/dk1_bimanual.yaml \
  --control.type=record \
  --control.fps=30 \
  --control.repo_id=your-username/dk1-bimanual-pick-place-v1 \
  --control.num_episodes=50 \
  --control.single_task="Bimanual: pick up the block with left arm, transfer to right arm" \
  --control.warmup_time_s=5 \
  --control.reset_time_s=10

Push to HuggingFace Hub

huggingface-cli login
python -m lerobot.scripts.push_dataset_to_hub \
  --repo_id=your-username/dk1-bimanual-pick-place-v1
Step 5 — Python API

Python API for Bimanual Control

The DK1 Python API provides direct access to both arms via serial. No ROS2 required for basic control and data logging.

Connect both arms

from trlc_dk1 import DK1Leader, DK1Follower, BimanualDK1

# Connect individually
leader = DK1Leader(port="/dev/ttyACM0")
follower = DK1Follower(port="/dev/ttyACM1")
leader.connect()
follower.connect()

# Or use the bimanual controller (recommended)
robot = BimanualDK1(
    leader_port="/dev/ttyACM0",
    follower_port="/dev/ttyACM1"
)
robot.connect()

Read joint states from both arms

import time
from trlc_dk1 import BimanualDK1

robot = BimanualDK1(leader_port="/dev/ttyACM0", follower_port="/dev/ttyACM1")
robot.connect()

for _ in range(100):
    leader_state = robot.get_leader_state()
    follower_state = robot.get_follower_state()
    print(f"Leader:   {leader_state.positions}")
    print(f"Follower: {follower_state.positions}")
    time.sleep(0.033)  # 30 Hz

robot.disconnect()

Run leader-follower loop manually

from trlc_dk1 import BimanualDK1
import time

robot = BimanualDK1(leader_port="/dev/ttyACM0", follower_port="/dev/ttyACM1")
robot.connect()
robot.enable_follower()

try:
    while True:
        leader_state = robot.get_leader_state()
        # Apply leader positions to follower (scaled if needed)
        robot.set_follower_positions(leader_state.positions)
        time.sleep(0.01)  # 100 Hz control loop
finally:
    robot.disable_follower()
    robot.disconnect()
Optional — Simulation

Simulation Support

The DK1 supports MuJoCo bimanual simulation with a calibrated model that mirrors real-hardware kinematics. Train policies in simulation before deploying to hardware.

MuJoCo bimanual simulation

pip install mujoco
git clone https://github.com/TRLC-AI/trlc-dk1-mujoco.git
cd trlc-dk1-mujoco

# Run the bimanual simulation with leader/follower
python examples/bimanual_sim.py

# Run with keyboard teleop
python examples/bimanual_sim.py --teleop keyboard

Train a policy against the MuJoCo environment

python -m lerobot.scripts.train \
  --policy.type=act \
  --env.type=dk1_bimanual_sim \
  --policy.chunk_size=100 \
  --training.num_epochs=5000 \
  --output_dir=outputs/dk1-act-sim

Sim-to-Real alignment: The DK1 MuJoCo model uses the TRLC-DK1-Follower_v0.3.0 STEP file geometry and measured DM4340/DM4310 motor torque curves. Policies trained in sim transfer to real hardware with minimal tuning for structured pick-and-place tasks.

Troubleshooting

Top 3 Bimanual-Specific Issues

Error 1 Arms assigned to the same port — follower mirrors leader but lags badly

Both arms connected to the same /dev/ttyACM* port, or port assignments swapped. The leader arm (Dynamixel XL330) and follower arm (DM series) use different protocols; wrong assignment causes immediate control failure.

Fix:

# 1. Unplug both arms
# 2. Connect ONLY the leader arm (XL330 servos)
python -m lerobot.scripts.find_motors_bus_port
# Note: leader_port = /dev/ttyACM?

# 3. Disconnect leader, connect ONLY the follower arm (DM servos)
python -m lerobot.scripts.find_motors_bus_port
# Note: follower_port = /dev/ttyACM?

# 4. Update your YAML config with the correct ports
# 5. Create udev rules to make assignments permanent
Error 2 Follower arm oscillates or overshoots during teleoperation

The follower arm's PD gains are too high for the current payload or arm configuration. This is especially common when the arms are loaded with end-effectors or when operating at full extension.

Fix:

# Reduce follower PD gains in the DK1 config
# Edit trlc-dk1/configs/follower_gains.yaml:
joint_gains:
  default:
    kp: 30   # reduce from default 50
    kd: 0.5  # reduce from default 1.0
  wrist:
    kp: 15   # wrist joints need lower gains
    kd: 0.3

# Apply and restart teleoperation
python -m lerobot.scripts.control_robot \
  --robot.type=bi_dk1_follower \
  --control.type=teleoperate
Error 3 Camera frames drop or go out of sync during bimanual recording

USB bandwidth contention with two cameras plus two USB serial arms on the same bus controller. LeRobot timestamp skew between camera streams and joint state readings exceeds acceptable limits.

Fix:

# 1. Check which USB bus each device is on
lsusb -t

# 2. Spread devices across separate USB bus controllers
#    - Cameras: use a powered USB hub on one controller
#    - Arms: connect directly on a different controller

# 3. Reduce camera resolution if bandwidth is still tight
# In dk1_bimanual.yaml:
cameras:
  wrist_left:
    width: 480
    height: 320   # lower resolution reduces USB bandwidth

# 4. Verify timestamp skew is acceptable
python -m trlc_dk1.tools.check_sync \
  --config ~/.lerobot/robots/dk1_bimanual.yaml
# Target: < 5ms skew between all streams

Still stuck? Ask on the DK1 Forum or check existing GitHub issues.

Software Working? Start Collecting Bimanual Data.

Once both arms are moving, the next step is teleoperation and bimanual dataset recording.