Spaces:
Running
Running
| # User Story 002: Robot Calibration | |
| ## Story | |
| **As a** robotics developer setting up SO-100 robot arms | |
| **I want** to calibrate my robot arms to establish proper motor positions and limits | |
| **So that** my robot operates safely and accurately within its intended range of motion | |
| ## Background | |
| Robot calibration is a critical setup step that establishes the zero positions, movement limits, and safety parameters for robotic arms. The Python lerobot provides a `calibrate.py` script that: | |
| 1. Connects to the specified robot (follower) or teleoperator (leader) | |
| 2. Runs the calibration procedure to set motor positions and limits | |
| 3. Saves calibration data for future robot operations | |
| 4. Ensures safe operation by establishing proper movement boundaries | |
| The calibration process uses the USB ports identified by the `find_port` functionality from User Story 001, and supports both robot arms (followers) and teleoperators (leaders). | |
| ## Acceptance Criteria | |
| ### Core Functionality | |
| - [x] **Robot Connection**: Connect to robot using discovered USB port from find_port | |
| - [x] **Robot Types**: Support SO-100 follower robot type | |
| - [x] **Teleoperator Support**: Support SO-100 leader teleoperator | |
| - [x] **Calibration Process**: Run device-specific calibration procedures | |
| - [x] **Configuration Management**: Handle robot-specific configuration parameters | |
| - [x] **Cross-Platform**: Work on Windows, macOS, and Linux | |
| - [x] **CLI Interface**: Provide `npx lerobot calibrate` command identical to Python version | |
| ### User Experience | |
| - [x] **Clear Feedback**: Show calibration progress and status messages | |
| - [x] **Error Handling**: Handle connection failures, calibration errors gracefully | |
| - [x] **Safety Validation**: Confirm successful calibration before completion | |
| - [x] **Results Display**: Show calibration completion status and saved configuration | |
| ### Technical Requirements | |
| - [x] **Dual Platform**: Support both Node.js (CLI) and Web (browser) platforms | |
| - [x] **Node.js Implementation**: Use serialport package for Node.js serial communication | |
| - [x] **Web Implementation**: Use Web Serial API for browser serial communication | |
| - [x] **TypeScript**: Fully typed implementation following project conventions | |
| - [x] **CLI Tool**: Executable via `npx lerobot calibrate` (matching Python version) | |
| - [x] **Configuration Storage**: Save/load calibration data to appropriate locations per platform | |
| - [x] **Platform Abstraction**: Abstract robot/teleoperator interfaces work on both platforms | |
| ## Expected User Flow | |
| ### Node.js CLI Calibration (Traditional) | |
| ```bash | |
| $ npx lerobot calibrate --robot.type=so100_follower --robot.port=COM4 --robot.id=my_follower_arm | |
| Calibrating robot... | |
| Robot type: so100_follower | |
| Port: COM4 | |
| ID: my_follower_arm | |
| Connecting to robot... | |
| Connected successfully. | |
| Starting calibration procedure... | |
| Calibration completed successfully. | |
| Configuration saved to: ~/.cache/huggingface/lerobot/calibration/robots/so100_follower/my_follower_arm.json | |
| Disconnecting from robot... | |
| ``` | |
| ### Web Browser Calibration (Interactive) | |
| ```typescript | |
| // In a web application | |
| import { calibrate } from "lerobot/web/calibrate"; | |
| // Must be triggered by user interaction (button click) | |
| await calibrate({ | |
| robot: { | |
| type: "so100_follower", | |
| id: "my_follower_arm", | |
| // port will be selected by user via browser dialog | |
| }, | |
| }); | |
| // Browser shows port selection dialog | |
| // User selects robot from available serial ports | |
| // Calibration proceeds similar to CLI version | |
| // Configuration saved to browser storage or downloaded as file | |
| ``` | |
| ### Teleoperator Calibration | |
| ```bash | |
| $ npx lerobot calibrate --teleop.type=so100_leader --teleop.port=COM3 --teleop.id=my_leader_arm | |
| Calibrating teleoperator... | |
| Teleoperator type: so100_leader | |
| Port: COM3 | |
| ID: my_leader_arm | |
| Connecting to teleoperator... | |
| Connected successfully. | |
| Starting calibration procedure... | |
| Please follow the on-screen instructions to move the teleoperator through its range of motion... | |
| Calibration completed successfully. | |
| Configuration saved to: ~/.cache/huggingface/lerobot/calibration/teleoperators/so100_leader/my_leader_arm.json | |
| Disconnecting from teleoperator... | |
| ``` | |
| ### Error Handling | |
| ```bash | |
| $ npx lerobot calibrate --robot.type=so100_follower --robot.port=COM99 | |
| Error: Could not connect to robot on port COM99 | |
| Please verify: | |
| 1. The robot is connected to the specified port | |
| 2. No other application is using the port | |
| 3. You have permission to access the port | |
| Use 'npx lerobot find-port' to discover available ports. | |
| ``` | |
| ## Implementation Details | |
| ### File Structure | |
| ``` | |
| src/lerobot/ | |
| βββ node/ | |
| β βββ calibrate.ts # Node.js calibration logic (uses serialport) | |
| β βββ robots/ | |
| β β βββ config.ts # Shared robot configuration types | |
| β β βββ robot.ts # Node.js Robot base class | |
| β β βββ so100_follower.ts # Node.js SO-100 follower implementation | |
| β βββ teleoperators/ | |
| β βββ config.ts # Shared teleoperator configuration types | |
| β βββ teleoperator.ts # Node.js Teleoperator base class | |
| β βββ so100_leader.ts # Node.js SO-100 leader implementation | |
| βββ web/ | |
| βββ calibrate.ts # Web calibration logic (uses Web Serial API) | |
| βββ robots/ | |
| β βββ robot.ts # Web Robot base class | |
| β βββ so100_follower.ts # Web SO-100 follower implementation | |
| βββ teleoperators/ | |
| βββ teleoperator.ts # Web Teleoperator base class | |
| βββ so100_leader.ts # Web SO-100 leader implementation | |
| src/cli/ | |
| βββ index.ts # CLI entry point (Node.js only) | |
| ``` | |
| ### Key Dependencies | |
| #### Node.js Platform | |
| - **serialport**: For Node.js serial communication | |
| - **commander**: For CLI argument parsing (matching Python argparse style) | |
| - **fs/promises**: For configuration file management | |
| - **os**: Node.js built-in for cross-platform home directory detection | |
| - **path**: Node.js built-in for path manipulation | |
| #### Web Platform | |
| - **Web Serial API**: Built-in browser API (no external dependencies) | |
| - **File System Access API**: For configuration file management (when available) | |
| - **Streams API**: Built-in browser streams for data handling | |
| ### Platform API Differences | |
| The Web Serial API and Node.js serialport APIs are **completely different** and require separate implementations: | |
| #### Node.js Serial API (Traditional) | |
| ```typescript | |
| // Node.js - Event-based, programmatic access | |
| import { SerialPort } from "serialport"; | |
| // List ports programmatically | |
| const ports = await SerialPort.list(); | |
| // Create port instance | |
| const port = new SerialPort({ | |
| path: "COM4", | |
| baudRate: 1000000, // Correct baudRate for Feetech motors (SO-100) | |
| }); | |
| // Event-based data handling | |
| port.on("data", (data) => { | |
| console.log("Received:", data.toString()); | |
| }); | |
| // Direct write | |
| port.write("command\r\n"); | |
| ``` | |
| #### Web Serial API (Modern) | |
| ```typescript | |
| // Web - Promise-based, user permission required | |
| // Request port (requires user interaction) | |
| const port = await navigator.serial.requestPort(); | |
| // Open with options | |
| await port.open({ baudRate: 1000000 }); // Correct baudRate for Feetech motors (SO-100) | |
| // Stream-based data handling | |
| const reader = port.readable.getReader(); | |
| while (true) { | |
| const { value, done } = await reader.read(); | |
| if (done) break; | |
| console.log("Received:", new TextDecoder().decode(value)); | |
| } | |
| // Stream-based write | |
| const writer = port.writable.getWriter(); | |
| await writer.write(new TextEncoder().encode("command\r\n")); | |
| writer.releaseLock(); | |
| ``` | |
| ### Core Functions to Implement | |
| #### Shared Interface | |
| ```typescript | |
| // calibrate.ts (matching Python naming and structure) | |
| interface CalibrateConfig { | |
| robot?: RobotConfig; | |
| teleop?: TeleoperatorConfig; | |
| } | |
| async function calibrate(config: CalibrateConfig): Promise<void>; | |
| // Robot/Teleoperator base classes (platform-agnostic) | |
| abstract class Robot { | |
| abstract connect(calibrate?: boolean): Promise<void>; | |
| abstract calibrate(): Promise<void>; | |
| abstract disconnect(): Promise<void>; | |
| } | |
| abstract class Teleoperator { | |
| abstract connect(calibrate?: boolean): Promise<void>; | |
| abstract calibrate(): Promise<void>; | |
| abstract disconnect(): Promise<void>; | |
| } | |
| ``` | |
| #### Platform-Specific Implementations | |
| ```typescript | |
| // Node.js implementation | |
| class NodeRobot extends Robot { | |
| private port: SerialPort; | |
| // Uses serialport package | |
| } | |
| // Web implementation | |
| class WebRobot extends Robot { | |
| private port: SerialPort; // Web Serial API SerialPort | |
| // Uses navigator.serial API | |
| } | |
| ``` | |
| ### Configuration Types | |
| ```typescript | |
| interface RobotConfig { | |
| type: "so100_follower"; | |
| port: string; | |
| id?: string; | |
| calibration_dir?: string; | |
| // SO-100 specific options | |
| disable_torque_on_disconnect?: boolean; | |
| max_relative_target?: number | null; | |
| use_degrees?: boolean; | |
| } | |
| interface TeleoperatorConfig { | |
| type: "so100_leader"; | |
| port: string; | |
| id?: string; | |
| calibration_dir?: string; | |
| // SO-100 leader specific options | |
| } | |
| ``` | |
| ### Technical Considerations | |
| #### Configuration Management | |
| - **Storage Location**: `{HF_HOME}/lerobot/calibration/robots/{robot_name}/{robot_id}.json` (matching Python version) | |
| - **HF_HOME Discovery**: Use Node.js equivalent of `huggingface_hub.constants.HF_HOME` | |
| - Default: `~/.cache/huggingface` (Linux/macOS) or `%USERPROFILE%\.cache\huggingface` (Windows) | |
| - Environment variable: `HF_HOME` can override the default | |
| - Environment variable: `HF_LEROBOT_CALIBRATION` can override the calibration directory | |
| - **File Format**: JSON for cross-platform compatibility | |
| - **Directory Structure**: `calibration/robots/{robot_name}/` where robot_name matches the robot type | |
| #### Safety Features | |
| - **Movement Limits**: Enforce maximum relative target constraints | |
| - **Torque Management**: Handle torque disable on disconnect | |
| - **Error Recovery**: Graceful handling of calibration failures | |
| #### Device Communication | |
| - **Serial Protocol**: Match Python implementation's communication protocol | |
| - **Timeout Handling**: Appropriate timeouts for device responses | |
| - **Connection Validation**: Verify device is responding before calibration | |
| #### Platform-Specific Challenges | |
| **Node.js Platform:** | |
| - **Port Access**: Direct system-level port access | |
| - **Port Discovery**: Programmatic port listing via `SerialPort.list()` | |
| - **Event Handling**: Traditional callback/event-based patterns | |
| - **Error Handling**: System-level error codes and messages | |
| **Web Platform:** | |
| - **User Permission**: Requires user interaction for port selection | |
| - **Limited Discovery**: Cannot programmatically list ports | |
| - **Stream-Based**: Modern Promise/Stream-based patterns | |
| - **Browser Security**: Limited to what browser security model allows | |
| - **Configuration Storage**: Use browser storage APIs (localStorage/IndexedDB) or File System Access API | |
| #### CLI Argument Parsing | |
| - **Exact Matching**: Command line arguments must match Python version exactly | |
| - **Validation**: Input validation for robot types, ports, and IDs | |
| - **Help Text**: Identical help text and usage examples as Python version | |
| #### Hugging Face Directory Discovery (Node.js) | |
| ```typescript | |
| // Equivalent to Python's huggingface_hub.constants.HF_HOME | |
| function getHfHome(): string { | |
| if (process.env.HF_HOME) { | |
| return process.env.HF_HOME; | |
| } | |
| const homeDir = os.homedir(); | |
| if (process.platform === "win32") { | |
| return path.join(homeDir, ".cache", "huggingface"); | |
| } else { | |
| return path.join(homeDir, ".cache", "huggingface"); | |
| } | |
| } | |
| // Equivalent to Python's HF_LEROBOT_CALIBRATION | |
| function getCalibrationDir(): string { | |
| if (process.env.HF_LEROBOT_CALIBRATION) { | |
| return process.env.HF_LEROBOT_CALIBRATION; | |
| } | |
| return path.join(getHfHome(), "lerobot", "calibration"); | |
| } | |
| ``` | |
| ## Definition of Done | |
| - [x] **Functional**: Successfully calibrates SO-100 robots and teleoperators on both platforms | |
| - [x] **CLI Compatible**: `npx lerobot calibrate` matches Python `python -m lerobot.calibrate` | |
| - [x] **Web Compatible**: Browser-based calibration with Web Serial API | |
| - [x] **Cross-Platform**: Node.js works on Windows, macOS, and Linux; Web works in Chromium browsers | |
| - [x] **Tested**: Unit tests for core logic, integration tests with mock devices for both platforms | |
| - [x] **Error Handling**: Platform-appropriate error handling and user-friendly messages | |
| - [x] **Configuration**: Platform-appropriate configuration storage (filesystem vs browser storage) | |
| - [x] **Type Safe**: Full TypeScript coverage with strict mode for both implementations | |
| - [x] **Follows Conventions**: Matches Python lerobot UX/API exactly (CLI), provides intuitive web UX | |
| - [x] **Integration**: Node.js works with ports discovered by User Story 001; Web uses browser port selection | |