Spaces:
Running
Running
| import type { Producer, RobotCommand } from "../models.js"; | |
| import { ROBOT_CONFIG } from "../config.js"; | |
| import { USBServoDriver } from "./USBServoDriver.js"; | |
| export class USBProducer extends USBServoDriver implements Producer { | |
| private commandQueue: RobotCommand[] = []; | |
| private isProcessingCommands = false; | |
| constructor(config: any) { | |
| super(config, "Producer"); | |
| } | |
| async connect(): Promise<void> { | |
| // Connect to USB first (this triggers browser's device selection dialog) | |
| await this.connectToUSB(); | |
| // Lock servos for software control (producer mode) | |
| await this.lockAllServos(); | |
| // Note: Calibration is checked when operations are actually needed | |
| } | |
| async disconnect(): Promise<void> { | |
| // Stop command processing | |
| this.isProcessingCommands = false; | |
| this.commandQueue = []; | |
| await this.disconnectFromUSB(); | |
| } | |
| async sendCommand(command: RobotCommand): Promise<void> { | |
| if (!this._status.isConnected) { | |
| throw new Error(`[${this.name}] Cannot send command: not connected`); | |
| } | |
| if (!this.isCalibrated) { | |
| throw new Error(`[${this.name}] Cannot send command: not calibrated`); | |
| } | |
| // Add command to queue for processing | |
| this.commandQueue.push(command); | |
| // Limit queue size to prevent memory issues | |
| if (this.commandQueue.length > ROBOT_CONFIG.commands.maxQueueSize) { | |
| this.commandQueue.shift(); // Remove oldest command | |
| } | |
| // Start processing if not already running | |
| if (!this.isProcessingCommands) { | |
| this.processCommandQueue(); | |
| } | |
| } | |
| // Event handlers already in base class | |
| // Private methods | |
| private async processCommandQueue(): Promise<void> { | |
| if (this.isProcessingCommands || !this._status.isConnected) { | |
| return; | |
| } | |
| this.isProcessingCommands = true; | |
| while (this.commandQueue.length > 0 && this._status.isConnected) { | |
| const command = this.commandQueue.shift(); | |
| if (command) { | |
| try { | |
| await this.executeCommand(command); | |
| } catch (error) { | |
| console.error(`[${this.name}] Command execution failed:`, error); | |
| // Continue processing other commands | |
| } | |
| } | |
| } | |
| this.isProcessingCommands = false; | |
| } | |
| private async executeCommand(command: RobotCommand): Promise<void> { | |
| if (!this.scsServoSDK || !this._status.isConnected) { | |
| return; | |
| } | |
| try { | |
| // Convert normalized values to servo positions using calibration (required) | |
| const servoCommands = new Map<number, number>(); | |
| command.joints.forEach((joint) => { | |
| const servoId = this.jointToServoMap[joint.name as keyof typeof this.jointToServoMap]; | |
| if (servoId) { | |
| const servoPosition = this.denormalizeValue(joint.value, joint.name); | |
| // Clamp to valid servo range | |
| const clampedPosition = Math.max(0, Math.min(4095, Math.round(servoPosition))); | |
| servoCommands.set(servoId, clampedPosition); | |
| } | |
| }); | |
| if (servoCommands.size > 0) { | |
| // Use batch commands when possible for better performance | |
| if (servoCommands.size > 1) { | |
| await this.scsServoSDK.syncWritePositions(servoCommands); | |
| } else { | |
| // Single servo command | |
| const entry = servoCommands.entries().next().value; | |
| if (entry) { | |
| const [servoId, position] = entry; | |
| await this.scsServoSDK.writePosition(servoId, position); | |
| } | |
| } | |
| console.debug(`[${this.name}] Sent positions to ${servoCommands.size} servos`); | |
| } | |
| } catch (error) { | |
| console.error(`[${this.name}] Failed to execute command:`, error); | |
| throw error; | |
| } | |
| } | |
| } | |