File size: 3,322 Bytes
130ae50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
/**
 * Helper to find the USB port associated with your MotorsBus.
 *
 * Example:
 * ```
 * npx lerobot find-port
 * ```
 */

import { SerialPort } from "serialport";
import { createInterface } from "readline";
import { platform } from "os";
import { readdir } from "fs/promises";
import { join } from "path";

/**
 * Find all available serial ports on the system
 * Mirrors Python's find_available_ports() function
 */
async function findAvailablePorts(): Promise<string[]> {
  if (platform() === "win32") {
    // List COM ports using serialport library (equivalent to pyserial)
    const ports = await SerialPort.list();
    return ports.map((port) => port.path);
  } else {
    // List /dev/tty* ports for Unix-based systems (Linux/macOS)
    try {
      const devFiles = await readdir("/dev");
      const ttyPorts = devFiles
        .filter((file) => file.startsWith("tty"))
        .map((file) => join("/dev", file));
      return ttyPorts;
    } catch (error) {
      // Fallback to serialport library if /dev reading fails
      const ports = await SerialPort.list();
      return ports.map((port) => port.path);
    }
  }
}

/**
 * Create readline interface for user input
 * Equivalent to Python's input() function
 */
function createReadlineInterface() {
  return createInterface({
    input: process.stdin,
    output: process.stdout,
  });
}

/**
 * Prompt user for input and wait for response
 * Equivalent to Python's input() function
 */
function waitForInput(prompt: string = ""): Promise<string> {
  const rl = createReadlineInterface();
  return new Promise((resolve) => {
    if (prompt) {
      process.stdout.write(prompt);
    }
    rl.on("line", (answer) => {
      rl.close();
      resolve(answer);
    });
  });
}

/**
 * Sleep for specified milliseconds
 * Equivalent to Python's time.sleep()
 */
function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

/**
 * Main find port function - direct port of Python find_port()
 * Maintains identical UX and messaging
 */
export async function findPort(): Promise<void> {
  console.log("Finding all available ports for the MotorsBus.");

  const portsBefore = await findAvailablePorts();
  console.log("Ports before disconnecting:", portsBefore);

  console.log(
    "Remove the USB cable from your MotorsBus and press Enter when done."
  );
  await waitForInput();

  // Allow some time for port to be released (equivalent to Python's time.sleep(0.5))
  await sleep(500);

  const portsAfter = await findAvailablePorts();
  const portsDiff = portsBefore.filter((port) => !portsAfter.includes(port));

  if (portsDiff.length === 1) {
    const port = portsDiff[0];
    console.log(`The port of this MotorsBus is '${port}'`);
    console.log("Reconnect the USB cable.");
  } else if (portsDiff.length === 0) {
    throw new Error(
      `Could not detect the port. No difference was found (${JSON.stringify(
        portsDiff
      )}).`
    );
  } else {
    throw new Error(
      `Could not detect the port. More than one port was found (${JSON.stringify(
        portsDiff
      )}).`
    );
  }
}

/**
 * CLI entry point when called directly
 */
if (import.meta.url === `file://${process.argv[1]}`) {
  findPort().catch((error) => {
    console.error(error.message);
    process.exit(1);
  });
}