Spaces:
Sleeping
Sleeping
| /** | |
| * Producer client for controlling robots in RobotHub TransportServer | |
| */ | |
| import { RoboticsClientCore } from './core.js'; | |
| import type { | |
| JointData, | |
| WebSocketMessage, | |
| JointUpdateMessage, | |
| ClientOptions, | |
| } from './types.js'; | |
| export class RoboticsProducer extends RoboticsClientCore { | |
| constructor(baseUrl: string, options: ClientOptions = {}) { | |
| super(baseUrl, options); | |
| } | |
| // ============= PRODUCER CONNECTION ============= | |
| async connect(workspaceId: string, roomId: string, participantId?: string): Promise<boolean> { | |
| return this.connectToRoom(workspaceId, roomId, 'producer', participantId); | |
| } | |
| // ============= PRODUCER METHODS ============= | |
| async sendJointUpdate(joints: JointData[]): Promise<void> { | |
| if (!this.connected || !this.websocket) { | |
| throw new Error('Must be connected to send joint updates'); | |
| } | |
| const message: JointUpdateMessage = { | |
| type: 'joint_update', | |
| data: joints, | |
| timestamp: new Date().toISOString(), | |
| }; | |
| this.websocket.send(JSON.stringify(message)); | |
| } | |
| async sendStateSync(state: Record<string, number>): Promise<void> { | |
| if (!this.connected || !this.websocket) { | |
| throw new Error('Must be connected to send state sync'); | |
| } | |
| // Convert state object to joint updates format | |
| const joints: JointData[] = Object.entries(state).map(([name, value]) => ({ | |
| name, | |
| value, | |
| })); | |
| await this.sendJointUpdate(joints); | |
| } | |
| async sendEmergencyStop(reason = 'Emergency stop'): Promise<void> { | |
| if (!this.connected || !this.websocket) { | |
| throw new Error('Must be connected to send emergency stop'); | |
| } | |
| const message = { | |
| type: 'emergency_stop' as const, | |
| reason, | |
| timestamp: new Date().toISOString(), | |
| }; | |
| this.websocket.send(JSON.stringify(message)); | |
| } | |
| // ============= MESSAGE HANDLING ============= | |
| protected override handleRoleSpecificMessage(message: WebSocketMessage): void { | |
| switch (message.type) { | |
| 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 producer: ${message.type}`); | |
| } | |
| } | |
| // ============= UTILITY METHODS ============= | |
| /** | |
| * Create a room and automatically connect as producer | |
| */ | |
| static async createAndConnect( | |
| baseUrl: string, | |
| workspaceId?: string, | |
| roomId?: string, | |
| participantId?: string | |
| ): Promise<RoboticsProducer> { | |
| const producer = new RoboticsProducer(baseUrl); | |
| const roomData = await producer.createRoom(workspaceId, roomId); | |
| const connected = await producer.connect(roomData.workspaceId, roomData.roomId, participantId); | |
| if (!connected) { | |
| throw new Error('Failed to connect as producer'); | |
| } | |
| return producer; | |
| } | |
| /** | |
| * Get the current room ID (useful when auto-created) | |
| */ | |
| get currentRoomId(): string | null { | |
| return this.roomId; | |
| } | |
| } |