Spaces:
Running
Running
| /** | |
| * Node.js Quick Start Example - Complete Workflow | |
| * | |
| * This example demonstrates the full robot control workflow: | |
| * 1. Find and connect to robot hardware | |
| * 2. Release motors for manual positioning | |
| * 3. Calibrate motor ranges and homing positions | |
| * 4. Control robot with keyboard teleoperation | |
| */ | |
| import { | |
| findPort, | |
| connectPort, | |
| releaseMotors, | |
| calibrate, | |
| teleoperate, | |
| } from "@lerobot/node"; | |
| import type { RobotConnection, DiscoveredPort } from "@lerobot/node"; | |
| // Utility for user confirmation | |
| import { createInterface } from "readline"; | |
| function askUser(question: string): Promise<string> { | |
| const rl = createInterface({ | |
| input: process.stdin, | |
| output: process.stdout, | |
| }); | |
| return new Promise((resolve) => { | |
| rl.question(question, (answer) => { | |
| rl.close(); | |
| resolve(answer.trim()); | |
| }); | |
| }); | |
| } | |
| async function quickStartDemo() { | |
| console.log("๐ค LeRobot.js Node.js Quick Start Demo"); | |
| console.log("=====================================\n"); | |
| try { | |
| // Step 1: Find available robot ports | |
| console.log("๐ก Step 1: Looking for connected robots..."); | |
| const findProcess = await findPort(); | |
| const discoveredPorts = await findProcess.result; | |
| if (discoveredPorts.length === 0) { | |
| throw new Error("No robots found. Please check your connections."); | |
| } | |
| console.log(`โ Found robot on ${discoveredPorts[0].path}`); | |
| // Step 2: Connect to the first robot found | |
| console.log("๐ Step 2: Connecting to robot..."); | |
| const robot = await connectPort( | |
| discoveredPorts[0].path, | |
| "so100_follower", | |
| "demo_robot_arm" | |
| ); | |
| console.log(`โ Connected: ${robot.robotType} (ID: ${robot.robotId})\n`); | |
| // Step 3: Release motors for calibration setup | |
| const shouldRelease = await askUser( | |
| "๐ Release motors for manual positioning? (y/n): " | |
| ); | |
| if (shouldRelease.toLowerCase() === "y") { | |
| console.log("๐ Step 2: Releasing motors..."); | |
| await releaseMotors(robot); | |
| console.log("โ Motors released - you can now move the robot by hand\n"); | |
| await askUser( | |
| "Move robot to desired starting position, then press Enter to continue..." | |
| ); | |
| } | |
| // Step 4: Calibrate the robot | |
| const shouldCalibrate = await askUser("๐ฏ Run calibration? (y/n): "); | |
| if (shouldCalibrate.toLowerCase() === "y") { | |
| console.log("\n๐ฏ Step 3: Starting calibration..."); | |
| console.log( | |
| "This will record the motor ranges and set homing positions.\n" | |
| ); | |
| const calibrationProcess = await calibrate({ | |
| robot: robot as RobotConnection, | |
| onProgress: (message) => { | |
| console.log(` ๐ ${message}`); | |
| }, | |
| onLiveUpdate: (data) => { | |
| // Show live motor positions during range recording | |
| const positions = Object.entries(data) | |
| .map( | |
| ([name, info]) => | |
| `${name}:${info.current}(${info.min}-${info.max})` | |
| ) | |
| .join(" "); | |
| process.stdout.write(`\r ๐ Live: ${positions}`); | |
| }, | |
| }); | |
| const calibrationData = await calibrationProcess.result; | |
| console.log("\nโ Calibration completed!"); | |
| // Show calibration summary | |
| console.log("\n๐ Calibration Results:"); | |
| Object.entries(calibrationData).forEach(([motorName, config]) => { | |
| console.log( | |
| ` ${motorName}: range ${config.range_min}-${config.range_max}, offset ${config.homing_offset}` | |
| ); | |
| }); | |
| } | |
| // Step 5: Teleoperation | |
| const shouldTeleoperate = await askUser( | |
| "\n๐ฎ Start keyboard teleoperation? (y/n): " | |
| ); | |
| if (shouldTeleoperate.toLowerCase() === "y") { | |
| console.log("\n๐ฎ Step 4: Starting teleoperation..."); | |
| console.log("Use keyboard to control the robot:\n"); | |
| const teleop = await teleoperate({ | |
| robot: robot as RobotConnection, | |
| teleop: { type: "keyboard" }, | |
| onStateUpdate: (state) => { | |
| if (state.isActive) { | |
| const motorInfo = state.motorConfigs | |
| .map( | |
| (motor) => `${motor.name}:${Math.round(motor.currentPosition)}` | |
| ) | |
| .join(" "); | |
| process.stdout.write(`\r๐ค Motors: ${motorInfo}`); | |
| } | |
| }, | |
| }); | |
| // Start keyboard control | |
| teleop.start(); | |
| console.log("โ Teleoperation active!"); | |
| console.log("๐ฏ Use arrow keys, WASD, Q/E, O/C to control"); | |
| console.log("โ ๏ธ Press ESC for emergency stop, Ctrl+C to exit\n"); | |
| // Handle graceful shutdown | |
| process.on("SIGINT", async () => { | |
| console.log("\n๐ Shutting down teleoperation..."); | |
| teleop.stop(); | |
| await teleop.disconnect(); | |
| console.log("โ Demo completed successfully!"); | |
| process.exit(0); | |
| }); | |
| // Keep the demo running | |
| console.log("Demo is running... Press Ctrl+C to stop"); | |
| await new Promise(() => {}); // Keep alive | |
| } | |
| console.log("\n๐ Quick Start Demo completed!"); | |
| console.log( | |
| "You can now integrate @lerobot/node into your own applications." | |
| ); | |
| } catch (error) { | |
| console.error("\nโ Demo failed:", error.message); | |
| console.log("\n๐ง Troubleshooting tips:"); | |
| console.log("- Check robot is connected and powered on"); | |
| console.log("- Verify correct serial port permissions"); | |
| console.log("- Try running 'npx lerobot find-port' to test connection"); | |
| process.exit(1); | |
| } | |
| } | |
| // Handle uncaught errors gracefully | |
| process.on("uncaughtException", (error) => { | |
| console.error("\n๐ฅ Unexpected error:", error.message); | |
| process.exit(1); | |
| }); | |
| process.on("unhandledRejection", (error) => { | |
| console.error("\n๐ฅ Unhandled promise rejection:", error); | |
| process.exit(1); | |
| }); | |
| // Run the demo | |
| quickStartDemo(); | |