NERDDISCO commited on
Commit
dc82a28
·
1 Parent(s): 130bae4

feat: everything is using just one config

Browse files
examples/robot-control-web/components/CalibrationPanel.tsx CHANGED
@@ -92,8 +92,9 @@ export function CalibrationPanel({ robot, onFinish }: CalibrationPanelProps) {
92
  setIsCalibrating(true);
93
  initializeMotorData();
94
 
95
- // Use the simple calibrate API - just pass the robot connection
96
- const process = await calibrate(robot, {
 
97
  onLiveUpdate: (data) => {
98
  setMotorData(data);
99
  setStatus(
 
92
  setIsCalibrating(true);
93
  initializeMotorData();
94
 
95
+ // Use the unified config API for calibration
96
+ const process = await calibrate({
97
+ robot,
98
  onLiveUpdate: (data) => {
99
  setMotorData(data);
100
  setStatus(
examples/robot-control-web/components/TeleoperationPanel.tsx CHANGED
@@ -99,14 +99,22 @@ export function TeleoperationPanel({
99
 
100
  return () => {
101
  // Cleanup on unmount
102
- if (keyboardProcessRef.current) {
103
- keyboardProcessRef.current.disconnect();
104
- keyboardProcessRef.current = null;
105
- }
106
- if (directProcessRef.current) {
107
- directProcessRef.current.disconnect();
108
- directProcessRef.current = null;
109
- }
 
 
 
 
 
 
 
 
110
  };
111
  }, [robot]);
112
 
@@ -165,22 +173,33 @@ export function TeleoperationPanel({
165
  }
166
  };
167
 
168
- const handleStop = () => {
169
- if (keyboardProcessRef.current) {
170
- keyboardProcessRef.current.stop();
171
- }
172
- if (directProcessRef.current) {
173
- directProcessRef.current.stop();
 
 
 
 
 
174
  }
175
- console.log("🛑 Both keyboard and direct teleoperation stopped");
176
  };
177
 
178
- const handleClose = () => {
179
- if (keyboardProcessRef.current) {
180
- keyboardProcessRef.current.stop();
181
- }
182
- if (directProcessRef.current) {
183
- directProcessRef.current.stop();
 
 
 
 
 
 
 
184
  }
185
  onClose();
186
  };
 
99
 
100
  return () => {
101
  // Cleanup on unmount
102
+ const cleanup = async () => {
103
+ try {
104
+ if (keyboardProcessRef.current) {
105
+ await keyboardProcessRef.current.disconnect();
106
+ keyboardProcessRef.current = null;
107
+ }
108
+ if (directProcessRef.current) {
109
+ await directProcessRef.current.disconnect();
110
+ directProcessRef.current = null;
111
+ }
112
+ console.log("🧹 Teleoperation cleanup completed");
113
+ } catch (error) {
114
+ console.warn("Error during teleoperation cleanup:", error);
115
+ }
116
+ };
117
+ cleanup();
118
  };
119
  }, [robot]);
120
 
 
173
  }
174
  };
175
 
176
+ const handleStop = async () => {
177
+ try {
178
+ if (keyboardProcessRef.current) {
179
+ keyboardProcessRef.current.stop();
180
+ }
181
+ if (directProcessRef.current) {
182
+ directProcessRef.current.stop();
183
+ }
184
+ console.log("🛑 Both keyboard and direct teleoperation stopped");
185
+ } catch (error) {
186
+ console.warn("Error during teleoperation stop:", error);
187
  }
 
188
  };
189
 
190
+ const handleClose = async () => {
191
+ try {
192
+ if (keyboardProcessRef.current) {
193
+ keyboardProcessRef.current.stop();
194
+ await keyboardProcessRef.current.disconnect();
195
+ }
196
+ if (directProcessRef.current) {
197
+ directProcessRef.current.stop();
198
+ await directProcessRef.current.disconnect();
199
+ }
200
+ console.log("🔌 Properly disconnected from robot");
201
+ } catch (error) {
202
+ console.warn("Error during teleoperation cleanup:", error);
203
  }
204
  onClose();
205
  };
packages/web/README.md CHANGED
@@ -6,7 +6,7 @@ Browser-native robotics control using WebSerial and WebUSB APIs.
6
 
7
  - **Direct Hardware Control**: STS3215 motor communication via WebSerial API
8
  - **Device Persistence**: WebUSB API for automatic robot reconnection
9
- - **Real-time Teleoperation**: Keyboard control with live motor feedback
10
  - **Motor Calibration**: Automated homing offset and range recording
11
  - **Cross-browser Support**: Chrome/Edge 89+ with HTTPS or localhost
12
  - **TypeScript Native**: Full type safety and IntelliSense support
@@ -27,7 +27,14 @@ yarn add @lerobot/web
27
  ## Quick Start
28
 
29
  ```typescript
30
- import { findPort, releaseMotors, calibrate, teleoperate } from "@lerobot/web";
 
 
 
 
 
 
 
31
 
32
  // 1. Find and connect to hardware
33
  const findProcess = await findPort();
@@ -39,7 +46,8 @@ await releaseMotors(robot);
39
  console.log("🔓 Motors released - you can move the robot by hand");
40
 
41
  // 3. Calibrate motors
42
- const calibrationProcess = await calibrate(robot, {
 
43
  onProgress: (message) => console.log(message),
44
  onLiveUpdate: (data) => console.log("Live positions:", data),
45
  });
@@ -54,7 +62,11 @@ setTimeout(() => {
54
  const calibrationData = await calibrationProcess.result;
55
 
56
  // 4. Start teleoperation
57
- const teleop = await teleoperate(robot, { calibrationData });
 
 
 
 
58
  teleop.start();
59
 
60
  // Stop teleoperation when done
@@ -66,7 +78,7 @@ setTimeout(() => {
66
 
67
  ## Core API
68
 
69
- ### `findPort(options?): Promise<FindPortProcess>`
70
 
71
  Discovers and connects to robotics hardware using WebSerial API. Two modes: interactive (shows port dialog) and auto-connect (reconnects to known robots).
72
 
@@ -157,12 +169,13 @@ interface RobotConfig {
157
 
158
  ---
159
 
160
- ### `calibrate(robot, options?): Promise<CalibrationProcess>`
161
 
162
  Calibrates motor homing offsets and records range of motion.
163
 
164
  ```typescript
165
- const calibrationProcess = await calibrate(robot, {
 
166
  onProgress: (message) => {
167
  console.log(message); // "⚙️ Setting motor homing offsets"
168
  },
@@ -187,8 +200,8 @@ const calibrationData = await calibrationProcess.result;
187
 
188
  #### Parameters
189
 
190
- - `robot: RobotConnection` - Connected robot from `findPort()`
191
- - `options?: CalibrationOptions`
192
  - `onProgress?: (message: string) => void` - Progress messages
193
  - `onLiveUpdate?: (data: LiveCalibrationData) => void` - Real-time position updates
194
 
@@ -214,13 +227,19 @@ const calibrationData = await calibrationProcess.result;
214
 
215
  ---
216
 
217
- ### `teleoperate(robot, options?): Promise<TeleoperationProcess>`
 
 
218
 
219
- Enables real-time robot control with keyboard input and programmatic movement.
220
 
221
  ```typescript
222
- const teleop = await teleoperate(robot, {
 
 
 
223
  calibrationData: savedCalibrationData, // From calibrate()
 
224
  onStateUpdate: (state) => {
225
  console.log(`Active: ${state.isActive}`);
226
  console.log(`Motors:`, state.motorConfigs);
@@ -228,37 +247,62 @@ const teleop = await teleoperate(robot, {
228
  });
229
 
230
  // Start keyboard control
231
- teleop.start();
 
 
 
 
 
 
 
 
 
 
 
 
 
232
 
233
- // Programmatic control
234
- await teleop.moveMotor("shoulder_pan", 2048);
235
- await teleop.setMotorPositions({
 
 
 
 
 
 
 
 
 
 
 
 
236
  shoulder_pan: 2048,
237
  elbow_flex: 1500,
238
  });
239
 
240
- // Stop teleoperation when finished
241
- setTimeout(() => {
242
- console.log("Stopping teleoperation...");
243
- teleop.stop();
244
- }, 30000); // Or call teleop.stop() when user is done
245
  ```
246
 
247
  #### Parameters
248
 
249
- - `robot: RobotConnection` - Connected robot from `findPort()`
250
- - `options?: TeleoperationOptions`
 
 
 
251
  - `calibrationData?: { [motorName: string]: any }` - Calibration data from `calibrate()`
252
  - `onStateUpdate?: (state: TeleoperationState) => void` - State change callback
253
 
254
  #### Returns: `TeleoperationProcess`
255
 
256
- - `start(): void` - Begin keyboard teleoperation
257
- - `stop(): void` - Stop teleoperation and clear key states
258
- - `updateKeyState(key: string, pressed: boolean): void` - Manual key state control
259
  - `getState(): TeleoperationState` - Current state and motor positions
260
- - `moveMotor(motorName: string, position: number): Promise<boolean>` - Move single motor
261
- - `setMotorPositions(positions: { [motorName: string]: number }): Promise<boolean>` - Move multiple motors
 
262
  - `disconnect(): Promise<void>` - Stop and disconnect
263
 
264
  #### Keyboard Controls (SO-100)
 
6
 
7
  - **Direct Hardware Control**: STS3215 motor communication via WebSerial API
8
  - **Device Persistence**: WebUSB API for automatic robot reconnection
9
+ - **Extensible Teleoperation**: Multiple input devices (keyboard, direct control, future: leader arms, joysticks)
10
  - **Motor Calibration**: Automated homing offset and range recording
11
  - **Cross-browser Support**: Chrome/Edge 89+ with HTTPS or localhost
12
  - **TypeScript Native**: Full type safety and IntelliSense support
 
27
  ## Quick Start
28
 
29
  ```typescript
30
+ import {
31
+ findPort,
32
+ releaseMotors,
33
+ calibrate,
34
+ teleoperate,
35
+ KeyboardTeleoperator,
36
+ DirectTeleoperator,
37
+ } from "@lerobot/web";
38
 
39
  // 1. Find and connect to hardware
40
  const findProcess = await findPort();
 
46
  console.log("🔓 Motors released - you can move the robot by hand");
47
 
48
  // 3. Calibrate motors
49
+ const calibrationProcess = await calibrate({
50
+ robot,
51
  onProgress: (message) => console.log(message),
52
  onLiveUpdate: (data) => console.log("Live positions:", data),
53
  });
 
62
  const calibrationData = await calibrationProcess.result;
63
 
64
  // 4. Start teleoperation
65
+ const teleop = await teleoperate({
66
+ robot,
67
+ calibrationData,
68
+ teleop: { type: "keyboard" }, // or { type: "direct" }
69
+ });
70
  teleop.start();
71
 
72
  // Stop teleoperation when done
 
78
 
79
  ## Core API
80
 
81
+ ### `findPort(config?): Promise<FindPortProcess>`
82
 
83
  Discovers and connects to robotics hardware using WebSerial API. Two modes: interactive (shows port dialog) and auto-connect (reconnects to known robots).
84
 
 
169
 
170
  ---
171
 
172
+ ### `calibrate(config): Promise<CalibrationProcess>`
173
 
174
  Calibrates motor homing offsets and records range of motion.
175
 
176
  ```typescript
177
+ const calibrationProcess = await calibrate({
178
+ robot,
179
  onProgress: (message) => {
180
  console.log(message); // "⚙️ Setting motor homing offsets"
181
  },
 
200
 
201
  #### Parameters
202
 
203
+ - `config: CalibrateConfig`
204
+ - `robot: RobotConnection` - Connected robot from `findPort()`
205
  - `onProgress?: (message: string) => void` - Progress messages
206
  - `onLiveUpdate?: (data: LiveCalibrationData) => void` - Real-time position updates
207
 
 
227
 
228
  ---
229
 
230
+ ### `teleoperate(config): Promise<TeleoperationProcess>`
231
+
232
+ Enables real-time robot control with extensible input devices. Supports keyboard control and direct programmatic movement, with architecture for future input devices like leader arms and joysticks.
233
 
234
+ #### Keyboard Teleoperation
235
 
236
  ```typescript
237
+ import { teleoperate, KeyboardTeleoperator } from "@lerobot/web";
238
+
239
+ const keyboardTeleop = await teleoperate({
240
+ robot,
241
  calibrationData: savedCalibrationData, // From calibrate()
242
+ teleop: { type: "keyboard" }, // Uses keyboard controls
243
  onStateUpdate: (state) => {
244
  console.log(`Active: ${state.isActive}`);
245
  console.log(`Motors:`, state.motorConfigs);
 
247
  });
248
 
249
  // Start keyboard control
250
+ keyboardTeleop.start();
251
+
252
+ // Access keyboard-specific methods
253
+ const keyboardController = keyboardTeleop.teleoperator as KeyboardTeleoperator;
254
+ await keyboardController.moveMotor("shoulder_pan", 2048);
255
+
256
+ // Stop when finished
257
+ setTimeout(() => keyboardTeleop.stop(), 30000);
258
+ ```
259
+
260
+ #### Direct Teleoperation
261
+
262
+ ```typescript
263
+ import { teleoperate, DirectTeleoperator } from "@lerobot/web";
264
 
265
+ const directTeleop = await teleoperate({
266
+ robot,
267
+ calibrationData: savedCalibrationData,
268
+ teleop: { type: "direct" }, // For programmatic control
269
+ onStateUpdate: (state) => {
270
+ console.log(`Motors:`, state.motorConfigs);
271
+ },
272
+ });
273
+
274
+ directTeleop.start();
275
+
276
+ // Access direct control methods
277
+ const directController = directTeleop.teleoperator as DirectTeleoperator;
278
+ await directController.moveMotor("shoulder_pan", 2048);
279
+ await directController.setMotorPositions({
280
  shoulder_pan: 2048,
281
  elbow_flex: 1500,
282
  });
283
 
284
+ // Stop when finished
285
+ setTimeout(() => directTeleop.stop(), 30000);
 
 
 
286
  ```
287
 
288
  #### Parameters
289
 
290
+ - `config: TeleoperateConfig`
291
+ - `robot: RobotConnection` - Connected robot from `findPort()`
292
+ - `teleop: TeleoperatorConfig` - Teleoperator configuration:
293
+ - `{ type: "keyboard", stepSize?: number, updateRate?: number, keyTimeout?: number }` - Keyboard control
294
+ - `{ type: "direct" }` - Direct programmatic control
295
  - `calibrationData?: { [motorName: string]: any }` - Calibration data from `calibrate()`
296
  - `onStateUpdate?: (state: TeleoperationState) => void` - State change callback
297
 
298
  #### Returns: `TeleoperationProcess`
299
 
300
+ - `start(): void` - Begin teleoperation
301
+ - `stop(): void` - Stop teleoperation and clear states
 
302
  - `getState(): TeleoperationState` - Current state and motor positions
303
+ - `teleoperator: BaseWebTeleoperator` - Access teleoperator-specific methods:
304
+ - **KeyboardTeleoperator**: `updateKeyState()`, `moveMotor()`, etc.
305
+ - **DirectTeleoperator**: `moveMotor()`, `setMotorPositions()`, etc.
306
  - `disconnect(): Promise<void>` - Stop and disconnect
307
 
308
  #### Keyboard Controls (SO-100)
packages/web/src/calibrate.ts CHANGED
@@ -18,6 +18,7 @@ import { createSO100Config } from "./robots/so100_config.js";
18
  import type { RobotConnection } from "./types/robot-connection.js";
19
  import type { RobotHardwareConfig } from "./types/robot-config.js";
20
  import type {
 
21
  WebCalibrationResults,
22
  LiveCalibrationData,
23
  CalibrationProcess,
@@ -127,29 +128,27 @@ function applyRobotSpecificRangeAdjustments(
127
  * Main calibrate function - simple API, handles robot types internally
128
  */
129
  export async function calibrate(
130
- robotConnection: RobotConnection,
131
- options?: {
132
- onLiveUpdate?: (data: LiveCalibrationData) => void;
133
- onProgress?: (message: string) => void;
134
- }
135
  ): Promise<CalibrationProcess> {
 
 
136
  // Validate required fields
137
- if (!robotConnection.robotType) {
138
  throw new Error(
139
  "Robot type is required for calibration. Please configure the robot first."
140
  );
141
  }
142
 
143
  // Create web serial port wrapper
144
- const port = new WebSerialPortWrapper(robotConnection.port);
145
  await port.initialize();
146
 
147
  // Get robot-specific configuration
148
- let config: RobotHardwareConfig;
149
- if (robotConnection.robotType.startsWith("so100")) {
150
- config = createSO100Config(robotConnection.robotType);
151
  } else {
152
- throw new Error(`Unsupported robot type: ${robotConnection.robotType}`);
153
  }
154
 
155
  let shouldStop = false;
@@ -158,26 +157,26 @@ export async function calibrate(
158
  // Start calibration process
159
  const resultPromise = (async (): Promise<WebCalibrationResults> => {
160
  // Step 1: Set homing offsets (automatic)
161
- options?.onProgress?.("⚙️ Setting motor homing offsets");
162
  const homingOffsets = await setHomingOffsets(
163
  port,
164
- config.motorIds,
165
- config.motorNames
166
  );
167
 
168
  // Step 2: Record ranges of motion with live updates
169
  const { rangeMins, rangeMaxes } = await recordRangesOfMotion(
170
  port,
171
- config.motorIds,
172
- config.motorNames,
173
  stopFunction,
174
- options?.onLiveUpdate
175
  );
176
 
177
  // Step 3: Apply robot-specific range adjustments
178
  applyRobotSpecificRangeAdjustments(
179
- robotConnection.robotType!,
180
- config.protocol,
181
  rangeMins,
182
  rangeMaxes
183
  );
@@ -185,8 +184,8 @@ export async function calibrate(
185
  // Step 4: Write hardware position limits to motors
186
  await writeHardwarePositionLimits(
187
  port,
188
- config.motorIds,
189
- config.motorNames,
190
  rangeMins,
191
  rangeMaxes
192
  );
@@ -194,13 +193,13 @@ export async function calibrate(
194
  // Step 5: Compile results
195
  const results: WebCalibrationResults = {};
196
 
197
- for (let i = 0; i < config.motorNames.length; i++) {
198
- const motorName = config.motorNames[i];
199
- const motorId = config.motorIds[i];
200
 
201
  results[motorName] = {
202
  id: motorId,
203
- drive_mode: config.driveModes[i],
204
  homing_offset: homingOffsets[motorName],
205
  range_min: rangeMins[motorName],
206
  range_max: rangeMaxes[motorName],
 
18
  import type { RobotConnection } from "./types/robot-connection.js";
19
  import type { RobotHardwareConfig } from "./types/robot-config.js";
20
  import type {
21
+ CalibrateConfig,
22
  WebCalibrationResults,
23
  LiveCalibrationData,
24
  CalibrationProcess,
 
128
  * Main calibrate function - simple API, handles robot types internally
129
  */
130
  export async function calibrate(
131
+ config: CalibrateConfig
 
 
 
 
132
  ): Promise<CalibrationProcess> {
133
+ const { robot, onLiveUpdate, onProgress } = config;
134
+
135
  // Validate required fields
136
+ if (!robot.robotType) {
137
  throw new Error(
138
  "Robot type is required for calibration. Please configure the robot first."
139
  );
140
  }
141
 
142
  // Create web serial port wrapper
143
+ const port = new WebSerialPortWrapper(robot.port);
144
  await port.initialize();
145
 
146
  // Get robot-specific configuration
147
+ let robotConfig: RobotHardwareConfig;
148
+ if (robot.robotType.startsWith("so100")) {
149
+ robotConfig = createSO100Config(robot.robotType);
150
  } else {
151
+ throw new Error(`Unsupported robot type: ${robot.robotType}`);
152
  }
153
 
154
  let shouldStop = false;
 
157
  // Start calibration process
158
  const resultPromise = (async (): Promise<WebCalibrationResults> => {
159
  // Step 1: Set homing offsets (automatic)
160
+ onProgress?.("⚙️ Setting motor homing offsets");
161
  const homingOffsets = await setHomingOffsets(
162
  port,
163
+ robotConfig.motorIds,
164
+ robotConfig.motorNames
165
  );
166
 
167
  // Step 2: Record ranges of motion with live updates
168
  const { rangeMins, rangeMaxes } = await recordRangesOfMotion(
169
  port,
170
+ robotConfig.motorIds,
171
+ robotConfig.motorNames,
172
  stopFunction,
173
+ onLiveUpdate
174
  );
175
 
176
  // Step 3: Apply robot-specific range adjustments
177
  applyRobotSpecificRangeAdjustments(
178
+ robot.robotType!,
179
+ robotConfig.protocol,
180
  rangeMins,
181
  rangeMaxes
182
  );
 
184
  // Step 4: Write hardware position limits to motors
185
  await writeHardwarePositionLimits(
186
  port,
187
+ robotConfig.motorIds,
188
+ robotConfig.motorNames,
189
  rangeMins,
190
  rangeMaxes
191
  );
 
193
  // Step 5: Compile results
194
  const results: WebCalibrationResults = {};
195
 
196
+ for (let i = 0; i < robotConfig.motorNames.length; i++) {
197
+ const motorName = robotConfig.motorNames[i];
198
+ const motorId = robotConfig.motorIds[i];
199
 
200
  results[motorName] = {
201
  id: motorId,
202
+ drive_mode: robotConfig.driveModes[i],
203
  homing_offset: homingOffsets[motorName],
204
  range_min: rangeMins[motorName],
205
  range_max: rangeMaxes[motorName],
packages/web/src/find_port.ts CHANGED
@@ -42,7 +42,7 @@ import type {
42
  } from "./types/robot-connection.js";
43
  import type {
44
  Serial,
45
- FindPortOptions,
46
  FindPortProcess,
47
  USBDevice,
48
  } from "./types/port-discovery.js";
@@ -171,7 +171,7 @@ async function getStoredUSBDeviceMetadata(port: SerialPort): Promise<{
171
  * Interactive mode: Show native dialogs for port + device selection
172
  */
173
  async function findPortInteractive(
174
- options: FindPortOptions
175
  ): Promise<RobotConnection[]> {
176
  const { onMessage } = options;
177
 
@@ -239,7 +239,7 @@ async function findPortInteractive(
239
  */
240
  async function findPortAutoConnect(
241
  robotConfigs: RobotConfig[],
242
- options: FindPortOptions
243
  ): Promise<RobotConnection[]> {
244
  const { onMessage } = options;
245
  const results: RobotConnection[] = [];
@@ -273,6 +273,8 @@ async function findPortAutoConnect(
273
  const wasOpen = port.readable !== null;
274
  if (!wasOpen) {
275
  await port.open({ baudRate: 1000000 });
 
 
276
  }
277
 
278
  // Test connection by trying basic motor communication
@@ -280,7 +282,18 @@ async function findPortAutoConnect(
280
  await portWrapper.initialize();
281
 
282
  // Try to read from motor ID 1 (most robots have at least one motor)
283
- const testPosition = await readMotorPosition(portWrapper, 1);
 
 
 
 
 
 
 
 
 
 
 
284
 
285
  // If we can read a position, this is likely a working robot port
286
  if (testPosition !== null) {
@@ -367,7 +380,7 @@ async function findPortAutoConnect(
367
  * Mode 2: Auto-connect - Returns RobotConnection[]
368
  */
369
  export async function findPort(
370
- options: FindPortOptions = {}
371
  ): Promise<FindPortProcess> {
372
  // Check WebSerial support
373
  if (!isWebSerialSupported()) {
@@ -376,7 +389,7 @@ export async function findPort(
376
  );
377
  }
378
 
379
- const { robotConfigs, onMessage } = options;
380
  let stopped = false;
381
 
382
  // Determine mode
@@ -395,9 +408,9 @@ export async function findPort(
395
  }
396
 
397
  if (isAutoConnectMode) {
398
- return await findPortAutoConnect(robotConfigs!, options);
399
  } else {
400
- return await findPortInteractive(options);
401
  }
402
  })();
403
 
 
42
  } from "./types/robot-connection.js";
43
  import type {
44
  Serial,
45
+ FindPortConfig,
46
  FindPortProcess,
47
  USBDevice,
48
  } from "./types/port-discovery.js";
 
171
  * Interactive mode: Show native dialogs for port + device selection
172
  */
173
  async function findPortInteractive(
174
+ options: FindPortConfig
175
  ): Promise<RobotConnection[]> {
176
  const { onMessage } = options;
177
 
 
239
  */
240
  async function findPortAutoConnect(
241
  robotConfigs: RobotConfig[],
242
+ options: FindPortConfig
243
  ): Promise<RobotConnection[]> {
244
  const { onMessage } = options;
245
  const results: RobotConnection[] = [];
 
273
  const wasOpen = port.readable !== null;
274
  if (!wasOpen) {
275
  await port.open({ baudRate: 1000000 });
276
+ // Small delay to allow port to stabilize after opening
277
+ await new Promise((resolve) => setTimeout(resolve, 100));
278
  }
279
 
280
  // Test connection by trying basic motor communication
 
282
  await portWrapper.initialize();
283
 
284
  // Try to read from motor ID 1 (most robots have at least one motor)
285
+ // Retry mechanism for more robust connection testing
286
+ let testPosition: number | null = null;
287
+ for (let attempt = 0; attempt < 3; attempt++) {
288
+ try {
289
+ testPosition = await readMotorPosition(portWrapper, 1);
290
+ if (testPosition !== null) break;
291
+ await new Promise((resolve) => setTimeout(resolve, 50));
292
+ } catch (retryError) {
293
+ if (attempt === 2) throw retryError;
294
+ await new Promise((resolve) => setTimeout(resolve, 100));
295
+ }
296
+ }
297
 
298
  // If we can read a position, this is likely a working robot port
299
  if (testPosition !== null) {
 
380
  * Mode 2: Auto-connect - Returns RobotConnection[]
381
  */
382
  export async function findPort(
383
+ config: FindPortConfig = {}
384
  ): Promise<FindPortProcess> {
385
  // Check WebSerial support
386
  if (!isWebSerialSupported()) {
 
389
  );
390
  }
391
 
392
+ const { robotConfigs, onMessage } = config;
393
  let stopped = false;
394
 
395
  // Determine mode
 
408
  }
409
 
410
  if (isAutoConnectMode) {
411
+ return await findPortAutoConnect(robotConfigs!, config);
412
  } else {
413
+ return await findPortInteractive(config);
414
  }
415
  })();
416
 
packages/web/src/index.ts CHANGED
@@ -26,6 +26,12 @@ export type {
26
  } from "./types/robot-connection.js";
27
 
28
  export type {
 
 
 
 
 
 
29
  WebCalibrationResults,
30
  LiveCalibrationData,
31
  CalibrationProcess,
 
26
  } from "./types/robot-connection.js";
27
 
28
  export type {
29
+ FindPortConfig,
30
+ FindPortProcess,
31
+ } from "./types/port-discovery.js";
32
+
33
+ export type {
34
+ CalibrateConfig,
35
  WebCalibrationResults,
36
  LiveCalibrationData,
37
  CalibrationProcess,
packages/web/src/teleoperators/base-teleoperator.ts CHANGED
@@ -48,6 +48,9 @@ export abstract class BaseWebTeleoperator implements WebTeleoperator {
48
 
49
  async disconnect(): Promise<void> {
50
  this.stop();
 
 
 
51
  }
52
 
53
  onMotorConfigsUpdate(motorConfigs: MotorConfig[]): void {
 
48
 
49
  async disconnect(): Promise<void> {
50
  this.stop();
51
+ if (this.port && "close" in this.port) {
52
+ await (this.port as any).close();
53
+ }
54
  }
55
 
56
  onMotorConfigsUpdate(motorConfigs: MotorConfig[]): void {
packages/web/src/types/calibration.ts CHANGED
@@ -2,18 +2,7 @@
2
  * Calibration-related types for web implementation
3
  */
4
 
5
- /**
6
- * Calibration results structure
7
- */
8
- export interface WebCalibrationResults {
9
- [motorName: string]: {
10
- id: number;
11
- drive_mode: number;
12
- homing_offset: number;
13
- range_min: number;
14
- range_max: number;
15
- };
16
- }
17
 
18
  /**
19
  * Live calibration data with current positions and ranges
@@ -27,6 +16,28 @@ export interface LiveCalibrationData {
27
  };
28
  }
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  /**
31
  * Calibration process control object
32
  */
 
2
  * Calibration-related types for web implementation
3
  */
4
 
5
+ import type { RobotConnection } from "./robot-connection.js";
 
 
 
 
 
 
 
 
 
 
 
6
 
7
  /**
8
  * Live calibration data with current positions and ranges
 
16
  };
17
  }
18
 
19
+ /**
20
+ * Config for calibrate function
21
+ */
22
+ export interface CalibrateConfig {
23
+ robot: RobotConnection;
24
+ onLiveUpdate?: (data: LiveCalibrationData) => void;
25
+ onProgress?: (message: string) => void;
26
+ }
27
+
28
+ /**
29
+ * Calibration results structure
30
+ */
31
+ export interface WebCalibrationResults {
32
+ [motorName: string]: {
33
+ id: number;
34
+ drive_mode: number;
35
+ homing_offset: number;
36
+ range_min: number;
37
+ range_max: number;
38
+ };
39
+ }
40
+
41
  /**
42
  * Calibration process control object
43
  */
packages/web/src/types/port-discovery.ts CHANGED
@@ -57,9 +57,9 @@ export interface USB {
57
  }
58
 
59
  /**
60
- * Options for findPort function
61
  */
62
- export interface FindPortOptions {
63
  // Auto-connect mode: provide robot configs to connect to
64
  robotConfigs?: RobotConfig[];
65
 
 
57
  }
58
 
59
  /**
60
+ * Config for findPort function
61
  */
62
+ export interface FindPortConfig {
63
  // Auto-connect mode: provide robot configs to connect to
64
  robotConfigs?: RobotConfig[];
65
 
packages/web/src/utils/serial-port-wrapper.ts CHANGED
@@ -68,6 +68,13 @@ export class WebSerialPortWrapper {
68
  }
69
 
70
  async close(): Promise<void> {
71
- // Don't close the port itself - just wrapper cleanup
 
 
 
 
 
 
 
72
  }
73
  }
 
68
  }
69
 
70
  async close(): Promise<void> {
71
+ try {
72
+ if (this.port && this.port.readable) {
73
+ await this.port.close();
74
+ console.log("🔌 Serial port closed successfully");
75
+ }
76
+ } catch (error) {
77
+ console.warn("Error closing serial port:", error);
78
+ }
79
  }
80
  }