LeRobot.js / packages /web /src /utils /motor-communication.ts
NERDDISCO's picture
feat: move "src/lerobot/web" to "packages/web/src"
b664dbe
raw
history blame
6.63 kB
/**
* Motor Communication Utilities
* STS3215 motor reading and writing operations
*/
import { STS3215_PROTOCOL } from "./sts3215-protocol.js";
/**
* Interface for motor communication port
* Compatible with both WebSerialPortWrapper and RobotConnectionManager
*/
export interface MotorCommunicationPort {
write(data: Uint8Array): Promise<void>;
read(timeout?: number): Promise<Uint8Array>;
}
/**
* Read single motor position with retry logic
*/
export async function readMotorPosition(
port: MotorCommunicationPort,
motorId: number
): Promise<number | null> {
try {
// Create Read Position packet
const packet = new Uint8Array([
0xff,
0xff, // Header
motorId, // Servo ID
0x04, // Length
0x02, // Instruction: READ_DATA
STS3215_PROTOCOL.PRESENT_POSITION_ADDRESS, // Present_Position register address
0x02, // Data length (2 bytes)
0x00, // Checksum placeholder
]);
const checksum =
~(
motorId +
0x04 +
0x02 +
STS3215_PROTOCOL.PRESENT_POSITION_ADDRESS +
0x02
) & 0xff;
packet[7] = checksum;
// Retry communication with timeouts
let attempts = 0;
while (attempts < STS3215_PROTOCOL.MAX_RETRIES) {
attempts++;
// Clear any remaining data in buffer first
try {
await port.read(0); // Non-blocking read to clear buffer
} catch (e) {
// Expected - buffer was empty
}
// Write command
await port.write(packet);
// Wait for motor response
await new Promise((resolve) =>
setTimeout(resolve, STS3215_PROTOCOL.WRITE_TO_READ_DELAY)
);
try {
const response = await port.read(150);
if (response.length >= 7) {
const id = response[2];
const error = response[4];
if (id === motorId && error === 0) {
const position = response[5] | (response[6] << 8);
return position;
}
}
} catch (readError) {
// Read timeout, retry
}
// Wait between retry attempts
if (attempts < STS3215_PROTOCOL.MAX_RETRIES) {
await new Promise((resolve) =>
setTimeout(resolve, STS3215_PROTOCOL.RETRY_DELAY)
);
}
}
// If all attempts failed, return null
return null;
} catch (error) {
return null;
}
}
/**
* Read all motor positions
*/
export async function readAllMotorPositions(
port: MotorCommunicationPort,
motorIds: number[]
): Promise<number[]> {
const motorPositions: number[] = [];
for (let i = 0; i < motorIds.length; i++) {
const motorId = motorIds[i];
const position = await readMotorPosition(port, motorId);
if (position !== null) {
motorPositions.push(position);
} else {
// Use fallback value for failed reads
const fallback = Math.floor((STS3215_PROTOCOL.RESOLUTION - 1) / 2);
motorPositions.push(fallback);
}
// Delay between motor reads
await new Promise((resolve) =>
setTimeout(resolve, STS3215_PROTOCOL.INTER_MOTOR_DELAY)
);
}
return motorPositions;
}
/**
* Write motor goal position
*/
export async function writeMotorPosition(
port: MotorCommunicationPort,
motorId: number,
position: number
): Promise<void> {
// STS3215 Write Goal_Position packet
const packet = new Uint8Array([
0xff,
0xff, // Header
motorId, // Servo ID
0x05, // Length
0x03, // Instruction: WRITE_DATA
STS3215_PROTOCOL.GOAL_POSITION_ADDRESS, // Goal_Position register address
position & 0xff, // Position low byte
(position >> 8) & 0xff, // Position high byte
0x00, // Checksum placeholder
]);
// Calculate checksum
const checksum =
~(
motorId +
0x05 +
0x03 +
STS3215_PROTOCOL.GOAL_POSITION_ADDRESS +
(position & 0xff) +
((position >> 8) & 0xff)
) & 0xff;
packet[8] = checksum;
await port.write(packet);
}
/**
* Generic function to write a 2-byte value to a motor register
*/
export async function writeMotorRegister(
port: MotorCommunicationPort,
motorId: number,
registerAddress: number,
value: number
): Promise<void> {
// Create Write Register packet
const packet = new Uint8Array([
0xff,
0xff, // Header
motorId, // Servo ID
0x05, // Length
0x03, // Instruction: WRITE_DATA
registerAddress, // Register address
value & 0xff, // Data_L (low byte)
(value >> 8) & 0xff, // Data_H (high byte)
0x00, // Checksum placeholder
]);
// Calculate checksum
const checksum =
~(
motorId +
0x05 +
0x03 +
registerAddress +
(value & 0xff) +
((value >> 8) & 0xff)
) & 0xff;
packet[8] = checksum;
// Write register value
await port.write(packet);
// Wait for response (silent unless error)
try {
await port.read(200);
} catch (error) {
// Silent - response not required for successful operation
}
}
/**
* Lock a motor (motor will hold its position and resist movement)
*/
export async function lockMotor(
port: MotorCommunicationPort,
motorId: number
): Promise<void> {
await writeMotorRegister(
port,
motorId,
STS3215_PROTOCOL.TORQUE_ENABLE_ADDRESS,
1
);
// Small delay for command processing
await new Promise((resolve) =>
setTimeout(resolve, STS3215_PROTOCOL.WRITE_TO_READ_DELAY)
);
}
/**
* Release a motor (motor can be moved freely by hand)
*/
export async function releaseMotor(
port: MotorCommunicationPort,
motorId: number
): Promise<void> {
await writeMotorRegister(
port,
motorId,
STS3215_PROTOCOL.TORQUE_ENABLE_ADDRESS,
0
);
// Small delay for command processing
await new Promise((resolve) =>
setTimeout(resolve, STS3215_PROTOCOL.WRITE_TO_READ_DELAY)
);
}
/**
* Release motors (motors can be moved freely - perfect for calibration)
*/
export async function releaseMotors(
port: MotorCommunicationPort,
motorIds: number[]
): Promise<void> {
for (const motorId of motorIds) {
await releaseMotor(port, motorId);
// Small delay between motors
await new Promise((resolve) =>
setTimeout(resolve, STS3215_PROTOCOL.INTER_MOTOR_DELAY)
);
}
}
/**
* Lock motors (motors will hold their positions - perfect after calibration)
*/
export async function lockMotors(
port: MotorCommunicationPort,
motorIds: number[]
): Promise<void> {
for (const motorId of motorIds) {
await lockMotor(port, motorId);
// Small delay between motors
await new Promise((resolve) =>
setTimeout(resolve, STS3215_PROTOCOL.INTER_MOTOR_DELAY)
);
}
}