Spaces:
Running
Running
@lerobot/web
Browser-native robotics control using WebSerial and WebUSB APIs.
Features
- Direct Hardware Control: STS3215 motor communication via WebSerial API
- Device Persistence: WebUSB API for automatic robot reconnection
- Real-time Teleoperation: Keyboard control with live motor feedback
- Motor Calibration: Automated homing offset and range recording
- Cross-browser Support: Chrome/Edge 89+ with HTTPS or localhost
- TypeScript Native: Full type safety and IntelliSense support
Installation
# pnpm (recommended)
pnpm add @lerobot/web
# npm
npm install @lerobot/web
# yarn
yarn add @lerobot/web
Quick Start
import { findPort, releaseMotors, calibrate, teleoperate } from "@lerobot/web";
// 1. Find and connect to hardware
const findProcess = await findPort();
const robots = await findProcess.result;
const robot = robots[0];
// 2. Release motors for manual positioning
await releaseMotors(robot);
console.log("🔓 Motors released - you can move the robot by hand");
// 3. Calibrate motors
const calibrationProcess = await calibrate(robot, {
onProgress: (message) => console.log(message),
onLiveUpdate: (data) => console.log("Live positions:", data),
});
// Move robot through its full range of motion...
// When done, stop calibration to proceed
setTimeout(() => {
console.log("⏱️ Stopping calibration...");
calibrationProcess.stop();
}, 10000); // Stop after 10 seconds, or call stop() when user is ready
const calibrationData = await calibrationProcess.result;
// 4. Start teleoperation
const teleop = await teleoperate(robot, { calibrationData });
teleop.start();
// Stop teleoperation when done
setTimeout(() => {
console.log("⏹️ Stopping teleoperation...");
teleop.stop();
}, 30000); // Stop after 30 seconds, or call stop() when needed
Core API
findPort(options?): Promise<FindPortProcess>
Discovers and connects to robotics hardware using WebSerial API. Two modes: interactive (shows port dialog) and auto-connect (reconnects to known robots).
Interactive Mode (Default)
First-time usage or discovering new robots. Shows native browser port selection dialog.
// User selects robot via browser dialog
const findProcess = await findPort();
const robots = await findProcess.result; // RobotConnection[]
const robot = robots[0]; // User-selected robot
// Configure and save robot for future auto-connect
robot.robotType = "so100_follower";
robot.robotId = "my_robot_arm";
// Save to localStorage (or your storage system)
localStorage.setItem(
`robot-${robot.serialNumber}`,
JSON.stringify({
robotType: robot.robotType,
robotId: robot.robotId,
serialNumber: robot.serialNumber,
})
);
Auto-Connect Mode
Automatically reconnects to previously configured robots without showing dialogs.
// Build robotConfigs from saved data
const robotConfigs = [];
// Option 1: Load from localStorage (typical web app pattern)
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key?.startsWith("robot-")) {
const saved = JSON.parse(localStorage.getItem(key)!);
robotConfigs.push({
robotType: saved.robotType,
robotId: saved.robotId,
serialNumber: saved.serialNumber,
});
}
}
// Option 2: Create manually if you know your robots
const robotConfigs = [
{ robotType: "so100_follower", robotId: "left_arm", serialNumber: "USB123" },
{ robotType: "so100_leader", robotId: "right_arm", serialNumber: "USB456" },
];
// Auto-connect to all known robots
const findProcess = await findPort({
robotConfigs,
onMessage: (msg) => console.log(msg),
});
const robots = await findProcess.result;
const connectedRobots = robots.filter((r) => r.isConnected);
console.log(
`Connected to ${connectedRobots.length}/${robotConfigs.length} robots`
);
RobotConfig Structure
interface RobotConfig {
robotType: "so100_follower" | "so100_leader";
robotId: string; // Your custom identifier (e.g., "left_arm")
serialNumber: string; // Device serial number (from previous findPort)
}
Options
robotConfigs?: RobotConfig[]
- Auto-connect to these known robotsonMessage?: (message: string) => void
- Progress messages callback
Returns: FindPortProcess
result: Promise<RobotConnection[]>
- Array of robot connectionsstop(): void
- Cancel discovery process
calibrate(robot, options?): Promise<CalibrationProcess>
Calibrates motor homing offsets and records range of motion.
const calibrationProcess = await calibrate(robot, {
onProgress: (message) => {
console.log(message); // "⚙️ Setting motor homing offsets"
},
onLiveUpdate: (data) => {
// Real-time motor positions during range recording
Object.entries(data).forEach(([motor, info]) => {
console.log(`${motor}: ${info.current} (range: ${info.range})`);
});
},
});
// Move robot through full range of motion...
// When finished recording ranges, stop the calibration
console.log("Move robot through its range, then stopping in 10 seconds...");
setTimeout(() => {
calibrationProcess.stop(); // Stop range recording
}, 10000);
const calibrationData = await calibrationProcess.result;
// Save calibration data to localStorage or file
Parameters
robot: RobotConnection
- Connected robot fromfindPort()
options?: CalibrationOptions
onProgress?: (message: string) => void
- Progress messagesonLiveUpdate?: (data: LiveCalibrationData) => void
- Real-time position updates
Returns: CalibrationProcess
result: Promise<WebCalibrationResults>
- Calibration data (Python-compatible format)stop(): void
- Stop calibration process
Calibration Data Format
{
"shoulder_pan": {
"id": 1,
"drive_mode": 0,
"homing_offset": 47,
"range_min": 985,
"range_max": 3085
},
// ... other motors
}
teleoperate(robot, options?): Promise<TeleoperationProcess>
Enables real-time robot control with keyboard input and programmatic movement.
const teleop = await teleoperate(robot, {
calibrationData: savedCalibrationData, // From calibrate()
onStateUpdate: (state) => {
console.log(`Active: ${state.isActive}`);
console.log(`Motors:`, state.motorConfigs);
},
});
// Start keyboard control
teleop.start();
// Programmatic control
await teleop.moveMotor("shoulder_pan", 2048);
await teleop.setMotorPositions({
shoulder_pan: 2048,
elbow_flex: 1500,
});
// Stop teleoperation when finished
setTimeout(() => {
console.log("Stopping teleoperation...");
teleop.stop();
}, 30000); // Or call teleop.stop() when user is done
Parameters
robot: RobotConnection
- Connected robot fromfindPort()
options?: TeleoperationOptions
calibrationData?: { [motorName: string]: any }
- Calibration data fromcalibrate()
onStateUpdate?: (state: TeleoperationState) => void
- State change callback
Returns: TeleoperationProcess
start(): void
- Begin keyboard teleoperationstop(): void
- Stop teleoperation and clear key statesupdateKeyState(key: string, pressed: boolean): void
- Manual key state controlgetState(): TeleoperationState
- Current state and motor positionsmoveMotor(motorName: string, position: number): Promise<boolean>
- Move single motorsetMotorPositions(positions: { [motorName: string]: number }): Promise<boolean>
- Move multiple motorsdisconnect(): Promise<void>
- Stop and disconnect
Keyboard Controls (SO-100)
Arrow Keys: Shoulder pan/lift
WASD: Elbow flex, wrist flex
Q/E: Wrist roll
O/C: Gripper open/close
Escape: Emergency stop
releaseMotors(robot, motorIds?): Promise<void>
Releases motor torque so robot can be moved freely by hand.
// Release all motors for calibration
await releaseMotors(robot);
// Release specific motors only
await releaseMotors(robot, [1, 2, 3]);
Parameters
robot: RobotConnection
- Connected robotmotorIds?: number[]
- Specific motor IDs (default: all motors for robot type)
Browser Requirements
- chromium 89+ with WebSerial and WebUSB API support
- HTTPS or localhost
- User gesture required for initial port selection
Hardware Support
Currently supports SO-100 follower and leader arms with STS3215 motors. More devices coming soon.