/** * Consumer client for receiving robot commands in LeRobot Arena */ import { RoboticsClientCore } from './core.js'; import type { WebSocketMessage, JointUpdateMessage, StateSyncMessage, ClientOptions, JointUpdateCallback, StateSyncCallback, } from './types.js'; export class RoboticsConsumer extends RoboticsClientCore { // Event callbacks private onStateSyncCallback: StateSyncCallback | null = null; private onJointUpdateCallback: JointUpdateCallback | null = null; constructor(baseUrl = 'http://localhost:8000', options: ClientOptions = {}) { super(baseUrl, options); } // ============= CONSUMER CONNECTION ============= async connect(workspaceId: string, roomId: string, participantId?: string): Promise { return this.connectToRoom(workspaceId, roomId, 'consumer', participantId); } // ============= CONSUMER METHODS ============= async getStateSyncAsync(): Promise> { if (!this.workspaceId || !this.roomId) { throw new Error('Must be connected to a room'); } const state = await this.getRoomState(this.workspaceId, this.roomId); return state.joints; } // ============= EVENT CALLBACKS ============= onStateSync(callback: StateSyncCallback): void { this.onStateSyncCallback = callback; } onJointUpdate(callback: JointUpdateCallback): void { this.onJointUpdateCallback = callback; } // ============= MESSAGE HANDLING ============= protected override handleRoleSpecificMessage(message: WebSocketMessage): void { switch (message.type) { case 'state_sync': this.handleStateSync(message as StateSyncMessage); break; case 'joint_update': this.handleJointUpdate(message as JointUpdateMessage); break; case 'emergency_stop': console.warn(`🚨 Emergency stop: ${message.reason || 'Unknown reason'}`); this.handleError(`Emergency stop: ${message.reason || 'Unknown reason'}`); break; case 'error': console.error(`Server error: ${message.message}`); this.handleError(message.message); break; default: console.warn(`Unknown message type for consumer: ${message.type}`); } } private handleStateSync(message: StateSyncMessage): void { if (this.onStateSyncCallback) { this.onStateSyncCallback(message.data); } this.emit('stateSync', message.data); } private handleJointUpdate(message: JointUpdateMessage): void { if (this.onJointUpdateCallback) { this.onJointUpdateCallback(message.data); } this.emit('jointUpdate', message.data); } // ============= UTILITY METHODS ============= /** * Create a consumer and automatically connect to a room */ static async createAndConnect( workspaceId: string, roomId: string, baseUrl = 'http://localhost:8000', participantId?: string ): Promise { const consumer = new RoboticsConsumer(baseUrl); const connected = await consumer.connect(workspaceId, roomId, participantId); if (!connected) { throw new Error('Failed to connect as consumer'); } return consumer; } }