Spaces:
Running
Running
/** | |
* Motor Communication Utilities | |
* STS3215 motor reading and writing operations for Node.js | |
*/ | |
import { STS3215_PROTOCOL } from "./sts3215-protocol.js"; | |
import type { NodeSerialPortWrapper } from "./serial-port-wrapper.js"; | |
/** | |
* Interface for motor communication port | |
* Compatible with NodeSerialPortWrapper | |
*/ | |
export interface MotorCommunicationPort { | |
write(data: Uint8Array): Promise<void>; | |
read(timeout?: number): Promise<Uint8Array>; | |
} | |
/** | |
* Read single motor position using OLD WORKING approach | |
*/ | |
export async function readMotorPosition( | |
port: MotorCommunicationPort, | |
motorId: number | |
): Promise<number | null> { | |
try { | |
// Create Read Position packet (exactly like old working version) | |
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 using OLD WORKING approach | |
for ( | |
let attempts = 1; | |
attempts <= STS3215_PROTOCOL.MAX_RETRIES; | |
attempts++ | |
) { | |
try { | |
// Write packet | |
await port.write(packet); | |
// Use OLD WORKING approach: direct port.once('data') event with proper cleanup | |
const timeout = 100 * attempts; // Progressive timeout like old approach | |
const response = await new Promise<Uint8Array>((resolve, reject) => { | |
// Access underlying SerialPort for event-based reading | |
const underlyingPort = | |
(port as any).underlyingPort || (port as any).port; | |
if (!underlyingPort || !underlyingPort.once) { | |
reject(new Error("Cannot access underlying port for old approach")); | |
return; | |
} | |
let isResolved = false; | |
const dataHandler = (data: Buffer) => { | |
if (!isResolved) { | |
isResolved = true; | |
clearTimeout(timer); | |
resolve(new Uint8Array(data)); | |
} | |
}; | |
const timer = setTimeout(() => { | |
if (!isResolved) { | |
isResolved = true; | |
// CRITICAL: Remove the event listener to prevent memory leak | |
underlyingPort.removeListener("data", dataHandler); | |
reject(new Error("Read timeout")); | |
} | |
}, timeout); | |
// Attach event listener | |
underlyingPort.once("data", dataHandler); | |
}); | |
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) { | |
if (attempts < STS3215_PROTOCOL.MAX_RETRIES) { | |
// Wait between retry attempts (like old approach) | |
const retryDelay = STS3215_PROTOCOL.RETRY_DELAY * attempts; | |
await new Promise((resolve) => setTimeout(resolve, retryDelay)); | |
} | |
} | |
} | |
// 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) | |
); | |
} | |
/** | |
* 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) | |
); | |
} | |
} | |
/** | |
* 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) | |
); | |
} | |
} | |