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