Spaces:
Running
Running
chore: remove comments that make no sense
Browse files- docs/conventions.md +1 -0
- src/lerobot/web/calibrate.ts +2 -3
- src/lerobot/web/find_port.ts +18 -14
- src/lerobot/web/robots/robot.ts +0 -1
- src/lerobot/web/robots/so100_config.ts +1 -2
- src/lerobot/web/teleoperate.ts +7 -7
- src/lerobot/web/types/calibration.ts +1 -1
- src/lerobot/web/types/robot-processes.ts +0 -43
- src/lerobot/web/types/teleoperation.ts +1 -1
- src/lerobot/web/utils/motor-calibration.ts +2 -2
- src/lerobot/web/utils/motor-communication.ts +11 -14
- src/lerobot/web/utils/robot-connection-manager.ts +0 -168
- src/lerobot/web/utils/serial-port-wrapper.ts +0 -1
- src/lerobot/web/utils/sts3215-protocol.ts +1 -1
docs/conventions.md
CHANGED
@@ -19,6 +19,7 @@
|
|
19 |
- **No Code Duplication**: Use shared utils, never reimplement the same functionality across files
|
20 |
- **Direct Library Usage**: End users call library functions directly (e.g., `calibrate()`, `teleoperate()`) - avoid unnecessary abstraction layers
|
21 |
- **Comments**: Write about the functionality, not what you did. We only need to know what the code is doing to make it more easy to understand, not a history of the changes
|
|
|
22 |
|
23 |
## Project Goals
|
24 |
|
|
|
19 |
- **No Code Duplication**: Use shared utils, never reimplement the same functionality across files
|
20 |
- **Direct Library Usage**: End users call library functions directly (e.g., `calibrate()`, `teleoperate()`) - avoid unnecessary abstraction layers
|
21 |
- **Comments**: Write about the functionality, not what you did. We only need to know what the code is doing to make it more easy to understand, not a history of the changes
|
22 |
+
- **No Reference Comments**: Never write comments like "same pattern as calibrate.ts", "matches Node.js", "copied from X", etc. Comments should explain what the code does, not where it came from or what it's similar to
|
23 |
|
24 |
## Project Goals
|
25 |
|
src/lerobot/web/calibrate.ts
CHANGED
@@ -39,7 +39,7 @@ async function recordRangesOfMotion(
|
|
39 |
const rangeMins: { [motor: string]: number } = {};
|
40 |
const rangeMaxes: { [motor: string]: number } = {};
|
41 |
|
42 |
-
// Read actual current positions
|
43 |
const startPositions = await readAllMotorPositions(port, motorIds);
|
44 |
|
45 |
for (let i = 0; i < motorNames.length; i++) {
|
@@ -105,7 +105,6 @@ function applyRobotSpecificRangeAdjustments(
|
|
105 |
if (robotType.startsWith("so100") && rangeMins["wrist_roll"] !== undefined) {
|
106 |
// The wrist_roll is a continuous rotation motor that should use the full
|
107 |
// 0-4095 range regardless of what the user recorded during calibration.
|
108 |
-
// This matches the hardware specification and Python lerobot behavior.
|
109 |
rangeMins["wrist_roll"] = 0;
|
110 |
rangeMaxes["wrist_roll"] = protocol.resolution - 1;
|
111 |
}
|
@@ -185,7 +184,7 @@ export async function calibrate(
|
|
185 |
rangeMaxes
|
186 |
);
|
187 |
|
188 |
-
// Step 5: Compile results
|
189 |
const results: WebCalibrationResults = {};
|
190 |
|
191 |
for (let i = 0; i < config.motorNames.length; i++) {
|
|
|
39 |
const rangeMins: { [motor: string]: number } = {};
|
40 |
const rangeMaxes: { [motor: string]: number } = {};
|
41 |
|
42 |
+
// Read actual current positions
|
43 |
const startPositions = await readAllMotorPositions(port, motorIds);
|
44 |
|
45 |
for (let i = 0; i < motorNames.length; i++) {
|
|
|
105 |
if (robotType.startsWith("so100") && rangeMins["wrist_roll"] !== undefined) {
|
106 |
// The wrist_roll is a continuous rotation motor that should use the full
|
107 |
// 0-4095 range regardless of what the user recorded during calibration.
|
|
|
108 |
rangeMins["wrist_roll"] = 0;
|
109 |
rangeMaxes["wrist_roll"] = protocol.resolution - 1;
|
110 |
}
|
|
|
184 |
rangeMaxes
|
185 |
);
|
186 |
|
187 |
+
// Step 5: Compile results
|
188 |
const results: WebCalibrationResults = {};
|
189 |
|
190 |
for (let i = 0; i < config.motorNames.length; i++) {
|
src/lerobot/web/find_port.ts
CHANGED
@@ -28,7 +28,8 @@
|
|
28 |
* await calibrate(storedRobots[0], options);
|
29 |
*/
|
30 |
|
31 |
-
import {
|
|
|
32 |
import type {
|
33 |
RobotConnection,
|
34 |
RobotConfig,
|
@@ -146,19 +147,22 @@ async function findPortAutoConnect(
|
|
146 |
await port.open({ baudRate: 1000000 });
|
147 |
}
|
148 |
|
149 |
-
// Test connection by trying
|
150 |
-
const
|
151 |
-
await
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
|
|
|
|
|
|
162 |
} catch (portError) {
|
163 |
// This port didn't work, try next one
|
164 |
console.log(
|
|
|
28 |
* await calibrate(storedRobots[0], options);
|
29 |
*/
|
30 |
|
31 |
+
import { WebSerialPortWrapper } from "./utils/serial-port-wrapper.js";
|
32 |
+
import { readMotorPosition } from "./utils/motor-communication.js";
|
33 |
import type {
|
34 |
RobotConnection,
|
35 |
RobotConfig,
|
|
|
147 |
await port.open({ baudRate: 1000000 });
|
148 |
}
|
149 |
|
150 |
+
// Test connection by trying basic motor communication
|
151 |
+
const portWrapper = new WebSerialPortWrapper(port);
|
152 |
+
await portWrapper.initialize();
|
153 |
+
|
154 |
+
// Try to read from motor ID 1 (most robots have at least one motor)
|
155 |
+
const testPosition = await readMotorPosition(portWrapper, 1);
|
156 |
+
|
157 |
+
// If we can read a position, this is likely a working robot port
|
158 |
+
if (testPosition !== null) {
|
159 |
+
matchedPort = port;
|
160 |
+
connected = true;
|
161 |
+
onMessage?.(`✅ Connected to ${config.robotId}`);
|
162 |
+
break;
|
163 |
+
} else {
|
164 |
+
throw new Error("No motor response - not a robot port");
|
165 |
+
}
|
166 |
} catch (portError) {
|
167 |
// This port didn't work, try next one
|
168 |
console.log(
|
src/lerobot/web/robots/robot.ts
CHANGED
@@ -1,7 +1,6 @@
|
|
1 |
/**
|
2 |
* Base Robot class for Web platform
|
3 |
* Uses Web Serial API for serial communication
|
4 |
-
* Mirrors Python lerobot/common/robots/robot.py but adapted for browser environment
|
5 |
*/
|
6 |
|
7 |
import type { RobotConfig } from "../../node/robots/config.js";
|
|
|
1 |
/**
|
2 |
* Base Robot class for Web platform
|
3 |
* Uses Web Serial API for serial communication
|
|
|
4 |
*/
|
5 |
|
6 |
import type { RobotConfig } from "../../node/robots/config.js";
|
src/lerobot/web/robots/so100_config.ts
CHANGED
@@ -1,6 +1,5 @@
|
|
1 |
/**
|
2 |
* SO-100 specific hardware configuration
|
3 |
-
* Matches Node.js SO-100 config structure and Python lerobot exactly
|
4 |
*/
|
5 |
|
6 |
import type { RobotHardwareConfig } from "../types/robot-config.js";
|
@@ -35,7 +34,7 @@ export const SO100_CONFIG = {
|
|
35 |
"gripper",
|
36 |
],
|
37 |
motorIds: [1, 2, 3, 4, 5, 6],
|
38 |
-
//
|
39 |
driveModes: [0, 0, 0, 0, 0, 0],
|
40 |
};
|
41 |
|
|
|
1 |
/**
|
2 |
* SO-100 specific hardware configuration
|
|
|
3 |
*/
|
4 |
|
5 |
import type { RobotHardwareConfig } from "../types/robot-config.js";
|
|
|
34 |
"gripper",
|
35 |
],
|
36 |
motorIds: [1, 2, 3, 4, 5, 6],
|
37 |
+
// All SO-100 motors use drive_mode=0
|
38 |
driveModes: [0, 0, 0, 0, 0, 0],
|
39 |
};
|
40 |
|
src/lerobot/web/teleoperate.ts
CHANGED
@@ -82,7 +82,7 @@ export class WebTeleoperationController {
|
|
82 |
} = {};
|
83 |
private onStateUpdate?: (state: TeleoperationState) => void;
|
84 |
|
85 |
-
// Movement parameters
|
86 |
private readonly STEP_SIZE = 8;
|
87 |
private readonly UPDATE_RATE = 60; // 60 FPS
|
88 |
private readonly KEY_TIMEOUT = 600; // ms - longer than browser keyboard repeat delay (~500ms)
|
@@ -100,7 +100,7 @@ export class WebTeleoperationController {
|
|
100 |
}
|
101 |
|
102 |
async initialize(): Promise<void> {
|
103 |
-
// Read current positions
|
104 |
for (const config of this.motorConfigs) {
|
105 |
const position = await readMotorPosition(this.port, config.id);
|
106 |
if (position !== null) {
|
@@ -213,7 +213,7 @@ export class WebTeleoperationController {
|
|
213 |
);
|
214 |
}
|
215 |
|
216 |
-
// Send motor commands
|
217 |
Object.entries(targetPositions).forEach(([motorName, targetPosition]) => {
|
218 |
const motorConfig = this.motorConfigs.find((m) => m.name === motorName);
|
219 |
if (motorConfig && targetPosition !== motorConfig.currentPosition) {
|
@@ -273,7 +273,7 @@ export class WebTeleoperationController {
|
|
273 |
}
|
274 |
|
275 |
/**
|
276 |
-
* Main teleoperate function - simple API
|
277 |
* Handles robot types internally, creates appropriate motor configurations
|
278 |
*/
|
279 |
export async function teleoperate(
|
@@ -290,11 +290,11 @@ export async function teleoperate(
|
|
290 |
);
|
291 |
}
|
292 |
|
293 |
-
// Create web serial port wrapper
|
294 |
const port = new WebSerialPortWrapper(robotConnection.port);
|
295 |
await port.initialize();
|
296 |
|
297 |
-
// Get robot-specific configuration
|
298 |
let config: RobotHardwareConfig;
|
299 |
if (robotConnection.robotType.startsWith("so100")) {
|
300 |
config = createSO100Config(robotConnection.robotType);
|
@@ -322,7 +322,7 @@ export async function teleoperate(
|
|
322 |
);
|
323 |
await controller.initialize();
|
324 |
|
325 |
-
// Wrap controller in process object
|
326 |
return {
|
327 |
start: () => {
|
328 |
controller.start();
|
|
|
82 |
} = {};
|
83 |
private onStateUpdate?: (state: TeleoperationState) => void;
|
84 |
|
85 |
+
// Movement parameters
|
86 |
private readonly STEP_SIZE = 8;
|
87 |
private readonly UPDATE_RATE = 60; // 60 FPS
|
88 |
private readonly KEY_TIMEOUT = 600; // ms - longer than browser keyboard repeat delay (~500ms)
|
|
|
100 |
}
|
101 |
|
102 |
async initialize(): Promise<void> {
|
103 |
+
// Read current motor positions
|
104 |
for (const config of this.motorConfigs) {
|
105 |
const position = await readMotorPosition(this.port, config.id);
|
106 |
if (position !== null) {
|
|
|
213 |
);
|
214 |
}
|
215 |
|
216 |
+
// Send motor commands
|
217 |
Object.entries(targetPositions).forEach(([motorName, targetPosition]) => {
|
218 |
const motorConfig = this.motorConfigs.find((m) => m.name === motorName);
|
219 |
if (motorConfig && targetPosition !== motorConfig.currentPosition) {
|
|
|
273 |
}
|
274 |
|
275 |
/**
|
276 |
+
* Main teleoperate function - simple API
|
277 |
* Handles robot types internally, creates appropriate motor configurations
|
278 |
*/
|
279 |
export async function teleoperate(
|
|
|
290 |
);
|
291 |
}
|
292 |
|
293 |
+
// Create web serial port wrapper
|
294 |
const port = new WebSerialPortWrapper(robotConnection.port);
|
295 |
await port.initialize();
|
296 |
|
297 |
+
// Get robot-specific configuration
|
298 |
let config: RobotHardwareConfig;
|
299 |
if (robotConnection.robotType.startsWith("so100")) {
|
300 |
config = createSO100Config(robotConnection.robotType);
|
|
|
322 |
);
|
323 |
await controller.initialize();
|
324 |
|
325 |
+
// Wrap controller in process object
|
326 |
return {
|
327 |
start: () => {
|
328 |
controller.start();
|
src/lerobot/web/types/calibration.ts
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
*/
|
4 |
|
5 |
/**
|
6 |
-
* Calibration results structure
|
7 |
*/
|
8 |
export interface WebCalibrationResults {
|
9 |
[motorName: string]: {
|
|
|
3 |
*/
|
4 |
|
5 |
/**
|
6 |
+
* Calibration results structure
|
7 |
*/
|
8 |
export interface WebCalibrationResults {
|
9 |
[motorName: string]: {
|
src/lerobot/web/types/robot-processes.ts
DELETED
@@ -1,43 +0,0 @@
|
|
1 |
-
/**
|
2 |
-
* Robot connection management and process types for web implementation
|
3 |
-
*/
|
4 |
-
|
5 |
-
import type { SerialPort } from "./robot-connection.js";
|
6 |
-
|
7 |
-
/**
|
8 |
-
* Robot connection state tracking
|
9 |
-
*/
|
10 |
-
export interface RobotConnectionState {
|
11 |
-
isConnected: boolean;
|
12 |
-
robotType?: "so100_follower" | "so100_leader";
|
13 |
-
robotId?: string;
|
14 |
-
serialNumber?: string;
|
15 |
-
lastError?: string;
|
16 |
-
}
|
17 |
-
|
18 |
-
/**
|
19 |
-
* Robot connection manager interface
|
20 |
-
*/
|
21 |
-
export interface RobotConnectionManager {
|
22 |
-
// State
|
23 |
-
getState(): RobotConnectionState;
|
24 |
-
|
25 |
-
// Connection management
|
26 |
-
connect(
|
27 |
-
port: SerialPort,
|
28 |
-
robotType: string,
|
29 |
-
robotId: string,
|
30 |
-
serialNumber: string
|
31 |
-
): Promise<void>;
|
32 |
-
disconnect(): Promise<void>;
|
33 |
-
|
34 |
-
// Port access
|
35 |
-
getPort(): SerialPort | null;
|
36 |
-
|
37 |
-
// Serial operations (shared by calibration, teleoperation, etc.)
|
38 |
-
writeData(data: Uint8Array): Promise<void>;
|
39 |
-
readData(timeout?: number): Promise<Uint8Array>;
|
40 |
-
|
41 |
-
// Event system
|
42 |
-
onStateChange(callback: (state: RobotConnectionState) => void): () => void;
|
43 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lerobot/web/types/teleoperation.ts
CHANGED
@@ -24,7 +24,7 @@ export interface TeleoperationState {
|
|
24 |
}
|
25 |
|
26 |
/**
|
27 |
-
* Teleoperation process control object
|
28 |
*/
|
29 |
export interface TeleoperationProcess {
|
30 |
start(): void;
|
|
|
24 |
}
|
25 |
|
26 |
/**
|
27 |
+
* Teleoperation process control object
|
28 |
*/
|
29 |
export interface TeleoperationProcess {
|
30 |
start(): void;
|
src/lerobot/web/utils/motor-calibration.ts
CHANGED
@@ -12,7 +12,7 @@ import {
|
|
12 |
} from "./motor-communication.js";
|
13 |
|
14 |
/**
|
15 |
-
* Reset homing offsets to 0 for all motors
|
16 |
*/
|
17 |
export async function resetHomingOffsets(
|
18 |
port: MotorCommunicationPort,
|
@@ -115,7 +115,7 @@ export async function writeHomingOffsetsToMotors(
|
|
115 |
}
|
116 |
|
117 |
/**
|
118 |
-
* Set homing offsets with immediate writing
|
119 |
*/
|
120 |
export async function setHomingOffsets(
|
121 |
port: MotorCommunicationPort,
|
|
|
12 |
} from "./motor-communication.js";
|
13 |
|
14 |
/**
|
15 |
+
* Reset homing offsets to 0 for all motors
|
16 |
*/
|
17 |
export async function resetHomingOffsets(
|
18 |
port: MotorCommunicationPort,
|
|
|
115 |
}
|
116 |
|
117 |
/**
|
118 |
+
* Set homing offsets with immediate writing
|
119 |
*/
|
120 |
export async function setHomingOffsets(
|
121 |
port: MotorCommunicationPort,
|
src/lerobot/web/utils/motor-communication.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
/**
|
2 |
* Motor Communication Utilities
|
3 |
-
*
|
4 |
*/
|
5 |
|
6 |
import { STS3215_PROTOCOL } from "./sts3215-protocol.js";
|
@@ -15,15 +15,14 @@ export interface MotorCommunicationPort {
|
|
15 |
}
|
16 |
|
17 |
/**
|
18 |
-
* Read single motor position with
|
19 |
-
* Extracted from calibrate.ts with all proven timing and retry patterns
|
20 |
*/
|
21 |
export async function readMotorPosition(
|
22 |
port: MotorCommunicationPort,
|
23 |
motorId: number
|
24 |
): Promise<number | null> {
|
25 |
try {
|
26 |
-
// Create Read Position packet
|
27 |
const packet = new Uint8Array([
|
28 |
0xff,
|
29 |
0xff, // Header
|
@@ -45,23 +44,23 @@ export async function readMotorPosition(
|
|
45 |
) & 0xff;
|
46 |
packet[7] = checksum;
|
47 |
|
48 |
-
//
|
49 |
let attempts = 0;
|
50 |
|
51 |
while (attempts < STS3215_PROTOCOL.MAX_RETRIES) {
|
52 |
attempts++;
|
53 |
|
54 |
-
//
|
55 |
try {
|
56 |
await port.read(0); // Non-blocking read to clear buffer
|
57 |
} catch (e) {
|
58 |
// Expected - buffer was empty
|
59 |
}
|
60 |
|
61 |
-
// Write command
|
62 |
await port.write(packet);
|
63 |
|
64 |
-
//
|
65 |
await new Promise((resolve) =>
|
66 |
setTimeout(resolve, STS3215_PROTOCOL.WRITE_TO_READ_DELAY)
|
67 |
);
|
@@ -82,7 +81,7 @@ export async function readMotorPosition(
|
|
82 |
// Read timeout, retry
|
83 |
}
|
84 |
|
85 |
-
//
|
86 |
if (attempts < STS3215_PROTOCOL.MAX_RETRIES) {
|
87 |
await new Promise((resolve) =>
|
88 |
setTimeout(resolve, STS3215_PROTOCOL.RETRY_DELAY)
|
@@ -98,8 +97,7 @@ export async function readMotorPosition(
|
|
98 |
}
|
99 |
|
100 |
/**
|
101 |
-
* Read all motor positions
|
102 |
-
* Exactly matches calibrate.ts readMotorPositions() function
|
103 |
*/
|
104 |
export async function readAllMotorPositions(
|
105 |
port: MotorCommunicationPort,
|
@@ -120,7 +118,7 @@ export async function readAllMotorPositions(
|
|
120 |
motorPositions.push(fallback);
|
121 |
}
|
122 |
|
123 |
-
//
|
124 |
await new Promise((resolve) =>
|
125 |
setTimeout(resolve, STS3215_PROTOCOL.INTER_MOTOR_DELAY)
|
126 |
);
|
@@ -167,7 +165,6 @@ export async function writeMotorPosition(
|
|
167 |
|
168 |
/**
|
169 |
* Generic function to write a 2-byte value to a motor register
|
170 |
-
* Matches calibrate.ts writeMotorRegister() exactly
|
171 |
*/
|
172 |
export async function writeMotorRegister(
|
173 |
port: MotorCommunicationPort,
|
@@ -200,7 +197,7 @@ export async function writeMotorRegister(
|
|
200 |
) & 0xff;
|
201 |
packet[8] = checksum;
|
202 |
|
203 |
-
//
|
204 |
await port.write(packet);
|
205 |
|
206 |
// Wait for response (silent unless error)
|
|
|
1 |
/**
|
2 |
* Motor Communication Utilities
|
3 |
+
* STS3215 motor reading and writing operations
|
4 |
*/
|
5 |
|
6 |
import { STS3215_PROTOCOL } from "./sts3215-protocol.js";
|
|
|
15 |
}
|
16 |
|
17 |
/**
|
18 |
+
* Read single motor position with retry logic
|
|
|
19 |
*/
|
20 |
export async function readMotorPosition(
|
21 |
port: MotorCommunicationPort,
|
22 |
motorId: number
|
23 |
): Promise<number | null> {
|
24 |
try {
|
25 |
+
// Create Read Position packet
|
26 |
const packet = new Uint8Array([
|
27 |
0xff,
|
28 |
0xff, // Header
|
|
|
44 |
) & 0xff;
|
45 |
packet[7] = checksum;
|
46 |
|
47 |
+
// Retry communication with timeouts
|
48 |
let attempts = 0;
|
49 |
|
50 |
while (attempts < STS3215_PROTOCOL.MAX_RETRIES) {
|
51 |
attempts++;
|
52 |
|
53 |
+
// Clear any remaining data in buffer first
|
54 |
try {
|
55 |
await port.read(0); // Non-blocking read to clear buffer
|
56 |
} catch (e) {
|
57 |
// Expected - buffer was empty
|
58 |
}
|
59 |
|
60 |
+
// Write command
|
61 |
await port.write(packet);
|
62 |
|
63 |
+
// Wait for motor response
|
64 |
await new Promise((resolve) =>
|
65 |
setTimeout(resolve, STS3215_PROTOCOL.WRITE_TO_READ_DELAY)
|
66 |
);
|
|
|
81 |
// Read timeout, retry
|
82 |
}
|
83 |
|
84 |
+
// Wait between retry attempts
|
85 |
if (attempts < STS3215_PROTOCOL.MAX_RETRIES) {
|
86 |
await new Promise((resolve) =>
|
87 |
setTimeout(resolve, STS3215_PROTOCOL.RETRY_DELAY)
|
|
|
97 |
}
|
98 |
|
99 |
/**
|
100 |
+
* Read all motor positions
|
|
|
101 |
*/
|
102 |
export async function readAllMotorPositions(
|
103 |
port: MotorCommunicationPort,
|
|
|
118 |
motorPositions.push(fallback);
|
119 |
}
|
120 |
|
121 |
+
// Delay between motor reads
|
122 |
await new Promise((resolve) =>
|
123 |
setTimeout(resolve, STS3215_PROTOCOL.INTER_MOTOR_DELAY)
|
124 |
);
|
|
|
165 |
|
166 |
/**
|
167 |
* Generic function to write a 2-byte value to a motor register
|
|
|
168 |
*/
|
169 |
export async function writeMotorRegister(
|
170 |
port: MotorCommunicationPort,
|
|
|
197 |
) & 0xff;
|
198 |
packet[8] = checksum;
|
199 |
|
200 |
+
// Write register value
|
201 |
await port.write(packet);
|
202 |
|
203 |
// Wait for response (silent unless error)
|
src/lerobot/web/utils/robot-connection-manager.ts
DELETED
@@ -1,168 +0,0 @@
|
|
1 |
-
/**
|
2 |
-
* Internal Robot Connection Manager Utility
|
3 |
-
* Singleton manager providing shared robot connection state and communication
|
4 |
-
* Used internally by calibrate, teleoperate, find_port, etc.
|
5 |
-
*
|
6 |
-
* This is an internal utility - users should not import this directly.
|
7 |
-
* Instead, use the public APIs: calibrate(), findPort(), teleoperate()
|
8 |
-
*/
|
9 |
-
|
10 |
-
import type { MotorCommunicationPort } from "./motor-communication.js";
|
11 |
-
import type { SerialPort } from "../types/robot-connection.js";
|
12 |
-
import type {
|
13 |
-
RobotConnectionState,
|
14 |
-
RobotConnectionManager,
|
15 |
-
} from "../types/robot-processes.js";
|
16 |
-
|
17 |
-
/**
|
18 |
-
* Singleton Robot Connection Manager Implementation
|
19 |
-
*/
|
20 |
-
class RobotConnectionManagerImpl implements RobotConnectionManager {
|
21 |
-
private port: SerialPort | null = null;
|
22 |
-
private state: RobotConnectionState = { isConnected: false };
|
23 |
-
private stateChangeCallbacks: Set<(state: RobotConnectionState) => void> =
|
24 |
-
new Set();
|
25 |
-
|
26 |
-
getState(): RobotConnectionState {
|
27 |
-
return { ...this.state };
|
28 |
-
}
|
29 |
-
|
30 |
-
async connect(
|
31 |
-
port: SerialPort,
|
32 |
-
robotType: string,
|
33 |
-
robotId: string,
|
34 |
-
serialNumber: string
|
35 |
-
): Promise<void> {
|
36 |
-
try {
|
37 |
-
// Validate port is open
|
38 |
-
if (!port.readable || !port.writable) {
|
39 |
-
throw new Error("Port is not open");
|
40 |
-
}
|
41 |
-
|
42 |
-
// Update connection state
|
43 |
-
this.port = port;
|
44 |
-
this.state = {
|
45 |
-
isConnected: true,
|
46 |
-
robotType: robotType as "so100_follower" | "so100_leader",
|
47 |
-
robotId,
|
48 |
-
serialNumber,
|
49 |
-
lastError: undefined,
|
50 |
-
};
|
51 |
-
|
52 |
-
this.notifyStateChange();
|
53 |
-
console.log(
|
54 |
-
`🤖 Robot connected: ${robotType} (${robotId}) - ${serialNumber}`
|
55 |
-
);
|
56 |
-
} catch (error) {
|
57 |
-
const errorMessage =
|
58 |
-
error instanceof Error ? error.message : "Connection failed";
|
59 |
-
this.state = {
|
60 |
-
isConnected: false,
|
61 |
-
lastError: errorMessage,
|
62 |
-
};
|
63 |
-
this.notifyStateChange();
|
64 |
-
throw error;
|
65 |
-
}
|
66 |
-
}
|
67 |
-
|
68 |
-
async disconnect(): Promise<void> {
|
69 |
-
this.port = null;
|
70 |
-
this.state = { isConnected: false };
|
71 |
-
this.notifyStateChange();
|
72 |
-
console.log("🤖 Robot disconnected");
|
73 |
-
}
|
74 |
-
|
75 |
-
getPort(): SerialPort | null {
|
76 |
-
return this.port;
|
77 |
-
}
|
78 |
-
|
79 |
-
async writeData(data: Uint8Array): Promise<void> {
|
80 |
-
if (!this.port?.writable) {
|
81 |
-
throw new Error("Robot not connected or port not writable");
|
82 |
-
}
|
83 |
-
|
84 |
-
const writer = this.port.writable.getWriter();
|
85 |
-
try {
|
86 |
-
await writer.write(data);
|
87 |
-
} finally {
|
88 |
-
writer.releaseLock();
|
89 |
-
}
|
90 |
-
}
|
91 |
-
|
92 |
-
async readData(timeout: number = 1000): Promise<Uint8Array> {
|
93 |
-
if (!this.port?.readable) {
|
94 |
-
throw new Error("Robot not connected or port not readable");
|
95 |
-
}
|
96 |
-
|
97 |
-
const reader = this.port.readable.getReader();
|
98 |
-
|
99 |
-
try {
|
100 |
-
const timeoutPromise = new Promise<never>((_, reject) => {
|
101 |
-
setTimeout(() => reject(new Error("Read timeout")), timeout);
|
102 |
-
});
|
103 |
-
|
104 |
-
const readPromise = reader
|
105 |
-
.read()
|
106 |
-
.then((result: { done: boolean; value?: Uint8Array }) => {
|
107 |
-
if (result.done || !result.value) {
|
108 |
-
throw new Error("Read failed - port closed or no data");
|
109 |
-
}
|
110 |
-
return result.value;
|
111 |
-
});
|
112 |
-
|
113 |
-
return await Promise.race([readPromise, timeoutPromise]);
|
114 |
-
} finally {
|
115 |
-
reader.releaseLock();
|
116 |
-
}
|
117 |
-
}
|
118 |
-
|
119 |
-
onStateChange(callback: (state: RobotConnectionState) => void): () => void {
|
120 |
-
this.stateChangeCallbacks.add(callback);
|
121 |
-
|
122 |
-
// Return unsubscribe function
|
123 |
-
return () => {
|
124 |
-
this.stateChangeCallbacks.delete(callback);
|
125 |
-
};
|
126 |
-
}
|
127 |
-
|
128 |
-
private notifyStateChange(): void {
|
129 |
-
this.stateChangeCallbacks.forEach((callback) => {
|
130 |
-
try {
|
131 |
-
callback(this.getState());
|
132 |
-
} catch (error) {
|
133 |
-
console.warn("Error in state change callback:", error);
|
134 |
-
}
|
135 |
-
});
|
136 |
-
}
|
137 |
-
}
|
138 |
-
|
139 |
-
// Singleton instance
|
140 |
-
const robotConnectionManager = new RobotConnectionManagerImpl();
|
141 |
-
|
142 |
-
/**
|
143 |
-
* Get the singleton robot connection manager
|
144 |
-
* This is the single source of truth for robot connections
|
145 |
-
*/
|
146 |
-
export function getRobotConnectionManager(): RobotConnectionManager {
|
147 |
-
return robotConnectionManager;
|
148 |
-
}
|
149 |
-
|
150 |
-
/**
|
151 |
-
* Adapter to make robot connection manager compatible with motor-communication utilities
|
152 |
-
* Provides the MotorCommunicationPort interface for the singleton manager
|
153 |
-
*/
|
154 |
-
export class RobotConnectionManagerAdapter implements MotorCommunicationPort {
|
155 |
-
private manager: RobotConnectionManager;
|
156 |
-
|
157 |
-
constructor(manager: RobotConnectionManager) {
|
158 |
-
this.manager = manager;
|
159 |
-
}
|
160 |
-
|
161 |
-
async write(data: Uint8Array): Promise<void> {
|
162 |
-
return this.manager.writeData(data);
|
163 |
-
}
|
164 |
-
|
165 |
-
async read(timeout?: number): Promise<Uint8Array> {
|
166 |
-
return this.manager.readData(timeout);
|
167 |
-
}
|
168 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lerobot/web/utils/serial-port-wrapper.ts
CHANGED
@@ -5,7 +5,6 @@
|
|
5 |
|
6 |
/**
|
7 |
* Web Serial Port wrapper - direct write/read with immediate lock release
|
8 |
-
* Follows Chrome documentation exactly for proper Web Serial API usage
|
9 |
*/
|
10 |
export class WebSerialPortWrapper {
|
11 |
private port: SerialPort;
|
|
|
5 |
|
6 |
/**
|
7 |
* Web Serial Port wrapper - direct write/read with immediate lock release
|
|
|
8 |
*/
|
9 |
export class WebSerialPortWrapper {
|
10 |
private port: SerialPort;
|
src/lerobot/web/utils/sts3215-protocol.ts
CHANGED
@@ -26,7 +26,7 @@ export const STS3215_PROTOCOL = {
|
|
26 |
MIN_POSITION_LIMIT_LENGTH: 2,
|
27 |
MAX_POSITION_LIMIT_LENGTH: 2,
|
28 |
|
29 |
-
// Communication timing
|
30 |
WRITE_TO_READ_DELAY: 10,
|
31 |
RETRY_DELAY: 20,
|
32 |
INTER_MOTOR_DELAY: 10,
|
|
|
26 |
MIN_POSITION_LIMIT_LENGTH: 2,
|
27 |
MAX_POSITION_LIMIT_LENGTH: 2,
|
28 |
|
29 |
+
// Communication timing
|
30 |
WRITE_TO_READ_DELAY: 10,
|
31 |
RETRY_DELAY: 20,
|
32 |
INTER_MOTOR_DELAY: 10,
|