Spaces:
Running
Running
| /** | |
| * Teleoperation Demo | |
| * | |
| * Demonstrates different ways to control robot motors | |
| */ | |
| import { findPort, connectPort, teleoperate } from "@lerobot/node"; | |
| import type { RobotConnection, DiscoveredPort } from "@lerobot/node"; | |
| 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 demoTeleoperate() { | |
| console.log("๐ฎ Teleoperation Demo"); | |
| console.log("=====================\n"); | |
| try { | |
| // Step 1: Find available robot ports | |
| console.log("๐ก Looking for connected robots..."); | |
| const findProcess = await findPort(); | |
| const discoveredPorts = await findProcess.result; | |
| if (discoveredPorts.length === 0) { | |
| throw new Error("No robots found. Please connect your robot first."); | |
| } | |
| console.log(`โ Found robot on ${discoveredPorts[0].path}`); | |
| // Step 2: Connect to robot | |
| console.log("๐ Connecting to robot..."); | |
| const robot = await connectPort( | |
| discoveredPorts[0].path, | |
| "so100_follower", | |
| "teleop_demo" | |
| ); | |
| console.log(`โ Connected: ${robot.robotType} (ID: ${robot.robotId})\n`); | |
| // Step 3: Choose teleoperation mode | |
| console.log("๐ฏ Choose teleoperation mode:"); | |
| console.log("1. Keyboard Control (interactive)"); | |
| console.log("2. Direct Control (programmatic)"); | |
| const mode = await askUser("Enter choice (1 or 2): "); | |
| if (mode === "1") { | |
| // Keyboard teleoperation demo | |
| await demoKeyboardControl(robot); | |
| } else if (mode === "2") { | |
| // Direct control demo | |
| await demoDirectControl(robot); | |
| } else { | |
| console.log("Invalid choice. Defaulting to keyboard control..."); | |
| await demoKeyboardControl(robot); | |
| } | |
| } catch (error) { | |
| console.error("\nโ Teleoperation failed:", error.message); | |
| console.log("\n๐ง Troubleshooting:"); | |
| console.log("- Ensure robot is calibrated first"); | |
| console.log("- Check that robot is connected and responsive"); | |
| console.log("- Verify calibration data exists"); | |
| console.log("- Try smaller step sizes if movements are too large"); | |
| process.exit(1); | |
| } | |
| } | |
| async function demoKeyboardControl(robot: RobotConnection) { | |
| console.log("\nโจ๏ธ Keyboard Control Demo"); | |
| console.log("========================="); | |
| console.log("\n๐ฎ Robot Controls:"); | |
| console.log(" Arrow Keys: Shoulder pan/lift"); | |
| console.log(" W/S: Elbow flex/extend"); | |
| console.log(" A/D: Wrist down/up"); | |
| console.log(" Q/E: Wrist roll left/right"); | |
| console.log(" O/C: Gripper open/close"); | |
| console.log(" ESC: Emergency stop"); | |
| console.log(" Ctrl+C: Exit demo\n"); | |
| const teleop = await teleoperate({ | |
| robot, | |
| teleop: { | |
| type: "keyboard", | |
| // Using optimized defaults for smooth control | |
| }, | |
| onStateUpdate: (state) => { | |
| if (state.isActive) { | |
| // Show live motor positions | |
| const motorInfo = state.motorConfigs | |
| .map((motor) => { | |
| const pos = Math.round(motor.currentPosition); | |
| const percent = ( | |
| ((pos - motor.minPosition) / | |
| (motor.maxPosition - motor.minPosition)) * | |
| 100 | |
| ).toFixed(0); | |
| return `${motor.name}:${pos}(${percent}%)`; | |
| }) | |
| .join(" "); | |
| process.stdout.write(`\r๐ค ${motorInfo}`); | |
| } | |
| }, | |
| }); | |
| // Start keyboard control | |
| teleop.start(); | |
| console.log("โ Keyboard control active!"); | |
| console.log("๐ก Move robot with keyboard, press Ctrl+C to exit"); | |
| // Handle graceful shutdown | |
| process.on("SIGINT", async () => { | |
| console.log("\n๐ Stopping keyboard control..."); | |
| teleop.stop(); | |
| await teleop.disconnect(); | |
| console.log("โ Keyboard control demo completed!"); | |
| process.exit(0); | |
| }); | |
| // Keep demo running | |
| await new Promise(() => {}); // Keep alive | |
| } | |
| async function demoDirectControl(robot: RobotConnection) { | |
| console.log("\n๐ฏ Direct Control Demo"); | |
| console.log("======================"); | |
| console.log("This demonstrates programmatic robot control\n"); | |
| const teleop = await teleoperate({ | |
| robot, | |
| teleop: { type: "direct" }, | |
| onStateUpdate: (state) => { | |
| const motorInfo = state.motorConfigs | |
| .map((motor) => `${motor.name}:${Math.round(motor.currentPosition)}`) | |
| .join(" "); | |
| console.log(`๐ค ${motorInfo}`); | |
| }, | |
| }); | |
| teleop.start(); | |
| // Get direct control interface | |
| const directController = teleop.teleoperator as any; | |
| console.log("๐ฌ Running automated movement sequence...\n"); | |
| try { | |
| // Demo sequence: move different motors | |
| const movements = [ | |
| { | |
| motor: "shoulder_pan", | |
| position: 2048, | |
| description: "Center shoulder pan", | |
| }, | |
| { motor: "shoulder_lift", position: 1500, description: "Lift shoulder" }, | |
| { motor: "elbow_flex", position: 2500, description: "Flex elbow" }, | |
| { motor: "wrist_flex", position: 2000, description: "Adjust wrist" }, | |
| { motor: "wrist_roll", position: 2048, description: "Center wrist roll" }, | |
| { motor: "gripper", position: 1800, description: "Adjust gripper" }, | |
| ]; | |
| for (const movement of movements) { | |
| console.log(`๐ฏ ${movement.description}...`); | |
| await directController.moveMotor(movement.motor, movement.position); | |
| await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second | |
| } | |
| console.log("\n๐ Movement sequence completed!"); | |
| // Demo multi-motor movement | |
| console.log("\n๐ญ Demonstrating simultaneous multi-motor movement..."); | |
| const results = await directController.moveMotors({ | |
| shoulder_pan: 2048, | |
| shoulder_lift: 2048, | |
| elbow_flex: 2048, | |
| wrist_flex: 2048, | |
| }); | |
| console.log("๐ Movement results:"); | |
| Object.entries(results).forEach(([motor, success]) => { | |
| console.log(` ${motor}: ${success ? "โ " : "โ"}`); | |
| }); | |
| // Show current positions | |
| const positions = directController.getCurrentPositions(); | |
| console.log("\n๐ Final positions:"); | |
| Object.entries(positions).forEach(([motor, position]) => { | |
| console.log(` ${motor}: ${Math.round(position as number)}`); | |
| }); | |
| } finally { | |
| console.log("\n๐ Stopping direct control..."); | |
| teleop.stop(); | |
| await teleop.disconnect(); | |
| console.log("โ Direct control demo completed!"); | |
| } | |
| } | |
| demoTeleoperate(); | |