blanchon's picture
Update
8eeb37a
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;
}
}
}