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(); | |