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