File size: 7,341 Bytes
c8b4583
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
540cfa6
c8b4583
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
540cfa6
c8b4583
 
 
 
 
 
 
 
 
 
 
 
 
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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/**
 * lerobot.js Web Interface
 *
 * Browser-based interface for lerobot functionality
 * Provides the same find-port functionality as the CLI but in the browser
 */

import "./web_interface.css";
import { findPortWeb } from "./lerobot/web/find_port.js";

document.querySelector<HTMLDivElement>("#app")!.innerHTML = `
  <div class="lerobot-app">
    <header class="lerobot-header">
      <h1>πŸ€– lerobot.js</h1>
      <p>use your robot in the web & node with an api similar to LeRobot in python</p>
    </header>
    
    <main class="lerobot-main">
      <section class="tool-section">
        <h2>πŸ” Find USB Ports</h2>
        <p>Identify which USB ports your robot arms are connected to</p>
        <div class="button-group">
          <button id="show-ports-btn" class="secondary-btn" style="display: none;">Show Available Ports</button>
          <button id="manage-devices-btn" class="secondary-btn">Manage Devices</button>
          <button id="find-port-btn" class="primary-btn">Find MotorsBus Port</button>
        </div>
        <div id="info-box" class="info-box" style="display: none;">
          <span class="info-icon">πŸ’‘</span>
          <span class="info-text">Use "Manage Devices" to pair additional devices or "Find MotorsBus Port" to start detection.</span>
        </div>
        <div id="port-results" class="results-area"></div>
      </section>
      
      <section class="info-section" id="compatibility-section" style="display: none;">
        <h3>Browser Compatibility Issue</h3>
        <p>Your browser doesn't support the <a href="https://web.dev/serial/" target="_blank">WebSerial API</a>. Please use:</p>
        <ul>
          <li>Chrome/Edge 89+ or Chrome Android 105+</li>
          <li>HTTPS connection (or localhost for development)</li>
        </ul>
        <p>Alternatively, use the <strong>CLI version</strong>: <code>npx lerobot find-port</code></p>
      </section>
    </main>
  </div>
`;

// Set up button functionality
const showPortsBtn =
  document.querySelector<HTMLButtonElement>("#show-ports-btn")!;
const manageDevicesBtn = document.querySelector<HTMLButtonElement>(
  "#manage-devices-btn"
)!;
const findPortBtn =
  document.querySelector<HTMLButtonElement>("#find-port-btn")!;
const resultsArea = document.querySelector<HTMLDivElement>("#port-results")!;
const infoBox = document.querySelector<HTMLDivElement>("#info-box")!;

// Function to display paired devices
async function displayPairedDevices() {
  try {
    // Check WebSerial support
    if (!("serial" in navigator)) {
      resultsArea.innerHTML =
        '<p class="error">WebSerial API not supported. Please use Chrome/Edge 89+ with HTTPS or localhost.</p>';
      infoBox.style.display = "none";
      showPortsBtn.style.display = "inline-block";
      return;
    }

    // Check what ports we already have access to
    const ports = await navigator.serial.getPorts();

    if (ports.length > 0) {
      // We have paired devices, show them
      resultsArea.innerHTML = `<p class="success">Found ${ports.length} paired device(s):</p>`;
      ports.forEach((port, index) => {
        const info = port.getInfo();
        if (info.usbVendorId && info.usbProductId) {
          resultsArea.innerHTML += `<p class="log">Port ${index + 1}: USB:${
            info.usbVendorId
          }:${info.usbProductId}</p>`;
        } else {
          resultsArea.innerHTML += `<p class="log">Port ${
            index + 1
          }: Serial device</p>`;
        }
      });

      // Show the info box with guidance
      infoBox.style.display = "flex";

      // Hide show ports button since we have devices
      showPortsBtn.style.display = "none";
    } else {
      // No devices paired, show helpful message
      resultsArea.innerHTML =
        '<p class="log">No paired devices found. Click "Show Available Ports" to get started.</p>';

      // Hide the info box since we don't have devices
      infoBox.style.display = "none";

      // Show the show ports button since we need it
      showPortsBtn.style.display = "inline-block";
    }
  } catch (error) {
    resultsArea.innerHTML += `<p class="error">Error checking devices: ${
      error instanceof Error ? error.message : error
    }</p>`;
    infoBox.style.display = "none";
    showPortsBtn.style.display = "inline-block";
  }
}

// Check browser compatibility and show warning if needed
function checkBrowserCompatibility() {
  const compatibilitySection = document.querySelector(
    "#compatibility-section"
  ) as HTMLElement;

  if (!("serial" in navigator)) {
    // Browser doesn't support WebSerial API, show compatibility warning
    compatibilitySection.style.display = "block";
  } else {
    // Browser supports WebSerial API, hide compatibility section
    compatibilitySection.style.display = "none";
  }
}

// Check for paired devices and browser compatibility on page load
checkBrowserCompatibility();
displayPairedDevices();

// Show available ports button (only for when no devices are paired)
showPortsBtn.addEventListener("click", async () => {
  try {
    showPortsBtn.disabled = true;
    showPortsBtn.textContent = "Pairing devices...";
    resultsArea.innerHTML =
      '<p class="status">Requesting permission to access serial ports...</p>';

    try {
      await navigator.serial.requestPort();
      // Refresh the display
      await displayPairedDevices();
    } catch (permissionError) {
      console.log("Permission dialog cancelled:", permissionError);
    }
  } catch (error) {
    resultsArea.innerHTML += `<p class="error">Error: ${
      error instanceof Error ? error.message : error
    }</p>`;
  } finally {
    showPortsBtn.disabled = false;
    showPortsBtn.textContent = "Show Available Ports";
  }
});

// Manage devices button (always available)
manageDevicesBtn.addEventListener("click", async () => {
  try {
    manageDevicesBtn.disabled = true;
    manageDevicesBtn.textContent = "Managing...";

    // Always show the permission dialog to pair new devices
    try {
      await navigator.serial.requestPort();
      // Refresh the display to show updated device list
      await displayPairedDevices();
      resultsArea.innerHTML +=
        '<p class="success">Device pairing completed. Updated device list above.</p>';
    } catch (permissionError) {
      console.log("Permission dialog cancelled:", permissionError);
    }
  } catch (error) {
    resultsArea.innerHTML += `<p class="error">Error: ${
      error instanceof Error ? error.message : error
    }</p>`;
  } finally {
    manageDevicesBtn.disabled = false;
    manageDevicesBtn.textContent = "Manage Devices";
    resultsArea.scrollTop = resultsArea.scrollHeight;
  }
});

// Find port button
findPortBtn.addEventListener("click", async () => {
  try {
    findPortBtn.disabled = true;
    findPortBtn.textContent = "Finding ports...";
    resultsArea.innerHTML = '<p class="status">Starting port detection...</p>';

    await findPortWeb((message: string) => {
      resultsArea.innerHTML += `<p class="log">${message}</p>`;
      resultsArea.scrollTop = resultsArea.scrollHeight;
    });
  } catch (error) {
    resultsArea.innerHTML += `<p class="error">Error: ${
      error instanceof Error ? error.message : error
    }</p>`;
  } finally {
    findPortBtn.disabled = false;
    findPortBtn.textContent = "Find MotorsBus Port";
  }
});