Step 1: Python SDK and Virtual Environment

# Create a dedicated venv for the O6
python3 -m venv ~/o6-env
source ~/o6-env/bin/activate

# Install the LinkerBot SDK and data dependencies
pip install roboticscenter lerobot numpy torch torchvision

# Verify the import works
python3 -c "from roboticscenter.o6 import O6Robot; print('SDK OK')"

Step 2: ROS2 Controller Setup

The LinkerBot ROS2 package exposes the O6 as a standard ros2_control hardware interface. This lets you use any ROS2-compatible planner, MoveIt2, or your own controllers.

mkdir -p ~/ros2_ws/src && cd ~/ros2_ws/src
git clone https://github.com/roboticscenter/linkerbot_ros2.git

cd ~/ros2_ws
source /opt/ros/humble/setup.bash
rosdep install --from-paths src --ignore-src -r -y
colcon build --symlink-install
source install/setup.bash

Launch the controller with the arm connected:

ros2 launch linkerbot_bringup o6_bringup.launch.py \
  can_interface:=can0

In a second terminal, confirm joint state topics are publishing:

source ~/ros2_ws/install/setup.bash
ros2 topic echo /joint_states --once

Step 3: Joint Limits Reference

Joint Min (rad) Max (rad) Max Vel (rad/s)
Joint 1 (base rotation)-3.14+3.141.5
Joint 2 (shoulder)-2.09+2.091.5
Joint 3 (elbow)-2.62+2.621.5
Joint 4 (forearm roll)-3.14+3.142.0
Joint 5 (wrist pitch)-1.57+1.572.0
Joint 6 (wrist roll)-3.14+3.142.0

Step 4: Run a Scripted Trajectory

This verifies all 6 joints respond to position commands. Keep speed_fraction low on first run.

from roboticscenter.o6 import O6Robot
import time

robot = O6Robot(can_interface="can0")
robot.connect()
robot.enable_torque()

# Move to a pre-reach position
print("Moving to pre-reach...")
robot.move_joints(
    positions=[0.0, -0.5, 1.0, 0.0, 0.5, 0.0],  # radians
    speed_fraction=0.2
)
time.sleep(3)

# Reach forward (extend joint 2 and 3)
print("Reaching forward...")
robot.move_joints(
    positions=[0.0, -1.0, 1.5, 0.0, 0.5, 0.0],
    speed_fraction=0.2
)
time.sleep(3)

# Return to home
print("Returning to home...")
robot.move_to_home(speed_fraction=0.2)
time.sleep(3)

robot.disable_torque()
robot.disconnect()
print("Trajectory complete")
⚠ Keep hands clear of the arm workspace during scripted motion. Run with speed_fraction=0.15 or lower on your first attempt and watch the arm closely for unexpected motion before increasing speed.

Step 5: LeRobot SDK Integration

Confirm LeRobot can communicate with the O6 using the provided robot config:

python3 -c "
from lerobot.common.robot_devices.robots.factory import make_robot
robot = make_robot('linker_bot_o6')
robot.connect()
obs = robot.capture_observation()
print('Observation keys:', list(obs.keys()))
robot.disconnect()
"

You should see keys like observation.state (joint positions and velocities) and observation.images.top if a camera is configured. If you see a RobotDeviceNotConnectedError, verify the CAN interface and that the arm is powered and responding.

Unit 2 Complete When...

The ROS2 controller is launching without errors and /joint_states is publishing. The scripted trajectory runs all 6 joints through two waypoints and returns to home. LeRobot's capture_observation() returns a valid observation dict. You understand the joint limits table and are not commanding positions outside those ranges.