Spaces:
Running
Running
File size: 3,221 Bytes
02eac4b |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
/**
* Producer client for controlling robots in LeRobot Arena
*/
import { RoboticsClientCore } from './core.js';
import type {
JointData,
WebSocketMessage,
JointUpdateMessage,
ClientOptions,
} from './types.js';
export class RoboticsProducer extends RoboticsClientCore {
constructor(baseUrl = 'http://localhost:8000', 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 = 'http://localhost:8000',
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;
}
} |