Spaces:
Running
Running
File size: 5,834 Bytes
bdc1ac8 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
/**
* 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();
|