/** * 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; read(timeout?: number): Promise; } /** * Read single motor position using OLD WORKING approach */ export async function readMotorPosition( port: MotorCommunicationPort, motorId: number ): Promise { 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((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 { 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 { // 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 { // 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 { 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 { 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 { 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 { for (const motorId of motorIds) { await releaseMotor(port, motorId); // Small delay between motors await new Promise((resolve) => setTimeout(resolve, STS3215_PROTOCOL.INTER_MOTOR_DELAY) ); } }