dlouapre HF Staff commited on
Commit
563f5e2
·
1 Parent(s): c2174e8

Add fallback for Spaces deployment with duration limitation

Browse files
Files changed (2) hide show
  1. README.md +3 -0
  2. frontend/app.js +34 -26
README.md CHANGED
@@ -67,6 +67,9 @@ uv run python app.py
67
  ## Current Limitations
68
 
69
  - Sound and picture actions are not yet implemented in execution
 
 
 
70
 
71
  ## Deployment to HuggingFace Spaces
72
 
 
67
  ## Current Limitations
68
 
69
  - Sound and picture actions are not yet implemented in execution
70
+ - **On HuggingFace Spaces**: Due to browser security policies (HTTPS → HTTP localhost), movement durations may not be fully respected. For accurate timing, run the demo locally.
71
+ - Spaces version uses WebSocket fallback which moves faster than specified durations
72
+ - Local version uses REST API `/goto` endpoint with proper duration support
73
 
74
  ## Deployment to HuggingFace Spaces
75
 
frontend/app.js CHANGED
@@ -355,27 +355,21 @@ function matrixToEulerXYZ(rotMatrix) {
355
  }
356
  }
357
 
358
- // Execute a single IR action (send to robot using /goto endpoint)
359
  async function executeIRAction(action) {
360
  if (!state.robotConnected) {
361
  throw new Error('Robot not connected');
362
  }
363
 
364
- const gotoRequest = {
365
- duration: action.duration,
366
- interpolation: action.interpolation || 'minjerk'
367
- };
368
-
369
  if (action.head_pose) {
370
- // Extract translation from 4x4 matrix
371
  const x = action.head_pose[0][3];
372
  const y = action.head_pose[1][3];
373
  const z = action.head_pose[2][3];
374
-
375
- // Extract rotation (euler angles) from 3x3 rotation matrix portion
376
  const euler = matrixToEulerXYZ(action.head_pose);
377
 
378
- gotoRequest.head_pose = {
379
  x: x,
380
  y: y,
381
  z: z,
@@ -385,16 +379,18 @@ async function executeIRAction(action) {
385
  };
386
  }
387
 
388
- if (action.antennas) {
389
- gotoRequest.antennas = action.antennas;
390
- }
 
 
 
 
391
 
392
- if (action.body_yaw !== null) {
393
- gotoRequest.body_yaw = action.body_yaw;
394
- }
395
 
396
- // Send POST request to /goto endpoint with duration
397
- try {
398
  const response = await fetch('http://localhost:8000/api/move/goto', {
399
  method: 'POST',
400
  headers: { 'Content-Type': 'application/json' },
@@ -402,18 +398,30 @@ async function executeIRAction(action) {
402
  });
403
 
404
  if (!response.ok) {
405
- const error = await response.json();
406
- throw new Error(error.detail || 'Failed to execute movement');
407
  }
408
 
409
- const result = await response.json();
410
-
411
- // Wait for the movement to complete (duration + small buffer)
412
  await sleep(action.duration * 1000 + 100);
413
 
414
- return result;
415
- } catch (error) {
416
- throw new Error(`Robot movement failed: ${error.message}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
  }
418
  }
419
 
 
355
  }
356
  }
357
 
358
+ // Execute a single IR action (send to robot)
359
  async function executeIRAction(action) {
360
  if (!state.robotConnected) {
361
  throw new Error('Robot not connected');
362
  }
363
 
364
+ // Extract pose data
365
+ let head_pose = null;
 
 
 
366
  if (action.head_pose) {
 
367
  const x = action.head_pose[0][3];
368
  const y = action.head_pose[1][3];
369
  const z = action.head_pose[2][3];
 
 
370
  const euler = matrixToEulerXYZ(action.head_pose);
371
 
372
+ head_pose = {
373
  x: x,
374
  y: y,
375
  z: z,
 
379
  };
380
  }
381
 
382
+ // Try using /goto endpoint (works locally, respects duration)
383
+ // Falls back to WebSocket if fetch fails (e.g., on HuggingFace Spaces HTTPS -> HTTP localhost)
384
+ try {
385
+ const gotoRequest = {
386
+ duration: action.duration,
387
+ interpolation: action.interpolation || 'minjerk'
388
+ };
389
 
390
+ if (head_pose) gotoRequest.head_pose = head_pose;
391
+ if (action.antennas) gotoRequest.antennas = action.antennas;
392
+ if (action.body_yaw !== null) gotoRequest.body_yaw = action.body_yaw;
393
 
 
 
394
  const response = await fetch('http://localhost:8000/api/move/goto', {
395
  method: 'POST',
396
  headers: { 'Content-Type': 'application/json' },
 
398
  });
399
 
400
  if (!response.ok) {
401
+ throw new Error('goto endpoint failed');
 
402
  }
403
 
404
+ // Wait for movement to complete
 
 
405
  await sleep(action.duration * 1000 + 100);
406
 
407
+ } catch (fetchError) {
408
+ // Fallback to WebSocket (for Spaces deployment)
409
+ // Note: This won't respect duration perfectly - movements will be fast
410
+ console.warn('Using WebSocket fallback (duration may not be accurate):', fetchError.message);
411
+
412
+ if (!state.robotWs || state.robotWs.readyState !== WebSocket.OPEN) {
413
+ throw new Error('Robot WebSocket not connected');
414
+ }
415
+
416
+ const wsMessage = {};
417
+ if (head_pose) wsMessage.target_head_pose = head_pose;
418
+ if (action.antennas) wsMessage.target_antennas = action.antennas;
419
+ if (action.body_yaw !== null) wsMessage.target_body_yaw = action.body_yaw;
420
+
421
+ state.robotWs.send(JSON.stringify(wsMessage));
422
+
423
+ // Wait for duration even though robot may move faster
424
+ await sleep(action.duration * 1000);
425
  }
426
  }
427